sd <- SharedData$new(pen, key = ~id)Linked views with crosstalk
crosstalk links HTML widgets in the browser through a shared key, with no Shiny and no server. Wrap a data frame in a SharedData object, hand it to every widget, and a selection made in one propagates to the rest. as_widget() joins in through its crosstalk argument: the plot’s data_ids are matched against the SharedData’s keys.
Here one SharedData over the 333 complete Palmer Penguins drives everything on the page. The key is a row id that each view carries.
Filter inputs
crosstalk ships input widgets that filter the shared data directly. A filter hides the non-matching rows in every linked view at once.
bscols(
filter_checkbox("species", "Species", sd, ~species, inline = TRUE),
filter_select("island", "Island", sd, ~island),
filter_slider("mass", "Body mass (g)", sd, ~body_mass_g, width = "100%")
)Two plots, linked
Both scatterplots read from sd, so a brush drawn in either one highlights the same penguins in the other. The left plot is the well-known one: pooled, bill length and depth look negatively related, but brush a single species and the trend inside it runs the other way.
bill <- vplot(pen) |>
mark_point(x = bill_length_mm, y = bill_depth_mm, color = species,
data_id = id, tooltip = species, size = 2.4) |>
labs(title = "Bill length vs. depth", x = "length (mm)", y = "depth (mm)")
body <- vplot(pen) |>
mark_point(x = flipper_length_mm, y = body_mass_g, color = species,
data_id = id, tooltip = species, size = 2.4) |>
labs(title = "Flipper vs. body mass", x = "flipper (mm)", y = "mass (g)")
bscols(
as_widget(bill, crosstalk = sd, height = 360),
as_widget(body, crosstalk = sd, height = 360)
)A table, linked too
The same sd backs a DT table. Brushing either plot scrolls the selection to the top and highlights it here; ticking rows here highlights the points there.
datatable(
sd,
rownames = FALSE,
colnames = c("Species", "Island", "Bill length", "Bill depth",
"Flipper", "Mass", "Sex", "Year", "id"),
options = list(pageLength = 6, dom = "tp")
)Nothing above talks to a server. crosstalk resolves every selection and filter in the browser from the shared keys, so the whole page works as a static file. The trade-off is that the data ships to the client, which suits a few thousand rows, not a few million.