Connection_to_other_elements.Rmd
In this article, we demonstrate how to connect a leafdown
map to other Shiny elements.
Click here for the full demo app
As described in the Introduction article., we need two types of data:
SpatialPolygonsDataFrames, the shapes of the US-States and Counties, taken from the raster
package.
Election Results and Census Data, the data we want to display on the map, taken from the example data sets that come with the leafdown
package. (The original data comes from Deleetdk. For more information about the data, please see ?us_election_states
or ?us_election_counties
respectively)
The structure of the map is pretty similar to the map from the Introduction article. Here we show the results of the US Presidential Election from 2016.
In this section, we want to demonstrate how simple it is to connect graphs or similar UI-elements with the map.
We create two graphs that give more insight into the currently selected shapes:
The changes in the UI are straightforward:
column(
width = 5,
# box for racial makeup graph
bs4Card(
width = 12,
closable = F,
collapsible = F,
title = "Racial makeup in percentages",
echarts4rOutput("socio")
),
# box for party percent graph
bs4Card(
width = 12,
closable = F,
collapsible = F,
title = "Votes in percent",
echarts4rOutput("party")
)
)
To connect the graphs with the map, we can use the $curr_sel_data()
attribute. This attribute is a reactiveValue
which allows us to update the graphs whenever the user selects a shape on the map or drills a level up or down.
In the server, we obtain the data using df <- my_leafdown$curr_sel_data()
.
Creating the rest of the graph is again straightforward.
output$party <- renderEcharts4r({
# get the currently selected data from the map
df <- my_leafdown$curr_sel_data()
# check whether any shape is selected, show general election-result if nothing is selected
if (nrow(df) > 0) {
if (my_leafdown$curr_map_level == 1) {
df <- df[, c("state_abbr", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
pivot_longer(2:5, "party") %>%
group_by(party)
} else {
df <- df[, c("County", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
pivot_longer(2:5, "party") %>%
group_by(party)
df$value <- df$value
names(df)[1] <- "state_abbr"
}
} else {
# show general election-result as no state is selected
df <- data.frame(
party = c("Democrats2016", "Republicans2016", "Libertarians2016", "Green2016"),
state_abbr = "USA",
value = c(0.153, 0.634, 0.134, 0.059)
) %>%
group_by(party)
}
# create the graph
df %>%
e_charts(state_abbr, stack = "grp") %>%
e_bar(value) %>%
e_y_axis(formatter = e_axis_formatter("percent", digits = 2)) %>%
e_tooltip(trigger = "axis", axisPointer = list(type = "shadow")) %>%
e_legend(right = 10, top = 10) %>%
e_color(c("#232066", "#E91D0E", "#f3b300", "#006900")) %>%
e_tooltip(formatter = e_tooltip_item_formatter("percent", digits = 2))
})
Note: The shapes have to be manually downloaded before the app can be used. In the given election app the shapes have also been simplified to 0.5% of their original size.
states <- raster::getData(country = "USA", level = 1)
counties <- raster::getData(country = "USA", level = 2)
# TODO replace the path to your downloaded shapes in the server code
library(shiny)
library(bs4Dash)
library(shinyjs)
library(leaflet)
library(leafdown)
library(echarts4r)
library(dplyr)
library(tidyr)
library(RColorBrewer)
ui <- bs4DashPage(
title = "Leafdown Showcase - USA Election Data",
navbar = bs4DashNavbar(tags$h3("Leafdown Showcase - USA Election Data", style = "margin-bottom: .2rem;")),
bs4DashSidebar(disable = TRUE),
body = bs4DashBody(
# set the background of the map-container to be white
tags$head(
tags$style(HTML(".leaflet-container { background: #fff; height: 100%}")),
# workaround for the NA in leaflet legend see https://github.com/rstudio/leaflet/issues/615
tags$style(HTML(".leaflet-control div:last-child {clear: both;}")),
tags$style(HTML(".card {height: 100%;}")),
tags$style(HTML(".col-sm-12:last-child .card {margin-bottom: 0 !important;}")),
tags$style(HTML("#leafdown {height: 80% !important; margin-top: 10px; margin-bottom: 10px;}"))
),
# we need shinyjs for the leafdown map
useShinyjs(),
fluidRow(
# a card for the map
bs4Card(
title = "Map",
closable = FALSE,
collapsible = FALSE,
width = 6,
# a dropdown to select what KPI should be displayed on the map
selectInput(
"map_sel", "Select what KPI to display on the map:",
c("Votes" = "votes", "Unemployment" = "unemployment")
),
# the two buttons used for drilling
actionButton("drill_down", "Drill Down"),
actionButton("drill_up", "Drill Up"),
# the actual map element
leafletOutput("leafdown")
),
# a column with the two graphs
column(
width = 6,
# box for racial makeup graph
bs4Card(
width = 12,
closable = F,
collapsible = F,
title = "Racial makeup in percentages",
echarts4rOutput("socio")
),
# box for party percent graph
bs4Card(
width = 12,
closable = F,
collapsible = F,
title = "Votes in percent",
echarts4rOutput("party")
)
)
)
)
)
# Create user-defined function
percent <- function(x, digits = 2, format = "f", ...) {
paste0(formatC(x * 100, format = format, digits = digits, ...), "%")
}
create_labels <- function(data, map_level) {
labels <- sprintf(
"<strong>%s</strong><br/>
Democrats: %s<br/>
Republicans: %s<br/>
Libertarians: %s<br/>
Green: %s<br/>
</sup>",
data[, paste0("NAME_", map_level)],
percent(data$Democrats2016),
percent(data$Republicans2016),
percent(data$Libertarians2016),
percent(data$Green2016)
)
labels %>% lapply(htmltools::HTML)
}
# Define server for leafdown app
server <- function(input, output) {
# load the shapes for the two levels
# TODO load the shapes you have downloaded via the raster package
states <- readRDS("../inst/app_election/us1.RDS")
counties <- readRDS("../inst/app_election/us2.RDS")
spdfs_list <- list(states, counties)
# create leafdown object
my_leafdown <- Leafdown$new(spdfs_list, "leafdown", input)
rv <- reactiveValues()
rv$update_leafdown <- 0
# observers for the drilling buttons
observeEvent(input$drill_down, {
my_leafdown$drill_down()
rv$update_leafdown <- rv$update_leafdown + 1
})
observeEvent(input$drill_up, {
my_leafdown$drill_up()
rv$update_leafdown <- rv$update_leafdown + 1
})
data <- reactive({
req(rv$update_leafdown)
# fetch the current metadata from the leafdown object
data <- my_leafdown$curr_data
# join the metadata with the election-data.
# depending on the map_level we have different election-data so the 'by' columns for the join are different
if (my_leafdown$curr_map_level == 2) {
data$ST <- substr(data$HASC_2, 4, 5)
# there are counties with the same name in different states so we have to join on both
data <- left_join(data, us_election_counties, by = c("NAME_2", "ST"))
} else {
data$ST <- substr(data$HASC_1, 4, 5)
data <- left_join(data, us_election_states, by = "ST")
}
# add the data back to the leafdown object
my_leafdown$add_data(data)
data
})
# this is where the leafdown magic happens
output$leafdown <- renderLeaflet({
req(spdfs_list)
req(data)
data <- data()
# depending on the selected KPI in the dropdown we show different data
if (input$map_sel == "unemployment") {
data$y <- data$Unemployment * 100
fillcolor <- leaflet::colorNumeric("Greens", data$y)
legend_title <- "Unemployment in Percent"
} else {
data$y <- ifelse(data$Republicans2016 > data$Democrats2016, "Republicans", "Democrats")
fillcolor <- leaflet::colorFactor(c("#232066", "#E91D0E"), data$y)
legend_title <- "Winning Party"
}
labels <- create_labels(data, my_leafdown$curr_map_level)
# draw the leafdown object
my_leafdown$draw_leafdown(
fillColor = ~ fillcolor(data$y),
weight = 3, fillOpacity = 1, color = "white", label = labels
) %>%
# set the view to be center on the USA
setView(-95, 39, 4) %>%
# add a nice legend
addLegend(
pal = fillcolor,
values = ~ data$y,
title = legend_title,
opacity = 1
)
})
# plots
output$socio <- renderEcharts4r({
df <- my_leafdown$curr_sel_data()
# check whether any shape is selected, show basic info for the whole usa if nothing is selected
if (nrow(df) > 0) {
if (my_leafdown$curr_map_level == 1) {
df <- df[, c("State", "Hispanic", "White", "Black", "Asian", "Amerindian", "Other")]
df <- df %>%
pivot_longer(2:7, "race") %>%
group_by(State)
df$value <- round(df$value, 2)
} else {
df <- df[, c("County", "Hispanic", "White", "Black", "Asian", "Amerindian", "Other")]
df <- df %>%
pivot_longer(2:7, "race") %>%
group_by(County)
df$value <- round(df$value / 100, 2)
}
} else {
# show basic info for the whole usa as no state is selected
df <- data.frame(
ST = "USA",
race = c("Hispanic", "White", "Black", "Asian", "Amerindian", "Other"),
value = c(0.15, 0.634, 0.134, 0.059, 0.015, 0.027)
) %>%
group_by(ST)
}
# create the graph
df %>%
e_charts(race) %>%
e_bar(value) %>%
e_tooltip(trigger = "axis", axisPointer = list(type = "shadow")) %>%
e_y_axis(
splitArea = list(show = FALSE),
splitLine = list(show = FALSE),
formatter = e_axis_formatter("percent", digits = 2)
) %>%
e_legend(orient = "vertical", right = 10, top = 10) %>%
e_color(brewer.pal(nrow(df), "Set3")) %>%
e_tooltip(formatter = e_tooltip_item_formatter("percent"))
})
output$party <- renderEcharts4r({
df <- my_leafdown$curr_sel_data()
# check whether any shape is selected, show general election-result if nothing is selected
if (nrow(df) > 0) {
if (my_leafdown$curr_map_level == 1) {
df <- df[, c("ST", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
pivot_longer(2:5, "party") %>%
group_by(party)
} else {
df <- df[, c("County", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
pivot_longer(2:5, "party") %>%
group_by(party)
df$value <- df$value
names(df)[1] <- "ST"
}
} else {
# show general election-result as no state is selected
df <- data.frame(
party = c("Democrats2016", "Republicans2016", "Libertarians2016", "Green2016"),
ST = "USA",
value = c(0.153, 0.634, 0.134, 0.059)
) %>%
group_by(party)
}
# create the graph
df %>%
e_charts(ST, stack = "grp") %>%
e_bar(value) %>%
e_y_axis(formatter = e_axis_formatter("percent", digits = 2)) %>%
e_tooltip(trigger = "axis", axisPointer = list(type = "shadow")) %>%
e_legend(right = 10, top = 10) %>%
e_color(c("#232066", "#E91D0E", "#f3b300", "#006900")) %>%
e_tooltip(formatter = e_tooltip_item_formatter("percent", digits = 2))
})
}
shinyApp(ui, server)
You can find the full demo app hosted on shinyapps.io