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. 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 an 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 practical purposes, this should always be FALSE
knitr::opts_chunk$set(widgetframe_self_contained = FALSE) # default = FALSE
# For all practical 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. Whether the final HTML document has
inlined dependencies depends on the self_contained
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 an R list which is 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
and your Makefile
and inside index.Rmd
you should have something like