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.
$party <- renderEcharts4r({
output# get the currently selected data from the map
<- my_leafdown$curr_sel_data()
df
# 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[, c("state_abbr", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
df pivot_longer(2:5, "party") %>%
group_by(party)
else {
} <- df[, c("County", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
df pivot_longer(2:5, "party") %>%
group_by(party)
$value <- df$value
dfnames(df)[1] <- "state_abbr"
}else {
} # show general election-result as no state is selected
<- data.frame(
df 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.
<- raster::getData(country = "USA", level = 1)
states <- raster::getData(country = "USA", level = 2)
counties # 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)
<- bs4DashPage(
ui 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
$head(
tags$style(HTML(".leaflet-container { background: #fff; height: 100%}")),
tags# workaround for the NA in leaflet legend see https://github.com/rstudio/leaflet/issues/615
$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;}"))
tags
),# 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
<- function(x, digits = 2, format = "f", ...) {
percent paste0(formatC(x * 100, format = format, digits = digits, ...), "%")
}
<- function(data, map_level) {
create_labels <- sprintf(
labels "<strong>%s</strong><br/>
Democrats: %s<br/>
Republicans: %s<br/>
Libertarians: %s<br/>
Green: %s<br/>
</sup>",
paste0("NAME_", map_level)],
data[, percent(data$Democrats2016),
percent(data$Republicans2016),
percent(data$Libertarians2016),
percent(data$Green2016)
)%>% lapply(htmltools::HTML)
labels
}
# Define server for leafdown app
<- function(input, output) {
server # load the shapes for the two levels
# TODO load the shapes you have downloaded via the raster package
<- readRDS("../inst/app_election/us1.RDS")
states <- readRDS("../inst/app_election/us2.RDS")
counties <- list(states, counties)
spdfs_list
# create leafdown object
<- Leafdown$new(spdfs_list, "leafdown", input)
my_leafdown
<- reactiveValues()
rv $update_leafdown <- 0
rv
# observers for the drilling buttons
observeEvent(input$drill_down, {
$drill_down()
my_leafdown$update_leafdown <- rv$update_leafdown + 1
rv
})
observeEvent(input$drill_up, {
$drill_up()
my_leafdown$update_leafdown <- rv$update_leafdown + 1
rv
})
<- reactive({
data req(rv$update_leafdown)
# fetch the current metadata from the leafdown object
<- my_leafdown$curr_data
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) {
$ST <- substr(data$HASC_2, 4, 5)
data# there are counties with the same name in different states so we have to join on both
<- left_join(data, us_election_counties, by = c("NAME_2", "ST"))
data else {
} $ST <- substr(data$HASC_1, 4, 5)
data<- left_join(data, us_election_states, by = "ST")
data
}# add the data back to the leafdown object
$add_data(data)
my_leafdown
data
})
# this is where the leafdown magic happens
$leafdown <- renderLeaflet({
outputreq(spdfs_list)
req(data)
<- data()
data
# depending on the selected KPI in the dropdown we show different data
if (input$map_sel == "unemployment") {
$y <- data$Unemployment * 100
data<- leaflet::colorNumeric("Greens", data$y)
fillcolor <- "Unemployment in Percent"
legend_title else {
} $y <- ifelse(data$Republicans2016 > data$Democrats2016, "Republicans", "Democrats")
data<- leaflet::colorFactor(c("#232066", "#E91D0E"), data$y)
fillcolor <- "Winning Party"
legend_title
}
<- create_labels(data, my_leafdown$curr_map_level)
labels # draw the leafdown object
$draw_leafdown(
my_leafdownfillColor = ~ 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
$socio <- renderEcharts4r({
output<- my_leafdown$curr_sel_data()
df # 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[, c("State", "Hispanic", "White", "Black", "Asian", "Amerindian", "Other")]
df <- df %>%
df pivot_longer(2:7, "race") %>%
group_by(State)
$value <- round(df$value, 2)
dfelse {
} <- df[, c("County", "Hispanic", "White", "Black", "Asian", "Amerindian", "Other")]
df <- df %>%
df pivot_longer(2:7, "race") %>%
group_by(County)
$value <- round(df$value / 100, 2)
df
}else {
} # show basic info for the whole usa as no state is selected
<- data.frame(
df 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"))
})
$party <- renderEcharts4r({
output<- my_leafdown$curr_sel_data()
df # 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[, c("ST", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
df pivot_longer(2:5, "party") %>%
group_by(party)
else {
} <- df[, c("County", "Democrats2016", "Republicans2016", "Libertarians2016", "Green2016")]
df <- df %>%
df pivot_longer(2:5, "party") %>%
group_by(party)
$value <- df$value
dfnames(df)[1] <- "ST"
}else {
} # show general election-result as no state is selected
<- data.frame(
df 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