shiny downgrade fontawesome 5 to 4 - r

I work on a shiny project quite entangled with fontawesome 4.7, and it has brought us great value. As a free user of fontawesome, I don't see we have any advantage of upgrading to 5.3.1. Many of the free icons have become uglier/cruder, and one would have to pay for the pro version to get the icon styles similar to 4.7.
Example table available in 4.7 with 9 cells
in 5.3 table is onle free as 4 cells and rather chubby lines. The old 9 cell format is only available for pro users
From my own simple perspective, it seems the fontawesome team intends to strongly nudge their free users to go pro.
Rstudio shiny 1.1 links to fontawesome 4.7.1
Rstudio shiny 1.2 links to fontawesome 5.3.1
Are there any easy ways to both have shiny 1.2 and fontawesome 4.7.1?
EDIT
Link by pork chop seems very relevant, I will try it out and update...

Download fontawesome 4.7.1 & unzip
insert code below in global.R
update path to unzipped fontawesome
.... and then shiny can do both fontawesome 4.7.1 and +5. This specific solution copies as suggested by Pork Chop old version of font-awesome in installed shiny library. Also I updated the icon()-function so it is possible to have fontawesome versions to coexist and to ensure correct linking. In this solution a new icon() function is placed in globalEnv hence in top of search()-path. That saved my code base legacy issues without changing anything else.
However for making a new shiny-application, I would name icon-function icon_legacy() to avoid relying on search()-path or implement in a support R-package for shiny-application.
##install new shiny version
install.packages("shiny") #install newest shiny
library(shiny)
library(htmltools)
#source in this function to globalEnv
#' Legacy means good old iconic times
#'
#' #param local_path_fa_4.7.1
#' #param shiny_path
#'
#' #return
#' #export
#' #import shiny htmltools
#' #details #this installs legacy font-awesome and return a function similar to icon
#'
#' #examples
#'
#' install.packages("shiny") #install newest shiny
#' library(shiny)
#' library(htmltools)
#' my_fa_path = "./misc/global_source/fa_shiny_4.7.1/font-awesome"
#' icon_legacy = activate_icon_legacy(my_fa_path) #tadaaa use icon_legacy now
#' #btw css pseudo-elements seem to work out-of-the-box also
#'
#' icon = icon_legacy #you may also feel like placing icon in global env to override shiny::icon
activate_icon_legacy = function(
local_path_fa_4.7.1,
shiny_path = system.file(package="shiny")
) {
#find out what version of shiny is installed
uses_fontawesome5 = packageVersion("shiny")>=1.2 #because implemented since 1.2
shiny_resource_path = paste0(shiny_path,"/www/shared")
misses_fontawesome4 = !"font-awesome" %in% list.files(shiny_resource_path) #because new fa dir is called 'fontawesome'
#if legacy dir is missing from library copy into installed library
if(uses_fontawesome5 && misses_fontawesome4) {
file.copy(
from = local_path_fa_4.7.1,
to = shiny_resource_path,
recursive = TRUE,copy.mode = FALSE
)
}
#import minor dependency from shiny library into closure
font_awesome_brands = shiny:::font_awesome_brands
tags = htmltools::tags
#source this modified icon() function from library/shiny/R/bootstrap.R
#notice the legacy feature if true will use old fa 4.7.1 else new
icon_legacy <- function(name, class = NULL, lib = "font-awesome",legacy=TRUE) {
prefixes <- list(
"font-awesome" = "fa",
"glyphicon" = "glyphicon"
)
prefix <- prefixes[[lib]]
# determine stylesheet
if (is.null(prefix)) {
stop("Unknown font library '", lib, "' specified. Must be one of ",
paste0('"', names(prefixes), '"', collapse = ", "))
}
# build the icon class (allow name to be null so that other functions
# e.g. buildTabset can pass an explicit class value)
iconClass <- ""
if (!is.null(name)) {
prefix_class <- prefix
if (prefix_class == "fa" && name %in% font_awesome_brands) {
prefix_class <- "fab"
}
iconClass <- paste0(prefix_class, " ", prefix, "-", name)
}
if (!is.null(class))
iconClass <- paste(iconClass, class)
iconTag <- tags$i(class = iconClass)
# font-awesome needs an additional dependency (glyphicon is in bootstrap)
if (lib == "font-awesome") {
if(legacy) {
htmlDependencies(iconTag) <- htmlDependency(
"fontwesome","4.7.1", "www/shared/font-awesome", package = "shiny",
stylesheet = c("css/font-awesome.css","font-awesome.min.css"))
} else {
htmlDependencies(iconTag) <- htmlDependency(
"font-awesome", "5.3.1", "www/shared/fontawesome", package = "shiny",
stylesheet = c("css/all.min.css","css/v4-shims.min.css")
)
}
}
htmltools::browsable(iconTag)
}
return(icon_legacy)
}
#download extract fontawesome 4.7.1 and write path here
my_fa_path = "./misc/global_source/fa_shiny_4.7.1/font-awesome"
icon_legacy = activate_icon_legacy(my_fa_path) #tadaaa use icon_legacy now
#btwcss pseudos seem to work out-of-the-box also
#one may also feel like placing icon_legacy() as icon() in globalEnv to override shiny::icon
#if youre too lazy change all your original code. This will work any code in ui.R and server.R
#however packages with explicit namespaces are likely not overridden by this.
icon = icon_legacy
#now shiny code will behave like this
icon("table",legacy=TRUE) # old style 9 cell table
icon("table",legacy=FALSE) # new fat 4 cell table
#...one may feel like opting for more explicit and strict namespace solution wrapped in some package.
#but that would be a lot more boiler plate code not relevant for this answer
#this solution also fixed my fontawesome CSS pseudo-elements issues

Related

Why Do "rstudio:run:" Hyperlinks Work Only for Functions Exported by "rlang"?

Background
I've often aspired to prototype an R package which serves as a source of truth for styling conventions in (say) diagnostic output, and which enables central updates to those conventions.
After many failed experiments with crayon::hyperlink(), I was excited to stumble across Console hyperlinks to functions like `rlang::last_error()`
which preview documentation when hovered
and execute the code when clicked!
Attempt
On a whim, I prototyped a family of functions (see Code section below) that make universal provisions for such functionality. By inspecting the source code, I was able to replicate the functionality above for other rlang functions. Here we pipe (|>) my function style_run_call0() through cat()
rlang::quo("test") |> style_run_call0() |> cat()
to display in the Console a hyperlink to live code
which previews documentation when hovered
and executes the code when clicked:
Problem
This all works well enough for rlang functions. But for functions from other packages
base::sum(1:10) |> style_run_call0() |> cat()
it wrongly displays a "broken" link:
Even with rlang, the links are broken for any arguments that are calls themselves
rlang::quo(rlang::as_string("test")) |> style_run_call0() |> cat()
and for all private functions
rlang:::ansi_alert() |> style_run_call0() |> cat()
though the links do work for "simple" arguments with operators:
rlang::quo(TRUE || FALSE) |> style_run_call0() |> cat()
#> `rlang::quo(TRUE || FALSE)`
style_run_expr0(TRUE || FALSE) |> cat()
#> `TRUE || FALSE`
Question
I am 99% sure this problem boils down to the rstudio:run: format
style_rlang_run <- function(code) {
style_hyperlink(
paste0("rlang::", code),
paste0("rstudio:run:rlang::", code)
)
}
and its limitations for hyperlinking code.
But why would rlang:::style_rlang_run() need to specify "rlang::" if the "rstudio:run:" accommodated only rlang functions and nothing else?
Code
Text Links
These links are generated from character strings.
# Hyperlink to a URL.
style_hyperlink_url <- function (url, text = NULL, params = NULL) {
# Display text defaults to URL.
if (is.null(text))
text <- url
# Underline the link in blue for classic URL style.
crayon::underline$blue(rlang:::style_hyperlink(text, url, params))
}
# Hyperlink to run code (given as text) interactively in RStudio.
style_run_code <- function(code, text = NULL) {
# Display text defaults to code in backticks.
if (is.null(text))
text <- paste0("`", code, "`")
# Underline the hyperlink in silver to designate code link.
crayon::underline$silver(rlang:::style_hyperlink(text, paste0("rstudio:run:", code)))
}
Live Code
These code links are generated from R language itself.
# Hyperlink to run a (simple) 'call' object as an interactive command in RStudio.
style_run_call <- function(call, text = NULL) {
call_expr <- rlang::get_expr(call)
call_qual <- call_qualify(call_expr)
style_run_code(base::deparse1(call_qual), text)
}
# Hyperlink to run a (simple) literal call as an interactive command in RStudio.
style_run_call0 <- function(call, text = NULL) {
call_quo <- rlang::enquo0(call)
style_run_call(call_quo, text)
}
# Hyperlink to run a (simple) 'expression' object as an interactive command in RStudio.
style_run_expr <- function(expr, text = NULL) {
expr_expr <- rlang::get_expr(expr)
call_quo <- rlang::quo(rlang::eval_bare(!!expr_expr))
# Text defaults to the expression itself, not the code evaluating it.
if (is.null(text))
text <- paste0("`", base::deparse1(expr_expr), "`")
style_run_call(call_quo, text)
}
# Hyperlink to run a (simple) literal 'expression' as an interactive command in RStudio.
style_run_expr0 <- function(expr, text = NULL) {
expr_quo <- rlang::enquo0(expr)
style_run_expr(expr_quo, text)
}
Call Qualification
This function qualifies a call to fn() as pkg::fn().
call_qualify <- function(call) {
if (!rlang::is_call(call))
rlang::abort("`call` must be a call")
if (!rlang::is_call_simple(call))
rlang::abort("`call` must be a simple call")
# Check the namespace that qualifies the function.
call_ns_name <- rlang::call_ns(call)
# Qualify if necessary.
if (is.null(call_ns_name)) {
call_fn <- rlang.call_fn(call)
call_fn_name <- rlang::call_name(call)
call_ns_name <- rlang::ns_env_name(call_fn)
call_ns_sym <- rlang::sym(call_ns_name)
call_fn_sym <- rlang::sym(call_fn_name)
# TODO: Check if namespace exports the function.
if (fn_is_exported(call_fn_name, call_ns_name)) {
qual_sym <- quote(`::`)
} else {
qual_sym <- quote(`:::`)
}
# Assemble the qualified function name.
qual_expr <- quote(`::`(pkg = NULL, name = NULL))
qual_expr[[1]] <- qual_sym
qual_expr$pkg <- call_ns_sym
qual_expr$name <- call_fn_sym
# Assemble the qualified call.
call_expr <- rlang::get_expr(call)
call_expr[[1]] <- qual_expr
call <- rlang::set_expr(call, call_expr)
}
# Return the qualified call.
call
}
Helper Functions
# Function to check if a function is exported (TRUE) from its namespace, or internal (FALSE).
fn_is_exported <- function(fn_name, ns_name) {
# Placeholder.
TRUE
# TODO: Figure out an efficient algorithm.
# Since a function object may be assigned to a new name, perhaps we should match by bytecode instead?
}
# Current styler from "rlang".
.rlang.style_hyperlink <- rlang:::style_hyperlink
# Function to extract the function from a call. Deprecated in "rlang" and reconstructed here.
.rlang.call_fn <- function(call, env = caller_env()) {
expr <- rlang::get_expr(call)
env <- rlang::get_env(call, env)
if (!rlang::is_call(expr)) {
rlang:::abort_call_input_type("call")
}
switch(rlang:::call_type(expr),
recursive = rlang::abort("`call` does not call a named or inlined function"),
inlined = rlang:::node_car(expr),
named = , namespaced = ,
rlang::eval_bare(rlang:::node_car(expr), env)
)
}
Executing base:: functions is explicitly forbidden, see the RStudio PR and discussion in the issue.
If I understand the test code correctly, you can run code of your own package with:
cli::style_hyperlink("show style code", "ide:run:yourpackage::style()")
If yourpackage is not installed, it should just be copied into the console, but not executed.
I'm not really sure what your use case is, but maybe another option would be to generate a link to a help page or vignette in your package?
cli::style_hyperlink("help page", "ide:help:yourpackage::correct_style")

Can R packages add code snippets to users' snippet files?

There are several code snippets that are invaluable to my workflow and play nicely with functions in my custom R package. Can I include these code snippets in my R package so that they are added to users' code snippets (with permissions of course) when they install my package?
Rmd snippet example that creates a sql chunk:
snippet sql
```{sql, connection = conn, output.var = "${1:df}"}
${2}
```
Short answer: Yes
One way to achieve what you want (that works for my package) is:
Store the packages snippet definitions in two text files somewhere in the packages inst/ directory. It's important that the snippets follow exactly the formatting rules (e.g. tabs at the start of the lines, not spaces). I have one file for R code snippets and one for markdown.
Define a function that reads these files and copies their content into RStudios user snippets files. These files are generated at the first attempt to edit the snippets (Tools -> Global Options -> Code -> Edit Snippets) (I think RStudio uses an other, not user exposed file before one tries to edit, not sure though). On ubuntu the RStudio files are called 'r.snippets' and 'markdown.snippets' and are in '~/.R/snippets/'. I also check if the snipped definition already exists, and double check the tabs at the start of the lines before using cat(..., append=TRUE) to add the packages snippet definitions.
I first used an elaborate .onLoad function with configs and all but now I just export a addPackageSnippets function ;)
Edit
Some code:
Part that checks for already existing snippet definitons:
I just read the rstudio file and extract the lines starting with 'snippet'. I do the same for the packages snipptes definition file and use setdiff (one might want to also use trimws on the lists, just in case there is some trailing white-space)
# load package snippets definitions
#
pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)
# Extract names of package snippets
#
pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]
# Extract 'names' of already existing snitppets
#
rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]
# find definitions appearing in packageSnippets but not in rstudioSnippets
# if no snippets are missing go to next file
#
snippetsToCopy <- setdiff(pckgSnippetsFileDefinitions, rstudioSnippetDefinitions)
For context here is the whole 'addPackageSnippets' function. The function is using only the base package, except getOS which returns one of 'linux', 'windows' or 'mac' (i.e. a wrapper around Sys.info()
#' #title Export snippets
#'
#' #description \code{addPackageSnippets} copies all (missing) snippet definitions
#' in 'inst/rstudio/Rsnippets.txt' and 'Rmdsnippets.txt' to the RStudios user snippet location.
#'
#' #return boolean invisible(FALSE) if nothing was added, invisible(TRUE) if snipped definitions were added
#' #export
#'
#' #examples \dontrun{addPackageSnippets()}
addPackageSnippets <- function() {
added <- FALSE
# if not on RStudio or RStudioServer exit
#
if (!nzchar(Sys.getenv("RSTUDIO_USER_IDENTITY"))) {
return(NULL)
}
# Name of files containing snippet code to copy
#
pckgSnippetsFiles <- c("Rsnippets.txt", "Rmdsnippets.txt")
# Name of files to copy into. Order has to be the same
# as in 'pckgSnippetsFiles'
#
rstudioSnippetsFiles <- c("r.snippets", "markdown.snippets")
# Path to directory for RStudios user files depends on OS
#
if (getOS() == "linux") {
rstudioSnippetsPathBase <- "~/.R/snippets"
} else if (getOS() == "windows") {
rstudioSnippetsPathBase <- file.path(path.expand('~'), ".R", "snippets")
} else {
warning(paste0("goSnippets() is only implemented on linux and windows"))
return(NULL)
}
# Read each file in pckgSnippetsFiles and add its contents
#
for (i in seq_along(pckgSnippetsFiles)) {
# Try to get template, if template is not found skip it
#
pckgSnippetsFilesPath <- system.file("rstudio", pckgSnippetsFiles[i], package = "myFunc")
if (pckgSnippetsFilesPath == "") {
next()
}
# load package snippets definitions
#
pckgSnippetsFileContent <- readLines(pckgSnippetsFilesPath)
# Extract names of package snippets
#
pckgSnippetsFileDefinitions <- pckgSnippetsFileContent[grepl("^snippet (.*)", pckgSnippetsFileContent)]
# Construct path for destination file
#
rstudioSnippetsFilePath <- file.path(rstudioSnippetsPathBase, rstudioSnippetsFiles[i])
# If targeted RStudios user file does not exist, raise error (otherwise we would 'remove')
# the default snippets from the 'user file'
#
if (!file.exists(rstudioSnippetsFilePath)) {
stop(paste0( "'", rstudioSnippetsFilePath, "' does not exist yet\n.",
"Use RStudio -> Tools -> Global Options -> Code -> Edit Snippets\n",
"To initalize user defined snippets file by adding dummy snippet\n"))
}
# Extract 'names' of already existing snitppets
#
rstudioSnippetsFileContent <- readLines(rstudioSnippetsFilePath)
rstudioSnippetDefinitions <- rstudioSnippetsFileContent[grepl("^snippet (.*)", rstudioSnippetsFileContent)]
# replace two spaces with tab, ONLY at beginning of string
#
pckgSnippetsFileContentSanitized <- gsub("(?:^ {2})|\\G {2}|\\G\t", "\t", pckgSnippetsFileContent, perl = TRUE)
# find defintions appearing in packageSnippets but not in rstudioSnippets
# if no snippets are missing go to next file
#
snippetsToCopy <- setdiff(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
snippetsNotToCopy <- intersect(trimws(pckgSnippetsFileDefinitions), trimws(rstudioSnippetDefinitions))
if (length(snippetsToCopy) == 0) {
# cat(paste0("(\nFollowing snippets will NOT be added because there is already a snippet with that name: ",
# paste0(snippetsNotToCopy, collapse=", ") ,")"))
next()
}
# Inform user about changes, ask to confirm action
#
if (interactive()) {
cat(paste0("You are about to add the following ", length(snippetsToCopy),
" snippets to '", rstudioSnippetsFilePath, "':\n",
paste0(paste0("-", snippetsToCopy), collapse="\n")))
if (length(snippetsNotToCopy) > 0) {
cat(paste0("\n(The following snippets will NOT be added because there is already a snippet with that name:\n",
paste0(snippetsNotToCopy, collapse=", ") ,")"))
}
answer <- readline(prompt="Do you want to procedd (y/n): ")
if (substr(answer, 1, 1) == "n") {
next()
}
}
# Create list of line numbers where snippet definitons start
# This list is used to determine the end of each definition block
#
allPckgSnippetDefinitonStarts <- grep("^snippet .*", pckgSnippetsFileContentSanitized)
for (s in snippetsToCopy) {
startLine <- grep(paste0("^", s, ".*"), pckgSnippetsFileContentSanitized)
# Find last line of snippet definition:
# First find start of next defintion and return
# previous line number or lastline if already in last definiton
#
endLine <- allPckgSnippetDefinitonStarts[allPckgSnippetDefinitonStarts > startLine][1] -1
if (is.na(endLine)) {
endLine <- length(pckgSnippetsFileContentSanitized)
}
snippetText <- paste0(pckgSnippetsFileContentSanitized[startLine:endLine], collapse = "\n")
# Make sure there is at least one empty line between entries
#
if (tail(readLines(rstudioSnippetsFilePath), n=1) != "") {
snippetText <- paste0("\n", snippetText)
}
# Append snippet block, print message
#
cat(paste0(snippetText, "\n"), file = rstudioSnippetsFilePath, append = TRUE)
cat(paste0("* Added '", s, "' to '", rstudioSnippetsFilePath, "'\n"))
added <- TRUE
}
}
if (added) {
cat("Restart RStudio to use new snippets")
}
return(invisible(added))
}
For anyone who comes across this thread, and to add to Dario's great answer: from RStudio v1.3, the filepaths have changes. So in his function, the section for setting rstudioSnippetsPathBase would need to change into something like the following
if (rstudioapi::versionInfo()$version < "1.3") {
rstudioSnippetsPathBase <- file.path(path.expand('~'),".R", "snippets")
} else {
if (.Platform$OS.type == "windows") {
rstudioSnippetsPathBase <- file.path(Sys.getenv("APPDATA"), "RStudio", "snippets")
} else {
rstudioSnippetsPathBase <- file.path(path.expand('~'), ".config/rstudio", "snippets")
}
}

Developing shiny app as a package, How to document the function?

I am a beginner in shiny and I am trying to make the package of my shiny application and then I will put it on the docker for future purpose use.
For doing so, I made the launch_application function
#' #export
runExample <- function() {
appDir <- system.file("shiny-examples", "myapp", package = "mypackage")
if (appDir == "") {
stop("Could not find example directory. Try re-installing `mypackage`.",
call. = FALSE)
}
shiny::runApp(appDir, display.mode = "normal")
}
I am not able to document this function. :( Could anyone suggest a solution?

Pulsing marker plugin with the R leaflet package

I want to add a pulsing marker to the map I built with the R leaflet package
Here is the plugin I want to use. In order to do it, I wrote the following code from this from this github account
library(leaflet)
library(htmltools)
library(htmlwidgets)
# This tells htmlwidgets about our plugin name, version, and
# where to find the script. (There's also a stylesheet argument
# if the plugin comes with CSS files.)
esriPlugin <- htmlDependency("leaflet-icon-pulse",version = "1.0",
src = c(href = "https://raw.githubusercontent.com/mapshakers/leaflet-icon-pulse/master/src/"),
script = "L.Icon.Pulse.js",stylesheet ="L.Icon.Pulse.css")
# A function that takes a plugin htmlDependency object and adds
# it to the map. This ensures that however or whenever the map
# gets rendered, the plugin will be loaded into the browser.
registerPlugin <- function(map, plugin) {
map$dependencies <- c(map$dependencies, list(plugin))
map
}
leaflet() %>% setView(-52.520, 13.185, zoom = 5) %>%
# Register ESRI plugin on this map instance
registerPlugin(esriPlugin) %>%
# Add your custom JS logic here. The `this` keyword
# refers to the Leaflet (JS) map object.
onRender("function(el,x) {
var pulsingIcon = L.icon.pulse({iconSize:[20,20],color:'red'});
var marker = L.marker([52.9167,13.9333],{icon: pulsingIcon}).addTo(this);
}")
However, it does not work. I got a grey rectangle instead of a beautiful map with a beautiful pulsing marker. Anyone see something wrong in my code?
This code works with three remarks:
the js and css file are stored locally
the icon is displayed correctly in RStudio viewer but it does not pulsate
With the "Show in new window" option in the Viewer everything works fine (tested in Firefox 48.0 and Chrome 53.0.2785.116 (64-bit))
This is the code (adjust the src parameter to match your file location):
library(leaflet)
library(htmltools)
library(htmlwidgets)
# This tells htmlwidgets about our plugin name, version, and
# where to find the script. (There's also a stylesheet argument
# if the plugin comes with CSS files.)
esriPlugin <- htmlDependency("leaflet-icon-pulse",version = "1.0",
src = "/home/valter/Desktop/test",
script = "L.Icon.Pulse.js",stylesheet ="L.Icon.Pulse.css")
# A function that takes a plugin htmlDependency object and adds
# it to the map. This ensures that however or whenever the map
# gets rendered, the plugin will be loaded into the browser.
registerPlugin <- function(map, plugin) {
map$dependencies <- c(map$dependencies, list(plugin))
map
}
leaflet() %>% addTiles() %>% setView(-52.520, 13.185, zoom = 5) %>%
# Register ESRI plugin on this map instance
registerPlugin(esriPlugin) %>%
# Add your custom JS logic here. The `this` keyword
# refers to the Leaflet (JS) map object.
onRender("function(el,x) { var pulsingIcon = L.icon.pulse({iconSize:[20,20],color:'red'});
var marker = L.marker([13.185,-52.520],{icon: pulsingIcon}).addTo(this); }")

knitr HTML output too large

I have been using rmarkdown/knitr's knit to html capability to generate html code for some blogs. I've found it extremely helpful and convenient, but have been running into some problems lately with file size.
When I knit a script that has graphics that use shapefiles or ggmap images, the html file gets too big for the blog host to make sense of it (I've tried with both blogger and wordpress). I believe this has to do with the relatively large data.frames/files that are the shapefiles/ggmap being put into html form. Is there anything I can do to get a smaller html file that can be parsed by a blog host?
For reference, the html output from an rmarkdown script with one graphic using a ggmap layer, a layer of shapefiles and some data is 1.90MB, which is too big for blogger or wordpress to handle in html input. Thanks for any ideas.
Below are 3 different options to help you reduce the file size of HTML files with encoded images.
1. Optimize an existing HTML file
You can run this Python script on an existing HTML file. The script will:
decode the base64 encoded images
run pngquant to optimize the images
re-encode the optimized images as base64
Usage:
python optimize_html.py infile.html
It writes output to infile-optimized.html.
2. Use the built-in knitr hook for optimizing PNG images
knitr 1.15 includes a hook called hook_optipng that will run the optipng program on generated PNG files to reduce file size.
Here is a .Rmd example (taken from: knitr-examples/035-optipng.Rmd):
# 035-optipng.Rmd
This demo shows you how to optimize PNG images with `optipng`.
```{r setup}
library(knitr)
knit_hooks$set(optipng = hook_optipng)
```
Now we set the chunk option `optipng` to a non-`NULL` value,
e.g. `optipng=''`, to activate the hook. This string is passed to
`optipng`, so you can use `optipng='-o7'` to optimize more heavily.
```{r use-optipng, optipng=''}
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
```
3. Write your own knitr hook for any image optimizer
Writing your own hook is also quite easy, so I wrote a hook that calls the pngquant program. I find that pngquant runs faster, and the output files are smaller and look better.
Here is a .R example that defines and uses hook_pngquant (taken from this gist).
#' ---
#' title: "pngquant demo"
#' author: "Kamil Slowikowski"
#' date: "`r Sys.Date()`"
#' output:
#' html_document:
#' self_contained: true
#' ---
#+ setup, include=FALSE
library(knitr)
# Functions taken from knitr/R/utils.R
all_figs = function(options, ext = options$fig.ext, num = options$fig.num) {
fig_path(ext, options, number = seq_len(num))
}
in_dir = function(dir, expr) {
if (!is.null(dir)) {
owd = setwd(dir); on.exit(setwd(owd))
}
wd1 = getwd()
res = expr
wd2 = getwd()
if (wd1 != wd2) warning(
'You changed the working directory to ', wd2, ' (probably via setwd()). ',
'It will be restored to ', wd1, '. See the Note section in ?knitr::knit'
)
res
}
is_windows = function() .Platform$OS.type == 'windows'
in_base_dir = function(expr) {
d = opts_knit$get('base.dir')
if (is.character(d) && !file_test('-d', d)) dir.create(d, recursive = TRUE)
in_dir(d, expr)
}
# Here is the code you can modify to use any image optimizer.
hook_pngquant <- function(before, options, envir) {
if (before)
return()
ext = tolower(options$fig.ext)
if (ext != "png") {
warning("this hook only works with PNG")
return()
}
if (!nzchar(Sys.which("pngquant"))) {
warning("cannot find pngquant; please install and put it in PATH")
return()
}
paths = all_figs(options, ext)
in_base_dir(lapply(paths, function(x) {
message("optimizing ", x)
cmd = paste(
"pngquant",
if (is.character(options$pngquant)) options$pngquant,
shQuote(x)
)
message(cmd)
(if (is_windows())
shell
else system)(cmd)
x_opt = sub("\\.png$", "-fs8.png", x)
file.rename(x_opt, x)
}))
return()
}
# Enable this hook in this R script.
knit_hooks$set(
pngquant = hook_pngquant
)
#' Here we set the chunk option `pngquant='--speed=1 --quality=0-50'`,
#' which activates the hook.
#+ use-pngquant, pngquant='--speed=1 --quality=0-50'
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
I prefer to write my reports in R scripts (.R) instead of R markdown documents (.Rmd). See http://yihui.name/knitr/demo/stitch/ for more information on how to do that.
One thing you could do would be to not use embedded image and other resources. To achieve this, you can set the self_contained option in the YAML header for your document to false, e.g.:
---
output:
html_document:
self_contained: false
---
More info here: http://rmarkdown.rstudio.com/html_document_format.html

Resources