Building a climate risk dashboard - server (output)

Application exercise

April 16, 2024

Communicating climate risk

FEMA has asked us to build an improved dashboard that visualizes the risk of climate change in the United States.

Based on your submitted designs and our skill level, we will work to implement this Shiny dashboard.

Construct the server (output)

National map

Starter code to generate the national map

Fill in the TODOs and correctly integrate into the server code chunk.

# visualize the risks nationally
ggplot(data = climate_sf) +
  # layer for each county's risk
  geom_sf(mapping = aes(fill = TODO)) +
  # layer for state boundaries to better locate regions
  geom_sf(data = state_sf, fill = NA, color = "white") +
  # appropriate color palette
  scale_fill_discrete_diverging(rev = TRUE) +
  # don't label the legend
    fill = NULL
  ) +
  # map theme
  theme_map(base_size = 18) +
  # position the legend on the right
  theme(legend.position = "right")

To access input values which are character strings and treat them as column names, use the bang-bang operator. For example, if input$var_name contained the name of a column and you wanted to use it in a ggplot2 visualization, you could write your code like:

#| context: server

output$myplot <- renderPlot({
  ggplot(data = df, mapping = aes(x = !!input$var_name)) +

County map

Starter code to generate the national map

Fill in the TODOs and correctly integrate into the server code chunk.

# get selected county's state
county_state <- climate_sf |>
  filter(county == TODO) |>

climate_sf |>
  # filter for counties in specific state
  filter(STATE_NAME == county_state) |>
  # variable to highlight selected county
  mutate(selected = county == TODO) |>
  # draw the map
  ggplot() +
  geom_sf(mapping = aes(fill = selected, color = selected)) +
  scale_fill_manual(values = c(NA, "orange")) +
  scale_color_manual(values = c("white", "orange")) +
  theme_map() +
  theme(legend.position = "none")

Validating inputs

What happens if there is no county provided in the input? Cryptic red error messages in the UI can be disconcerting to users. Instead, we can use validate() and need() to shunt the error messages to the console and print a human-readable message to the user.

For example, say the output relies on input$num which must not be empty. We can use

need(input$num, "Provide a number")

to check if input$num is provided. If the condition fails (i.e. input$num is blank or missing), then need() returns the specified character string. We can incorporate these requirements into the output using validate().

output$myplot <- renderPlot({
    need(input$num, "Provide a number")
  # remaining code...

validate() takes any number of arguments depending on how many conditions must be checked. If any of them fail, the app triggers an error which is reported in the console and displays the need()-provided message to the user in the UI.

County risk scores


County stats for value boxes

Use the filtered data frame to generate the text strings for each of the value boxes.

Starter code to generate the national map
# overall risk
output$county_risk <- renderText({
  # get selected county's overall risk score
  val <- selected_county() |>

  # format using scales function
  label_number(accuracy = 1)(val)

Individual hazard percentiles

Starter code to generate the national map

Fill in the TODOs and correctly integrate into the server code chunk.

# get selected county's hazard percentiles
selected_county_hazards <- climate_risk |>
  filter(county == TODO)

# plot hazard percentiles
selected_county_hazards |>
  # reshape to long format for visualizing
    cols = contains("hazard"),
    names_to = "hazard",
    values_to = "percentile"
  ) |>
  # only visualize selected hazard types
  filter(hazard %in% TODO) |>
  # order alphabetically
  mutate(hazard = str_remove(hazard, "_hazard_type_risk_index_score") |>
           make_clean_names(case = "title") |>
           fct_rev()) |>
  ggplot(mapping = aes(y = hazard)) +
  # all hazards range between 0 and 100
  geom_linerange(mapping = aes(xmin = 0, xmax = 100)) +
  # draw specific county
    mapping = aes(x = percentile, color = percentile),
    size = 4
  ) +
  # optimized color palette
  scale_color_continuous_diverging(mid = 50, rev = TRUE, guide = "none") +
  # appropriate labels
    x = "Percentile",
    y = NULL
  ) +
  # clean up the theme
  theme_minimal(base_size = 18) +
    panel.grid.minor = element_blank(),
    panel.grid.major.y = element_blank()