10 Saving and embedding HTML

Any widget made from any htmlwidgets package (e.g., plotly, leaflet, DT, etc) can be saved as a standalone HTML file via the htmlwidgets::saveWidget() function. By default, it produces a completely self-contained HTML file, meaning that all the necessary JavaScript and CSS dependency files are bundled inside the HTML file. This makes it very easy to share a widget as a single HTML file. In this case, consider using the partial_bundle() function to reduce the size of the bundled files. By default, it automatically determines a reduced version of plotly.js that is sufficient for rendering your graphic. This can lead to a substantial reduction in the overall file size, especially if you’re using basic chart types:

p <- plot_ly(x = 1:10, y = 1:10) %>% add_markers()
widget_file_size <- function(p) {
  d <- tempdir()
  withr::with_dir(d, htmlwidgets::saveWidget(p, "index.html"))
  f <- file.path(d, "index.html")
  mb <- round(file.info(f)$size / 1e6, 3)
  message("File is: ", mb," MB")
}
widget_file_size(p)
#> File is: 3.243 MB
widget_file_size(partial_bundle(p))
#> File is: 1.044 MB

If you want to embed numerous widgets in a larger HTML document (e.g., via HTML <iframe>s), self-contained HTML is not recommended. That’s because, if you embed numerous self-contained widgets inside a larger document, your browser has to repeatedly parse the same dependencies over and over. Instead, if you save all the dependency files externally into a single directory, the browser will only have to parse those dependencies once, which can dramatically improve responsiveness. You can do this by setting selfcontained = FALSE and specifying a fixed libdir in saveWidget(). It’s also worth noting that using htmlwidgets::saveWidget() with selfcontained = FALSE is essentially the same as using htmltools::save_html() which saves arbitrary HTML content to a file. The htmltools::save_html() function is useful for saving numerous htmlwidgets (e.g., Figure 13.12 or 13.13) and/or other custom HTML markup (e.g., Figure 22.1) in a single HTML page.

library(htmlwidgets)
p <- plot_ly(x = rnorm(100))
saveWidget(p, "p1.html", selfcontained = F, libdir = "lib")
saveWidget(p, "p2.html", selfcontained = F, libdir = "lib")

In this case, if you wanted to share "p1.html" and/or "p2.html" with someone else, make sure to include the libdir folder, perhaps via a zip file:

zip("p1.zip", c("p1.html", "lib"))
zip("p2.zip", c("p2.html", "lib"))

Embedding these HTML files via an HTML <iframe> is convenient not only for re-using a widget in various parent documents, but also for preventing any JavaScript and CSS in the parent document from negatively impacting how the widget renders. Rather than writing the HTML <iframe> tag directly, I recommend using htmltools::tags$iframe() – this will allow you to leverage bookdown’s figure captioning, numbering, and automatic snapshots for non-HTML output:

```{r}
htmltools::tags$iframe(
  src = "p1.html", 
  scrolling = "no", 
  seamless = "seamless",
  frameBorder = "0"
)
```

A great tool that helps automate this sort of workflow with responsive iframes is the widgetframe package (Karambelkar 2017). See the ‘widgetframe and knitr’ vignette for documentation of options for controling where, how, and if external dependencies are stored on the file system when using it inside a knitr/rmarkdown document.

browseVignettes("widgetframe")

References

Karambelkar, Bhaskar. 2017. Widgetframe: ’Htmlwidgets’ in Responsive ’Iframes’. https://CRAN.R-project.org/package=widgetframe.