robservable
allows
the use of Observable notebooks
(or part of them) as htmlwidgets in R.
Note that it is not just an iframe
embedding a whole
notebook. Cells are integrated directly into the document or application
with no iframe
wrapper. You can choose what cells to
display, update cell values from R, and add observers to cells to get
their values back into a Shiny application.
Warning: in Quarto documents, you must either use
robservable
orojs
cells. Using both in the same document will create conflicts and make at least one of both fail.
The most basic usage is to call the robservable()
function with the full URL or the identifier of the notebook you want to
display. The identifier is the same as the one used to import another
notebook in Observable (something like
@d3/horizontal-bar-chart
). Please see the Introduction
to Imports notebook for additional reference.
For example, the two following commands are equivalent and should display the whole corresponding notebook1 :
If the notebook is shared but not published, you can use the full URL or the hash identifier.
Instead of rendering a whole notebook, we can choose to display only
some of its cells. This is done by passing a character vector of cell
names to the include
argument.
If you need to display an unnamed cell2, you can do it by
specifying its number, ie its position in the notebook
(starting with 1). For example, to display the first cell of a notebook
if it is unnamed you would use include = 1
.
Note that specifying a named cell by its position will not
display it. You have to add its name to include
.
For some notebooks you’ll have to render several cells to get the
desired result. For example, in the eyes notebook, the
main chart is in a cell named canvas
, but it doesn’t render
anything if mouse
value is not present. For the chart to be
created, you have to render both cells.
In this case, we may want to render mouse
without
displaying it. This is possible by adding its name to the
hide
argument.
Finally, it is possible to mix the use of named and unnamed cells
both in cell
and hide
, so you can do something
like below.
robservable
allows to update a notebook cell value
directly from R. This is done by passing a named list as the
input
argument.
For example, in the horizontal bar chart notebook there is a cell
called height
which allows to customize the chart height.
We can modify its value when calling robservable
with
input = list(height = 300)
.
More interesting, we can update the data
cell value of
the notebook to generate the bar chart based on our own data. We just
have to be sure that it is in the same format as the notebook data. In
this example the data is in a standard d3-array
format, so
we can pass a data frame. We will need to take care of the column names,
and also pass the x
and y
input arguments to
specify which variables we want on each axis.
library(palmerpenguins)
df <- data.frame(table(penguins$species))
# change column names to match the names used in the observable notebook
names(df) <- c("Species", "Freq")
robservable(
"@juba/robservable-bar-chart",
include = "chart",
input = list(
data = df,
x = "Freq",
y = "Species"
)
)
There’s still one problem though. Our species names are truncated. We
can fix this because the notebook allows us to change the margins of the
plot by modifiying the margin
cell. As this cell value is a
JavaScript object, we can update it by passing a named
list
.
robservable(
"@juba/robservable-bar-chart",
include = "chart",
input = list(
data = df,
x = "Freq",
y = "Species",
# equivalent to {top: 20, right: 0, left: 70, bottom: 30} in JavaScript
margin = list(top = 20, right = 0, left = 70, bottom = 30)
)
)
Finally, here is a bit more complex example which displays a multi-line
chart with the gapminder
data. The
to_js_date
function is a helper to convert
Date
or POSIXt
R objects to JavaScript
Date
values (ie number of milliseconds since Unix
Epoch).
library(gapminder)
data(gapminder)
series <- lapply(unique(gapminder$country), function(country) {
values <- gapminder[gapminder$country == country, "lifeExp", drop = TRUE]
list(name = country, values = values)
})
dates <- sort(unique(gapminder$year))
dates <- as.Date(as.character(dates), format = "%Y")
df <- list(
y = "Life expectancy",
series = series,
dates = to_js_date(dates)
)
robservable(
"@juba/multi-line-chart",
include = "chart",
input = list(data = df)
)
Widget sizing is a bit complicated as it depends on several factors :
height
cell value, if it exists.width
cell value, if it exists. If
not, width
by default is defined by the Observable standard
library as the page width.By default, robservable
overrides the potential
width
and height
notebook values by the
htmlwidget
container HTML
element width and
height. This override is performed both at widget creation and on widget
resizing. Overriding width
allows making the widget “fit”
in its container, and avoids updating size when the page is resized but
not its container (which is the case when width
is taken
from Observable standard library). Overriding height
has
the same purpose, but not all notebooks define a height
value, so unlike width
, height
won’t always
have an effect.
This value override allows the following figure to fit in the widget dimensions.
If you explicitly specify the width
and
height
of the widget with the corresponding arguments, the
cell values will be updated accordingly.
robservable(
"@mbostock/eyes",
include = c("canvas", "mouse"),
hide = "mouse",
width = 500,
height = 100
)
If the notebook doesn’t provide a height
value, then
you’ll have to manually define an height suitable for the output.
Finally, if you provide both a widget height
and an
height
value with the input
argument, the
second one is not overriden.
robservable(
"@mbostock/eyes",
include = c("canvas", "mouse"),
hide = "mouse",
input = list(height = 50),
height = 200
)
There are some cases when these width
and
height
overrides are not suitable. First, those values
could be defined for something else than an output size
(height
could be another type of parameter). Second,
overriding the height can modify the chart appearance. In these cases,
you can set the update_width
or update_height
arguments to FALSE
to deactivate the value override.
If you are developing Observable notebooks to be used with robservable, here is some advice to make your notebook easier to use in R:
<style>
cells, this will
greatly ease their inclusion.In Observable, a cell can be
in three states : pending
, fulfilled
or
rejected
. For each included named cell,
robservable
emits custom JavaScript events which allow to
track the cell state.
For example, suppose you include a chart
cell from a
notebook. You can then listen to the following document
custom events :
robservable-chart-pending
: when the chart
cell is in pending staterobservable-chart-fulfilled
: when the
chart
cell is in fulfilled staterobservable-chart-rejected
: when the
chart
cell is in rejected stateHere is a sample use case : suppose you are including a notebook cell which generates a chart inside an Rmarkdown document or Shiny app, and you want to add a custom interaction when hovering certain charts elements. For this to work, you have to wait for the cell to be rendered, so that you can add event listeners. This is possible with something like the following code :
The examples above show how to update a cell with a “fixed” value : a string, a number, a data frame… But sometimes you need to redefine a cell as JavaScript code, for example to redefine a function.
Suppose in our Observable notebook we have the following cell :
If we want to redefine this function using the JavaScript Observable runtime API, following the variable.define documentation we would use something like :
To do this in robservable
, use the input_js
argument. This is a named list whose elements are also lists with
inputs
and definition
entries. In this case
the syntax would be :
robservable(...,
input_js = list(
func = list(inputs = NULL, definition = "() => (x) => (x + 10)")
)
)
In a slightly more complex case, maybe our initial function is the
following, when param
is not an argument of our function
but the value of another notebook cell :
If we want to redefine this function while still accessing the
param
cell value, we have to add its name to the
inputs
entry of input_js
:
robservable(...,
input_js = list(
func = list(inputs = "param", definition = "(param) => (x) => (x - param)")
)
)
For more details on the usage of inputs
and
definition
, see the Observable
runtime API documentation.
By default robservable
uses htmlwidgets
for
JSON serialization of the widgets arguments, with an additional
TOJSON_ARGS
value of list(dataframe = "rows")
so that R data frames are converted to a format suitable for
d3
.
You can change the value of TOJSON_ARGS
with the
json_args
argument of robservable
, or you can
pass a custom JSON serializing R function with the
json_func
argument. This function will be passed to
htmlwidgets
via its internal TOJSON_FUNC
parameter. See the custom
JSON serializer documentation for more details.