Synchronize your HTML Widgets with Crosstalk

Carl Howe

Crosstalk: the quick summary

Yihui Xie and I taught the R Markdown and Interactive Dashboards Workshop at rstudio::conf(2020). Based on some feedback we received from students there, I want to introduce you to the crosstalk package that we used to make HTML widgets interact in our R Markdown dashboards. Crosstalk is interesting to learn about because it:

  • Isn’t widely known. Several workshop attendees said that learning about this package was worth the entire cost of the workshop, despite the fact that it’s been around since 2016.
  • Adds simple interactivity without a lot of complexity. We frequently use HTML widgets to make R Markdown dashboards interactive. The crosstalk package allows those HTML widgets to interact with one another without using Shiny. For reference, you can refer to the crosstalk online documentation
  • Works with the most commonly used HTML widgets. HTML widgets have to be explicity designed to use crosstalk. However, crosstalk works out of the box with commonly used widgets such as plotly, DT, leaflet. Even if you only use those three widgets, you can build some very impressive dashboards.

Crosstalk adds meaningful interactivity to R Markdown dashboards

I want to begin by defining who should use crosstalk and why. Crosstalk is designed for people who:

  • Build interactive dashboards with R Markdown and HTML widgets. R Markdown allows you to create a dashboard from simple text annotations and code chunks while HTML Widgets add interactivity to what would normally be static R Markdown documents.
  • Want to coordinate the responses of multiple HTML widgets. Crosstalk allows multiple HTML Widgets to synchronize their views of a shared dataframe and to filter what’s shown.

To get a sense of how this works, let’s look at the simple example code we presented in the workshop. For the purposes of this blog post, I’m leaving out the R Markdown layout elements and simply showing the outputs.

library(crosstalk)
library(leaflet)
library(DT)
shared_df <- quakes[sample(nrow(quakes), 10),]
leaflet(shared_df) %>% addTiles() %>% addMarkers(~long, ~lat)
datatable(shared_df)

All we’ve done here is show 10 earthquakes near Fiji on a map and shown the same data in a data table. The two presentations of the data in the map and datatable HTML widgets are independent – searches done in the data table don’t affect the map. Similarly, while you can scale the map, the map elements themselves aren’t editable.

But what if we wanted to have the two widgets synchronize with one another? For example, could we have searches and row selections in the datatable drive which earthquakes are shown on the map?

The answer, of course, is yes. But the remarkable thing about crosstalk is how easy it is to create that synchronization: We only have to change one line at the beginning of the code. We simply change this line

shared_df <- quakes[sample(nrow(quakes), 10),]

to this:

shared_df <- SharedData$new(quakes[sample(nrow(quakes), 10),])

Now if we run that chunk of code with that simple change, the two HTML widgets interact as we want them to. Selected rows in the datatable highlight the appropriate location indicators in the map. Similarly, if you click the selection icon below the + and - zoom controls on the map and draw a box around only a few earthquakes (that’s referred to as a draggable selection), only the rows for those earthquakes show up in the datatable below.

library(crosstalk)
library(leaflet)
library(DT)
## The line below is what we changed.
shared_df <- SharedData$new(quakes[sample(nrow(quakes), 10),])
leaflet(shared_df) %>% addTiles() %>% addMarkers(~long, ~lat)
datatable(shared_df)

Crosstalk works by having widgets share a dynamic dataframe

This interactivity and synchronization is made possible by the shared_df object we created. Shared_df is an R6 object. If you’re not familiar with R6 objects, it doesn’t matter – all you need to know is that when we called SharedData$new, it created a new object called shared_df that walks, acts, and quacks like an ordinary data frame, but can dynamically change based on interactions with HTML widgets.

Only some HTML widgets work with crosstalk. You can read the full list of HTML widgets that work with crosstalk here, but here are my 3 personal go-tos:

  • leaflet, the mapping widget we used in this example
  • DT, the datatable widget we used in the example
  • plotly, a general purpose plotting widget that also can make any ggplot interactive.

With crosstalk and these three HTML widgets in our toolbag, we can make a lot of very impressive interactive dashboards. However, crosstalk really shines when we add one more feature: interactive filters.

Filter functions give the user direct control over plotting

Sometimes you want to explicitly specify the data to be plotted instead of relying on the user selecting rows or brushing across a plot. Crosstalk’s three filter operatings do exactly this with the following functions:

  • filter_checkbox: include rows in the shared dataframe whose column value equals any checked values. This is particularly useful for columns containing categorical values or factors.
  • filter_slider: include rows in the shared dataframe whose numeric column values fit within a range of specified values.
  • filter_select: include rows in the shared dataframe whose column value equals one of a list of possibilities. As with filter_checkbox, this is most useful for columns with categorical values or factors.

Let’s demonstrate all these filter functions by looking at our favorite(?) data set, mtcars. We’ll generate a plot using the ggplotly function of the plotly HTML widget, and then filter that data by the number of cylinders, horsepower, and whether the car has an automatic transmission or not. Because I’m publishing this in a blogdown post, I’m not going to try to make the plot look beautiful, but will just stack the filters inline ahead of the plot. If I were building an actual dashboard, I’d put the filter widgets inside a sidebar and dedicate most of the screen to the plot using an R Markdown layout.

The code is shown below. Play with the filter selectors and watch the plot change in response to your selections. I find it particularly fun to set the slider filter to a small range of values and then to slide the entire selection back and forth to show sub-ranges of horsepower values.

library(crosstalk)
library(plotly)
## Loading required package: ggplot2
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
new_mtcars <- mtcars %>% mutate(cyl = as.factor(cyl))
shared_mtcars <- SharedData$new(new_mtcars)
filter_checkbox("cyl", "Cylinders", shared_mtcars, ~cyl, inline = TRUE) ## that's a tilda in front of cyl, no a minus
filter_slider("hp", "Horsepower", shared_mtcars, ~hp, width = "40%")
filter_select("auto", "Automatic", shared_mtcars, ~ifelse(am == 0, "Yes", "No"))
g <- ggplot(shared_mtcars) + geom_point(aes(x = wt, y = mpg, color = cyl))
ggplotly(g)

Why not use Shiny instead of the crosstalk package?

Those of you who have built R Markdown dashboards before are probably wondering, “Why bother with crosstalk when we can do all of this and more in Shiny?” For some applications, R Markdown, HTML widgets, and crosstalk are a better choice for creating interactive applications because they:

  • Don’t require R once the dashboard is published. Shiny apps require a back end R environment to interact with the user, while an R Markdown dashboard with HTML widgets and crosstalk don’t.
  • Generate standalone documents. Not only do documents created using R Markdown, HTML widgets, and crosstalk not require an R back end – they don’t require any back end support whatsoever. Once your document has been knit, it runs entirely within the user’s browser.
  • Can be published and viewed in any environment that hosts web pages. Interactive applications built this way can be emailed, hosted on ordinary web servers, and used by people who don’t use R at all. If the person’s browser supports Javascript and HTML, that person can use your interactive document.

But there are real limitations too

While R Markdown, HTML widgets and crosstalk are wonderful tools, using them isn’t all rainbows and unicorns. Documents built using these tools aren’t nearly as flexible as Shiny apps, and crosstalk imposes significant limitations to what you can do. Should you use crosstalk and HTML widgets to build an interactive application, be aware that:

  • The HTML widgets you use must support crosstalk. While the HTML widgets we’ve chosen are some of the most popular, only a few others support crosstalk. Again, check with the full list of HTML widgets that work with crosstalk before committing to using crosstalk.
  • Your data must be in a data frame or tibble. The foundation of crosstalk is an R6 shared dataframe object, so any data you want to use with crosstalk must be in that format.
  • All your data must fit in memory. Because all your data will be loaded into the user’s browser, crosstalk is not the tool you want if you’re going to be browsing a multi-gigabyte data set.
  • Layouts and interactivity are limited. R Markdown and crosstalk typically work with grid-based layouts built from static data. If you want a document that dynamically changes in response to real-time data, you should probably be using Shiny instead.

Conclusion: try using crosstalk in your next interactive R Markdown project

As you can see from this posting, HTML widgets and crosstalk aren’t limited to standalone dashboards; they can be embedded in most R markdown documents that generate HTML, including blodown posts and emails. And of course, you can publish them on RStudio Connect or RPubs.com – you don’t need a Shiny server to share them.

I find the best part about crosstalk and its associated HTML widgets is that they seem to work well with my typical workflow. I spend much of my time exploring teaching survey data, and I usually plot results using ggplot and also show the raw data using the DT datatable package. To make those plots interactive, I only have to share my survey dataframe with SharedData$new(), adjust my ggplot and DT code to use ggplotly and my shared dataframe, and voila – I have an interactive way to explore my dataset. Learning to use crosstalk has saved me days of time; if you give it a try, it might do the same for you.

Resources

If you are interested in seeing the crosstalk materials we presented during the workshop, check out the workshop slides (in PDF form) and the exercises. You can also review the full set of workshop materials on github.

Contents
online
June 2 – 3, 2020
This workshop is the first step in becoming a certified RStudio instructor. It is run online for four hours on each of two days at a time suitable for participants in the Americas. Please contact us if you wish to take part.