---
title: "Introduction to robservable"
author: "Julien Barnier, Kenton Russell"
date: "`r Sys.Date()`"
output:
  rmarkdown::html_vignette:
    fig_width: 5
    toc: true
vignette: >
  %\VignetteIndexEntry{Introduction to robservable}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  chunk_output_type: console
---



```{r, include=FALSE}
library(robservable)
library(htmlwidgets)
knitr::opts_chunk$set(
  screenshot.force = FALSE,
  echo = TRUE
)
```

`robservable` allows the use of [Observable](https://observablehq.com/) notebooks (or part of them) as [htmlwidgets](http://www.htmlwidgets.org/) 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.

## Embedding a whole notebook

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](https://observablehq.com/@observablehq/introduction-to-imports) notebook for additional reference.

For example, the two following commands are equivalent and should display the whole corresponding notebook^[The result is not displayed here as a whole notebook is not very suitable for embedding in a document] :

```{r eval = FALSE}
robservable("https://observablehq.com/@d3/horizontal-bar-chart")
```

```{r eval = FALSE}
robservable("@d3/horizontal-bar-chart")
```

If the notebook is shared but not published, you can use the full URL or the hash identifier.

```{r eval = FALSE}
robservable("https://observablehq.com/d/61be71d2b8c0d456")
```

```{r eval = FALSE}
robservable("61be71d2b8c0d456")
```


## Choosing which cells to render

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.


```{r}
robservable(
  "@juba/robservable-bar-chart", 
  include = "chart"
)
```


If you need to display an unnamed cell^[Another option for unnamed cells is to fork the notebook in Observable and to add a name yourself.], 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`.

```{r, eval = FALSE}
robservable(
  "@juba/robservable-bar-chart", 
  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](https://observablehq.com/@mbostock/eyes), 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.


```{r}
robservable(
  "@mbostock/eyes",
  include = c("canvas", "mouse")
)
```


<p style = "height:30px;"></p>


In this case, we may want to render `mouse` without displaying it. This is possible by adding its name to the `hide` argument.


```{r}
robservable(
  "@mbostock/eyes",
  include = c("canvas", "mouse"),
  hide = "mouse"
)
```


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.

```{r, eval = FALSE}
robservable(
  "@mbostock/eyes",
  include = c(1, "canvas", "mouse"),
  hide = "mouse"
)
```




## Updating cell values

`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)`.


```{r}
robservable(
  "@juba/robservable-bar-chart", 
  include = "chart",
  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.


```{r}
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`.

```{r}
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](https://observablehq.com/@juba/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).

```{r message=FALSE}
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)
)
```

## Sizing the widget

Widget sizing is a bit complicated as it depends on several factors :

- the size of the HTML widget himself.
- the size of the outputs of the cells embedded in the widget.
- the value of the `height` cell value, if it exists.
- the value of the `width` cell value, if it exists. If not, `width` by default is defined by the [Observable standard library](https://github.com/observablehq/stdlib#width) 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.

```{r}
robservable(
  "@mbostock/eyes",
  include = c("canvas", "mouse"),
  hide = "mouse"
)
```


If you explicitly specify the `width` and `height` of the widget with the corresponding arguments, the cell values will be updated accordingly.

```{r}
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.

```{r}
robservable(
  "@juba/bivariate-choropleth",
  include = "chart",
  height = 450
)
```

Finally, if you provide both a widget `height` and an `height` value with the `input` argument, the second one is not overriden.

```{r}
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.


## Writing notebooks for robservable

If you are developing Observable notebooks to be used with robservable, here is some advice to make your notebook easier to use in R:

- you could see your notebook as a function. Most cells are input arguments, and others are output with results.
- to allow the output to be customized in R, move as many settings and values as possible in their own cell.
- name all the cells that could be included or whose value could be modified in R.
- give a name to the CSS `<style>` cells, this will greatly ease their inclusion.
- making your chart updatable with animated transitions can be a nice addition if the notebook could be used in a shiny app. See the [robservable in Shiny applications vignette](https://juba.github.io/robservable/articles/shiny.html#animated-transitions-1) for a bit more information on this.

## Advanced usage

### Inspector state events

In Observable, [a cell can be in three states](https://github.com/observablehq/runtime#observers) : `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 state
- `robservable-chart-fulfilled` : when the `chart` cell is in fulfilled state
- `robservable-chart-rejected` : when the `chart` cell is in rejected state

Here 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 :

```{js}
document.addEventListener("robservable-chart-fulfilled", (event) => {         
   d3.selectAll("#chart rect")
    .on("mouseover", (event) => { ... })
    .on("mouseout", (event) => { ... });
}, false)
```

### Updating a cell JavaScript 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 :

```js
func = x => (x + 2)
```

If we want to redefine this function using the JavaScript Observable runtime API, following the [variable.define documentation](https://github.com/observablehq/runtime#variable_define) we would use something like :

```js
module.redefine("func", [], () => x => (x + 10))
```

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 :

```{r, eval = FALSE}
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 :

```js
func = x => (x + param)
```

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` :

```{r, eval = FALSE}
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](https://github.com/observablehq/runtime#variable_define).



### Customizing JSON serialization

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](https://www.htmlwidgets.org/develop_advanced.html#custom-json-serializer) for more details.


