This project was made possible by cooperative agreement CDC-RFA-FT-23-0069 from the CDC’s Center for Forecasting and Outbreak Analytics. Its contents are solely the responsibility of the authors and do not necessarily represent the official views of the Centers for Disease Control and Prevention.
Create synchronized, interactive dashboards where one click updates multiple components
linkeR makes it effortless to create linked views in ‘shiny’ applications. When users interact with one component (like clicking a map marker), all related components (tables, charts, other maps) automatically update to show corresponding information.
What linkeR Does
- Bidirectional Linking: Click a map marker → table row highlights. Click table row → map zooms and shows popup.
- One-Line Setup: Link multiple components with a single function call
- Custom Behaviors: Define exactly what happens when users click different components
- Multi-Component Support: Link maps, tables, charts, and more in complex dashboards
- Zero Boilerplate: No manual observer setup or event handling required
Why Use linkeR?
Before linkeR (Manual Approach)
# Complex manual setup for each component pair
observeEvent(input$map_marker_click, {
clicked_id <- input$map_marker_click$id
# Find corresponding table row
row_idx <- which(my_data()$id == clicked_id)
# Update table selection
dataTableProxy("my_table") %>% selectRows(row_idx)
# Update map view
leafletProxy("my_map") %>% setView(...)
# Update any other components...
# Repeat this for every component combination!
})
observeEvent(input$my_table_rows_selected, {
# More boilerplate code...
# Handle edge cases...
# Ensure consistency...
})With linkeR (Simple Approach)
# One line links everything!
link_plots(
session,
my_map = my_data,
my_table = my_data,
shared_id_column = "id"
)Installation
# Install from CRAN (when available)
install.packages("linkeR")
# Or install development version from GitHub
# install.packages("devtools")
devtools::install_github("EpiForeSITE/linkeR")Requirements
For linking to work, your setup needs:
- Shared ID Column: All datasets must have a common identifier column
-
Matching Component IDs:
- Leaflet: Use
layerId = ~your_id_column - DT: Row numbers automatically map to data rows
- Leaflet: Use
-
Reactive Data: Wrap your data in
reactive()
# Good: Proper setup
my_data <- reactive({
data.frame(
id = 1:10, # ← Shared ID column
name = paste("Item", 1:10),
lat = runif(10), lng = runif(10)
)
})
output$my_map <- renderLeaflet({
leaflet(my_data()) %>%
addMarkers(layerId = ~id) # ← layerId matches shared_id_column
})
link_plots(session, my_map = my_data, shared_id_column = "id")
# Bad: Missing layerId
output$my_map <- renderLeaflet({
leaflet(my_data()) %>%
addMarkers() # ← No layerId = no linking!
})Supported Components
| Component | Status | Notes |
|---|---|---|
| Leaflet Maps | ✅ Full Support | Interactive maps with markers, circles, polygons |
| DT DataTables | ✅ Full Support | Sortable, filterable tables |
| Plotly Charts | 🔄 Partial | Single selection supported |
| Custom Components | 🔄 Partial | Any ‘shiny’ component with click events, Requires manual event handling |
| Base R Plots | 📋 Planned | Static plots with click detection |
| Mapbox | 📋 Planned | Alternative mapping solution |
Plotly visual feedback
For native plotly selection highlighting to work, your Shiny UI must include a custom JavaScript message handler:
tags$script(HTML("
Shiny.addCustomMessageHandler('eval', function(code) {
try {
eval(code);
} catch(e) {
console.error('JavaScript execution error:', e);
}
});
"))This enables linkeR to send selection updates to plotly charts for visual feedback.