library(tidyverse)
library(crosstalk)
library(d3scatter)
library(plotly)
library(DT)
library(summarywidget)
Most everyone who works with data is familiar with or at least aware of Shiny and all of the great things you can do with it’s interactive capabilities. However, Shiny also requires some extra work on the backend that may not be available to everyone. I can write Shiny apps all day, but I can’t share them with coworkers since they don’t have R/Rstudio, and my employer doesn’t have a Shiny server, nor do we have an account with shinyapps.io.
Most of the time when I need to share some data that I have worked on in R I use Rmarkdown/Quarto, with a PDF or Word output for printing purposes and an HTML report that uses Plotly, DT, Knitr, etc. to give some level of responsiveness. However, the crosstalk
library allows users to bridge the gap between a fully interactive Shiny document and a fixed Rmarkdown/Quarto document.
Libraries
First things first, we’ll need to load some libraries:
- tidyverse: Needs no explanation
- crosstalk: This is the magic sauce
- d3scatter: Simple plots (think
ggplot() + theme_bw()
) that work withcrosstalk
- plotly: Wonderful interactive plots on their own,
plotly
plots can be made more interactive withcrosstalk
- DT: DT makes tables that already have filtering and responsiveness built-in, but we can use crosstalk to further filter and update in sync with the plots
- summarywidget: This lets us generate summary values, like you might do in Shiny
Other crosstalk compatible libraries are leaflet
for mapping and rgl
for interactive 3d plotting.
The Basics
To use crosstalk, we have to create a SharedData object:
<- SharedData$new(mtcars) sharedMtCars
Then we use these objects like data.frames in crosstalk-compatible widgets.
crosstalk
also provides some filter_*
functions:
<- filter_checkbox("cyl", "Cylinders", sharedMtCars, ~cyl, inline=T)
carsCheckBox <- filter_select("am", "Automatic", sharedMtCars, ~am)
carsSelect <- filter_slider("disp", "Displacement", sharedMtCars, ~disp)
carsSlider
<- d3scatter(sharedMtCars, ~wt, ~mpg, ~factor(cyl), width="100%") carsPlot
Changing the selected values will cause this plot to update, no need for Shiny:
Simple Plots
d3scatter
d3scatter is a dead-simple library for graphs that work with crosstalk. As seen above, it’s very much like a simple ggplot in appearance. As long as the plot and all of the filter_*
functions use the same SharedData object, then the plot will update in real time with the various selections.
What’s really neat about crosstalk is that we can update multiple plots at once:
# create a new SharedObject so we're not manipulating everything on the page:
<- SharedData$new(mtcars)
sharedMtCars
<- filter_checkbox("cyl", "Cylinders", sharedMtCars, ~cyl, inline=T)
carsCheckBox <- filter_select("am", "Automatic", sharedMtCars, ~am)
carsSelect <- filter_slider("disp", "Displacement", sharedMtCars, ~disp)
carsSlider
<- d3scatter(sharedMtCars, ~wt, ~mpg, ~factor(cyl), width="100%")
carsPlotMpg <- d3scatter(sharedMtCars, ~wt, ~hp, ~factor(cyl), width="100%") carsPlothp
d3scatter
also allows click-and-drag selection of points, which will also be applied to any other d3scatter
plots using the same SharedData.
Plotly
Plotly graphs are already responsive and allow zooming and selection, but using crosstalk filter_
functions we can do even more selection than what would be available otherwise, with the effects carrying over into other plots.
<- SharedData$new(mtcars)
sharedMtCars
<- filter_checkbox("cyl", "Cylinders", sharedMtCars, ~cyl, inline=T)
carsCheckBox <- filter_select("am", "Automatic", sharedMtCars, ~am)
carsSelect <- filter_slider("disp", "Displacement", sharedMtCars, ~disp)
carsSlider
<- plot_ly(sharedMtCars, x=~wt, y=~mpg, color = ~factor(cyl))
plotlyMpg <- plot_ly(sharedMtCars, x=~wt, y=~hp, color = ~factor(cyl)) plotlyHp
Data Tables
Crosstalk can also update DT objects:
<- SharedData$new(mtcars)
sharedMtCars
filter_checkbox("cyl", "Cylinders", sharedMtCars, ~cyl, inline=T)
filter_select("am", "Automatic", sharedMtCars, ~am)
filter_slider("disp", "Displacement", sharedMtCars, ~disp)
datatable(sharedMtCars)
Summary Values
The crosstalk-compatible library summarywidget
allows for simple summaries of SharedData objects:
<- SharedData$new(mtcars)
sharedMtCars
filter_checkbox("cyl", "Cylinders", sharedMtCars, ~cyl, inline=T)
filter_select("am", "Automatic", sharedMtCars, ~am)
filter_slider("disp", "Displacement", sharedMtCars, ~disp)
# Counts
summarywidget(sharedMtCars, statistic = "count", digits=0)
# Means
summarywidget(sharedMtCars, statistic = "mean", column = "mpg", digits=1)
# Sum
summarywidget(sharedMtCars, statistic = "sum", column = "disp", digits=1)
These can be evaluated in text passages for ready-made summary statements.
summarywidget
can also apply to summary to a subset of the SharedData. This example averages mpg for automatic transmissions only:
summarywidget(sharedMtCars, statistic = "mean",
column = "mpg", digits=1, selection = ~am == 0)
Putting these pieces of code together, we can render the following:
Total cars selected:
Average MPG:
Average MPG (Manuals only):
Note: summarywidget can be used inline in rmarkdown documents rendered with knitr, but Quarto doesn’t want to play nicely with that yet.
To see a complete dashboard click here.