crosstalk
TutorialThis tutorial will help you learn to use the crosstalk
package to link different htmlwidgets
. After completing this tutorial, you will be able to build an application like the one below, which visualizes the number of rides at a sample of Chicago train stations in 2019 versus 2020.
Click and drag a subset of points on the scatterplot below to see how the map and table are dynamically filtered.
This tutorial assumes familiarity with the following packages:
ggplot2
plotly
DT
leaflet
You don’t need to be an expert of any of these packages to follow along. However, syntax related to those packages won’t be explained in great detail.
Now that you know how to make a SharedData
object with crosstalk
, we can make our first application. The steps are:
SharedData
objecthtmlwidgets
with SharedData
inputggplot
objects…ggplotly
objectsFor step 3, I use the crosstalk::bscols()
function to put the resulting interactive plots in a row (similar to grid.arrange
.) However, you could also just output them one-at-a-time.
In the output below, click on a point on one plot and notice that the point related to the same station is highlighted in the other plot.
# make SharedData object ----
trips_ct <- SharedData$new(trips, key = ~station_id)
# make ggplots using SharedData object ----
gg_year <-
ggplot(trips_ct) +
aes(x = year_2019, y = pct_change, col = pct_change) +
geom_point()
gg_week <-
ggplot(trips_ct) +
aes(x = prop_wend_2019, y = pct_change, col = pct_change) +
geom_point()
# convert ggplots to ggplotly ----
gg_year_ly <- ggplotly(gg_year)
gg_week_ly <- ggplotly(gg_week)
# compose output ----
bscols(gg_year_ly, gg_week_ly)
crosstalk
doesn’t just work with plots! It works with selected other htmlwidgets
also. Specifically, you may use a SharedData
object with DT
tables and leaflet
maps. Just as with ggplot()
, you simply pass your SharedData
into functions from these packages just as you would a normal dataframe.
In the following example, we will now make a plot interact with a table instead of another plot. Let’s recall our key steps and then execute!
SharedData
objecthtmlwidgets
with SharedData
inputggplotly
sggplotly
and one datatable
bscols
again, but I didn’t to show that it wasn’t necessary# make SharedData object ----
trips_ct <- SharedData$new(trips, key = ~station_id)
# make ggplots using SharedData object ----
gg_year <-
ggplot(trips_ct) +
aes(x = year_2019, y = pct_change, col = pct_change) +
geom_point()
# make htmlwidgets ----
gg_year_ly <- ggplotly(gg_year)
dt_stations <- datatable(trips_ct)
# compose output ----
gg_year_ly
dt_stations
If you recall our original motivation, we also showed a map. To make this map, we need the latitude and longitude of each station. However, this isn’t in our trips
dataset. Instead, it is in the stations
dataset shown below.
head(stations)
## # A tibble: 6 x 4
## # Groups: station_id [6]
## station_id station_name lat lon
## <chr> <chr> <dbl> <dbl>
## 1 40040 Quincy/Wells 41.9 -87.6
## 2 40150 Pulaski 41.9 -87.7
## 3 40280 Central 41.9 -87.8
## 4 40350 UIC-Halsted 41.9 -87.6
## 5 40360 Southport 41.9 -87.7
## 6 40390 Forest Park 41.9 -87.8
Fortunately, we can link multiple SharedData
objects together!
(In theory, we could also join stations
and trips
and use one dataset. We intentionally won’t do that to motivate the next example.)
Now, let’s practice. We follow the same recipe as before. Only now we create two SharedData
objects.
SharedData
objectshtmlwidgets
with SharedData
input# make SharedData object ----
trips_ct <- SharedData$new(trips, key = ~station_id, group = "group")
stations_ct <- SharedData$new(stations, key = ~station_id, group = "group")
# make ggplots with SharedData object ----
gg_year <-
ggplot(trips_ct) +
aes(x = year_2019, y = pct_change, col = pct_change) +
geom_point()
# make htmlwidgets ----
gg_year_ly <- ggplotly(gg_year)
lf_station <- leaflet(stations_ct) %>% addTiles() %>% addMarkers()
# compose output ----
bscols(gg_year_ly, lf_station)
Now, let’s return to our motivational example. With what you’ve learned, you should now be able to understand all of the code required to reproduce it. Read through and think about how each part maps to the output and matches the steps we’ve been following:
SharedData
objectshtmlwidgets
with SharedData
input# load libraries ----
library(ggplot2)
library(plotly)
library(DT)
library(leaflet)
library(crosstalk)
# create SharedData ----
trips_ct <- SharedData$new(trips, key = ~station_id, group = "loc")
trips_sub_ct <- SharedData$new(trips[,c("station_name", "station_id", "pct_change")],
key = ~station_id, group = "loc")
stations_ct <- SharedData$new(stations, key = ~station_id, group = "loc")
# create individual widgets ----
# map
lf <- leaflet(stations_ct) %>% addTiles() %>% addMarkers()
# table
dt <- datatable(trips_sub_ct,
fillContainer = TRUE,
rownames = FALSE,
colnames = c("Station", "ID", "% Change"),
options = list(autowidth = TRUE))
# plot
gg <- ggplot(trips_ct) +
aes(
x = prop_wend_2019,
y = pct_change,
col = pct_change,
name = station_name) +
geom_point() +
guides(col = FALSE) +
labs(
x = "% Apr 2019 Trips on Weekend",
y = "% Change Apr 2020 vs 2019"
) +
scale_x_continuous(labels = scales::percent) +
scale_y_continuous(labels = scales::percent)
gg_ly <-
ggplotly(gg, tooltip = "name") %>%
highlight(on = "plotly_selected",
off = "plotly_doubleclick")
# create final output ----
bscols(gg_ly, lf, dt, widths = c(6, 6, 12))
To learn more about crosstalk
and browser-based interactivity in R, check out package author Carson Sievert’s book Interactive web-based data visualization with R, plotly, and shiny from CRC Press!