knitr
The knitr
package, along with the rmarkdown
package converts a R markdown (Rmd) document into a target document, which can be an HTML Document, a HTML Website, a Dashboard, or a PDF/Word Document etc.
widgetframe
is designed to be used in Rmd documents which will eventually be knitted to an HTML or a derived format. There is little or no benefit in using widgetframe::frameWidget()
when the output format is not HTML based such as PDF, Microsoft Word etc. In fact widgetframe
will most definitely not work for such output formats.
htmlwidgets
and knitr
By default when knitting htmlwidgets
the htmlwidgets:::knit_print.htmlwidgets()
function (an internal function exposed as an S3 method) is called. The output of this call is a list
containing the HTML code + a list of HTML dependencies (JS/CSS) required to render the widget. How this is then inserted into the final document depends on the output format. For this discussion we are going to limit to HTML based output formats because as noted above widgetframe
is designed to work only for HTML based output formats.
Let’s start with the simplest of HTML output format rmarkdown::html_document()
. The self_contained
(default = TRUE
), and the lib_dir
(default = name of document + _files
) arguments of this output format dictate how htmlwidgets
instances appear in the final document. If self_contained
is TRUE
then the HTML dependencies (JS/CSS) are all inlined and the result is just one single HTML document. Naturally this document is quite large in size due to the dependencies being inlined. If self_contained
is FALSE
then the HTML dependencies are kept in a separate directory determined by the lib_dir
argument. Other HTML based formats like bookdown
, flexdashboard
etc. too have similar arguments. This is not surprising as they tend to extend rmarkdown::html_document
.
Unless you are working on small one-off Rmd document, it is never a good idea to have self_contained
set to its default value TRUE
. It results in large HTML documents which are slow to load in the browser. Secondly by inlining dependencies you lose the ability to share common dependencies across different HTMLs. Lastly you also limit the browser’s ability to cache HTML dependencies across sessions.
Even if you were to externalize the dependencies by setting sefl_contained=FALSE
, there are still two potential issues…
htmlwidgets
may depend on different versions of the same Javascript/CSS dependency. But when the final document is produced only a single version of the said dependency is used. This may cause certain widgets to not display, or work incorrectly.These problems typically don’t occur when knitting one-off HTML documents using the rmarkdown::html_document
format. But they do occur once you start outputting documents in the bookdown
, blogdown
, rmarkdown
websites, xaringan
and other HTML based output formats. And this is where widgetframe
comes in.
widgetframe
widgetframe
is itself a htmlwidgets
instance, but of a different kind. Instead of wrapping a Javascript based dataviz object, it wraps another htmlwidgets
instance like leaflet
, or DT
, or dygraphs
etc. It does so by embedding the target htmlwidgets
instance in a HTML iframe. It also uses NPR’s pym.js Javascript library to make the iframes responsive. This allows you to easily embed htmlwidgets
inside complex HTML documents and avoid the issues mentioned in the section above.
widgetframe
and knitr
To understand how the knitting of widgetframe
s differ from regular htmlwidgets
, let’s examine the following code. It shows you how to use widgetframe::fameWidget()
function in a RMarkdown document and the three knitr chunk options it supports.
```{r, include=FALSE}
# widgetframe supports 3 custom knitr chunk options...
# For all practicle purposes this should always be FALSE
knitr::opts_chunk$set(widgetframe_self_contained = FALSE) # default = FALSE
# For all practicle purposes this should always be TRUE
knitr::opts_chunk$set(widgetframe_isolate_widgets = TRUE) # default = TRUE
# Only needed in bookdown format/s such as bookdown::gitbook. Otherwise don't set.
# knitr::opts_chunk$set(widgetframe_widgets_dir = 'widgets' )
```
```{r leaflet-01}
library(leaflet)
library(widgetframe)
l <- leaflet() %>% addTiles() %>% setView(0,0,1)
frameWidget(l, width='90%')
```
While using wigdetframe
is as simple as widgetframe::frameWidget(some_other_widget)
, how the widget is knitted depends on three custom knitr chunk options explained below. When knitr
comes across a widgetframe::frameWidget()
call in a R Markdown document, it calls widgetframe:::knit_print.widgetframe()
function (internal function exposed as a S3 method) to render the widget. This function is a wrapper around the htmlwidgets:::knit_print.htmlwidgets()
function of the taget htmlwidgets
instance. It is important to know that both knitr_print.widgetframe()
and knitr_print.htmlwidgets()
functions have no way of knowing what the target output format is and what arguments were passed to the target output format. So in order to knit the target widget in a child HTML document, which is displayed inside an iframe of the parent HTML document, widgetframe
needs to depend of the three chunk options shown in the code sample above. We now discuss these 3 options and what their values should be for different output formats.
This option is similar to the self_contained
argument of the rmarkdown::html_document()
output format. The reason we need a separate chucnk option is because …
As explained above knitr_print.widgetframe()
has no way of knowing what the output format is and what arguments were passed to it.
Unlike self_contained
which defaults to TRUE
, widgetframe_self_contained
defaults to FALSE
. That is to say that by default when using widgetframe
the dependencies of the target htmlwidgets
instance are kept in a separate directory.
There is very little reason to set this option to TRUE
. If however you do set it to TRUE
the HTML file for the wrapped widget will have its dependencies inlined, resulting in a single large HTML file. Note that this is not the same as your final output HTML. This is just the HTML for the widget which is displayed inside an iframe in the final HTML document. Wheter the final HTML document has inlined dependencies depends on the self_contaied
argument of your output format e.g. rmarkdown::html_document()
.
Defaults to TRUE
, and when TRUE
, isolates HTML dependencies (CSS/JS) of htmlwidgets
of different types. This avoids compatibility issues when htmlwidgets
of different types depend on the same dependency(ies) but on different versions. For example if you are using some leaflet
, and some DT
htmlwdigets
in your document (doesn’t matter how many), then the dependencies of leaflet
widgets will be stored separately from dependencies of DT
widgets and any other widget other than leaflet
. So leaflet
can depend on version ‘X’ of jQuery
and DT
can depend on version ‘Y’ and both will work correctly. Note that multiple widgets of the same type will share the same set of dependencies in order to avoid unnecessary duplication. There is very little reason to set this option to FALSE
.
This option, if provided, determines where the HTML code for the wrapped htmlwidgets
instance is written. Before discussing more we should note that knitr_print.htmlwidgets()
never actually creates any files/directories or touches the file-system in any way. It merely returns a R list which then used by the output format to embed the widget’s HTML (and dependencies) in the final document. However, knitr_print.widgetframe()
does create HTML file/s and dependencies directories for the wrapped widget. It needs to, so that the final document can embed the widget’s HTML which is saved separately, inside an iframe. This may not be important to know from a user perspective but a must know, if you want to extend or contribute to widgetframe
.
For almost all output formats except bookdown
, this option should not be set and it will default to file.path(knitr::opts_chunk$get('fig.path'),'widgets')
. That is a widgets
directory inside the directory knitr::opts_chunk$get('fig.path')
. This is done this way because as noted knitr_print
has no way of knowing the arguments of the output format including the final destination directory of the document. However it does have access to the fig.path
knitr chunk option. Refer to this Github issue for a discussion around this. See the bookdown
section below for a use case where this value needs to be explicitly set.
Another scenario where you may need to set this option is when your RMarkdown output format has self_contained=TRUE
(Note: This is the self_contained
argument of your HTML output format e.g. rmarkdown::html_document
and not the widgetframe_self_contained
chunk argument described above).
To understand how each of the three knitr chunk options affect the wrapped widget’s HTML document, see the code and the table below.
```{r leaflet-01}
library(leaflet)
library(widgetframe)
l <- leaflet() %>% addTiles() %>% setView(0,0,1)
frameWidget(l, width='90%')
```
```{r dygraph-01}
library(dygraphs)
ts <- dygraph(nhtemp, main = "New Haven Temperatures")
frameWidget(ts, height = 350, width = '95%')
```
I’ve ommitted the widgetframe_
prefix for each of the 3 options below to conserve space.
self_contained |
isolate_widgets |
widgets_dir |
Output |
---|---|---|---|
FALSE † |
TRUE † |
Not Set † | Inside widgets directory, widget_leaflet-01.html and widget_dygraph-01.html files and leaflet_libs directory for leaflet and dygraph_libs directory for dygraph dependencies. |
FALSE † |
FALSE |
Not Set † | Inside widgets directory, widget_leaflet-01.html and widget_dygraph-01.html files and a single libs directory for both leaflet and dygraph dependencies. |
TRUE |
DOESN’T MATTER | Not Set † | Inside widgets directory, two huge widget_leaflet-01.html and widget_dygraph-01.html files with respective dependencies inlined. |
†: Default Value.
By default the widget HTML + dependencies (JS/CSS files) are located inside widgets
directory, which is created inside wherever knitr::opts_chunk$get('fig.path')
points too. However if widgetframe_widgets_dir
is set then the widget HTML + dependencies are placed inside a directory whose name is the value of this option, and the path is resolved relative to the current working directory (getwd()
) while knitting.
widgetframe
and bookdown
For the bookdown output format, we need some additional steps to correctly use widgetframe
s. This section describes those steps.
The default output directory of the bookdown
book is _book
, which can be configured via _bookdown.yml
file.
For the bookdown::gitbook
format we need to explicitly set the widgetframe_widgets_dir
to some value (e.g. ‘widgets’), so that the embedded widgets HTML code is saved in this directory instead of the default.
After 'bookdown::render_book("index.Rmd")
has been called, we need to move the widgets directory inside the final output directory.
You can easily use a Makefile for this. e.g.
Your _bookdown.yml
file
output_dir: "book"
and your Makefile
book: index.Rmd
Rscript -e 'bookdown::render_book("index.Rmd")'
mv widgets book/.
and inside index.Rmd
you should have something like
```{r, include=FALSE}
knitr::opts_chunk$set(widgetframe_widgets_dir = 'widgets' )
```