22 Leveraging web technologies from R
22.1 Web infrastructure
Sometimes supplying customdata
isn’t the best way to achieve a particular interactive feature. In those cases, you likely want to leverage other R lower-level interfaces to web technologies. Recall from Section 13.2 that htmlwidgets objects are a special case of htmltools tags. That means, you can always complement your widget(s) with arbitrary HTML content by adding additional tags. Figure 22.1 leverages this idea to place an empty HTML <div>
container below the correlation heatmap which is then populated with a plotly scatterplot upon clicking a cell. As it turns out, you could implement Figure 22.1 by binding x/y data to each heatmap cell via customdata
, but that would require the browser to store twice the amount of data as what’s required here. Instead, this approach serializes the input data (mtcars
) into a JSON file via jsonlite so the webpage can read and parse the full dataset once and select just the two required columns when required (on click). There are a lot of ways to read JSON in JavaScript, but here we use the d3.js library’s d3.json()
since plotly already comes bundled with the library (Heer 2011). Also, since the HTML file is reading the JSON from disk, most browsers won’t render the HTML file directly (at least, by default, for security reasons). To get around that, we can start up a simple web server from R using servr to serve both the HTML and JSON in way that your browser will deem safe to run (Xie 2016).
Click to see code
library(plotly)
library(htmltools)
nms <- names(mtcars)
p <- plot_ly(colors = "RdBu") %>%
add_heatmap(
x = nms,
y = nms,
z = ~round(cor(mtcars), 3)
) %>%
onRender("
function(el) {
Plotly.d3.json('mtcars.json', function(mtcars) {
el.on('plotly_click', function(d) {
var x = d.points[0].x;
var y = d.points[0].y;
var trace = {
x: mtcars[x],
y: mtcars[y],
mode: 'markers'
};
Plotly.newPlot('filtered-plot', [trace]);
});
});
}
")
# In a temporary directory, save the mtcars dataset as json and
# the html to an index.html file, then open via a web server
withr::with_path(tempdir(), {
jsonlite::write_json(as.list(mtcars), "mtcars.json")
html <- tagList(p, tags$div(id = 'filtered-plot'))
save_html(html, "index.html")
if (interactive()) servr::httd()
})
22.2 Modern JS & React
All the JavaScript (JS) we’ve seen thus far is natively supported by modern web browsers, but for larger projects, you may want to leverage modern versions of JS (i.e., ES6, ES7, etc) and modern JS development tools (e.g., Babel, Webpack, etc) for compiling modern JS to a version that all browsers can support (i.e., ES2015). The current landscape of JS development tooling is large, complex, fragmented, difficult for non-experts to navigate, and mostly beyond the scope of this book. However, thanks to R packages like V8, reactR, and runpkg, it turns out we can effectively leverage React40 components41 from R without fussing with system commands or setting up a complicated JS build toolchain.
The R package runpkg makes it easy to download any npm (the main repository network for JS) package (via https://unpkg.com/) and include it in a web page generated through the htmltools package (Sievert 2019b). It does so by returning a htmltools::htmlDependency()
object which encapsulates the downloaded files and includes the JS scripts (or CSS stylesheets) into any page that depends on that object. Here we use it to download a standalone bundle of a React library for rendering all sort of different video formats, called react-player
.
This react-player
library provides a function called renderReactPlayer()
that requires a placeholder (i.e., a DOM element) for inserting the video as well as a url (or file path) to the video. Figure 22.2 demonstrates how we could use it to render a YouTube video in response to a plotly click event:
Click to see code
library(htmltools)
# the video placeholder
video <- tags$div(id = "video")
# upon clicking the marker, populate a video
# in the DOM element with an id of 'video'
p <- plot_ly(x = 1, y = 1, size = I(50)) %>%
add_text(
text = emo::ji("rofl"),
customdata = "https://www.youtube.com/watch?v=oHg5SJYRHA0",
hovertext = "Click me!",
hoverinfo = "text"
) %>%
onRender(
"function(el) {
var container = document.getElementById('video');
el.on('plotly_click', function(d) {
var url = d.points[0].customdata;
renderReactPlayer(container, {url: url, playing: true});
})
}"
)
# create the HTML page
browsable(tagList(p, video, react_player))
This react-player
React library is rather unique in that it provides a standalone function, renderReactPlayer()
, that enables rendering of a React component without loading React itself or leveraging special React syntax like JSX. It’s more likely that the React component library will explicitly require you to import both React and ReactDOM. You could use runpkg to download these React/ReactDOM as well, but the html_dependency_react()
function from reactR package makes this even easier (Inc, Russell, and Dipert 2019). Furthermore, reactR provides a babel_transform()
function which will compile modern JS (e.g., ES6, ES2017, etc) as well as special React markup (e.g., JSX) to a version of JS that all browsers support (e.g., ES5). For a toy example, Figure 22.3 demonstrates how one could leverage ES6, React, and React’s JSX syntax to populate a <h1>
title filled with a customdata
message in response to a plotly click event.
Click to see code
library(reactR)
# a placeholder for our react 'app'
app <- tags$div(id = "app")
p <- plot_ly(x = 1, y = 1) %>%
add_markers(customdata = "Powered by React") %>%
onRender(babel_transform(
"el => {
el.on('plotly_click', d => {
let msg = d.points[0].customdata;
ReactDOM.render(
<h1>{msg}</h1>,
document.getElementById('app')
)
})
}"
))
# create the HTML page
browsable(tagList(p, app, html_dependency_react()))
For a more serious example, we could leverage another React component, named react-data-grid
, to display the data within a plotly scatterplot brush, as done in Figure 22.4. Again, we can use runpkg to download a bundle of react-data-grid
, but this library doesn’t come with React
/ReactDOM
, so we must explicitly include it this time around. In fact, this approach of explicitly importing and calling ReactDOM.render()
on your component is a more common approach than the custom standalone interface approach (i.e., renderReactPlayer()
) used in Figure 22.2.
Click to see code
data_grid_js <- runpkg::download_files(
"react-data-grid",
"dist/react-data-grid.min.js"
)
# the data table placeholder
data_grid <- tags$div(id = "data-grid")
# upon clicking the marker, populate a video
# in the DOM element with an id of 'video'
p <- plot_ly(mtcars, x = ~wt, y = ~mpg) %>%
add_markers(customdata = row.names(mtcars)) %>%
layout(dragmode = "select") %>%
onRender(babel_transform(
"el => {
var container = document.getElementById('data-grid');
var columns = [
{key: 'x', name: 'Weight'},
{key: 'y', name: 'MPG'},
{key: 'customdata', name: 'Model'}
];
el.on('plotly_selecting', d => {
if (d.points) {
var grid = <ReactDataGrid
columns={columns}
rowGetter={i => d.points[i]}
rowsCount={d.points.length}
/>;
ReactDOM.render(grid, container);
}
});
el.on('plotly_deselect', d => {
ReactDOM.render(null, container);
});
}"
))
# create the HTML page
browsable(
tagList(p, data_grid, html_dependency_react(), data_grid_js)
)
References
Heer, Michael Bostock AND Vadim Ogievetsky AND Jeffrey. 2011. “D3: Data-Driven Documents.” IEEE Trans. Visualization & Comp. Graphics (Proc. InfoVis). http://vis.stanford.edu/papers/d3.
Inc, Facebook, Kent Russell, and Alan Dipert. 2019. ReactR: React Helpers. https://CRAN.R-project.org/package=reactR.
Sievert, Carson. 2019b. Runpkg: Tools for Working with ’Unpkg’. https://github.com/cpsievert/runpkg.
Xie, Yihui. 2016. Servr: A Simple Http Server to Serve Static Files or Dynamic Documents. https://CRAN.R-project.org/package=servr.
React is a modern JavaScript library, backed by Facebook, for building and distributing components of a website – https://reactjs.org/↩︎
There are thousands of React components available. To get a sense what’s available, see this list https://github.com/brillout/awesome-react-components. ↩︎