library("bslib")
library("bsicons")
library("sf")
library("tidyverse")
bee_data <- readr::read_csv("bee_data.csv")
states_shp <- readr::read_rds("us_states_shp.rds")
color_1 <- "#E77500"
color_2 <- "#121212"Interactive Dashboards
In this blog post, I want to outline my upcoming Princeton Wintersession workshop Interactive Dashboards for Collaborative Communication. In this workshop, we will explore the new technology called Quarto dashboards. In this exploration, I have chosen a different data set per my interest, but much of the credit for the design of this workshop goes to the folks at Posit and other data scientists who have already created wonderful tutorials (see Additional Resources).
Dashboards
- Examples from Posit
Why R?
Why Quarto?
Posit Cloud
Case Study: Bees!
Concern for Bee Populations


Data Source
Our data come from the Quick Stats at USDA NASS (United States Department of Agriculture National Agricultural Statistics Service). Users can access a subset of the data through dropdown menus. For replicability, I list my selections below:

Commodity:
- Program: SURVEY
- Sector: ANIMALS & PRODUCTS
- Group: SPECIALTY
- Commodity: HONEY
- Category: INVENTORY
- Data item: HONEY, BEE COLONIES - INVENTORY, MEASURED IN COLONIES
Location:
- Geographic Level: STATE
- State: [leave unselected for all states]
Time:
- Year: [selected 2014 to 2024 to obtain about a decade of data]
- Period Type: ANNUAL (note: the “annual” measurements were done by each state, while other granularity might not be available for all states)
- Period: MARKETING YEAR
Bar Charts

bee_data |>
group_by(Year) |>
mutate(total_colonies = sum(Value)) |>
ungroup() |>
select(Year, total_colonies) |>
distinct() |>
mutate(num_colonies = total_colonies / 1e6) |>
ggplot(aes(x = factor(Year), y = num_colonies)) +
geom_bar(color = "#121212", fill = "#E77500",
stat = "identity") +
coord_cartesian(ylim = c(2.5,3.0)) +
labs(title = "US Bee Population",
subtitle = "Survey of bee colonies",
# caption = "source: USDA NASS",
x = "year",
y = "bee colonies (in millions)") +
theme_minimal()
nj_plot <- bee_data |>
filter(State == "NEW JERSEY") |>
mutate(num_colonies = Value / 1e3) |>
ggplot(aes(x = factor(Year), y = num_colonies)) +
geom_bar(color = "#121212", fill = "#E77500",
stat = "identity") +
labs(title = "NEW JERSEY",
subtitle = "Survey of bee colonies",
caption = "source: USDA NASS",
x = "year",
y = "bee colonies (in thousands)") +
theme_minimal()
nj_plot
bee_data |>
filter(Year >= 2015 & Year <= 2019) |>
filter(State %in% c("CALIFORNIA", "NEW JERSEY")) |>
ggplot(aes(x = Year, y = Value)) +
facet_wrap(vars(State), scales = "free_y") +
geom_bar(stat = "identity")Maps

states_shp |>
mutate(this_selection = ifelse(NAME == "New Jersey",
"focus", "other states")) |>
ggplot() +
geom_sf(aes(fill = this_selection)) +
labs(title = "State Selection",
subtitle = "toward a Dashboard card",
caption = "Princeton Wintersession") +
scale_fill_manual(values = c(color_1, "gray80")) +
theme_minimal()
nj_map <- states_shp |>
mutate(this_selection = ifelse(NAME == "New Jersey",
"focus", "other states")) |>
ggplot() +
geom_sf(aes(fill = this_selection)) +
labs(title = "New Jersey",
# subtitle = "toward a Dashboard card",
caption = "Princeton Wintersession") +
scale_fill_manual(values = c(color_1, "gray80")) +
theme_minimal() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "none")
nj_mapSetup (YAML)
In a Posit Cloud session, create a new Quarto document, and then change the format to dashboard.
---
title: "My Dashboard"
format: dashboard
---
# notebook content goes here...
Layout
Cards
Each additional visible object is arranged into the dashboard as a card.
Rows
By default, the arrangement of the cards is in rows.
nj_plot
nj_map
Columns
To change the arrangements into columns, change the YAML header
format:
dashboard:
orientation: columns
More specifically, our file will use
- first-level headers (
#) for tabs - second-level headers (
##) for columns - third-level headers (
###) for rows
Value Boxes
::: {.valuebox icon="award-fill" color="#E77500"}
NEW JERSEY
:::
Of importance, for a valuebox to appear and function as intended, the R code needs to output a value.
Heights and Widths
At the desired header, use commands like
{height = "25%"}{width = "30%"}
Interactivity
Inputs
Let the user pick the state. Place the following code into the sidebar.
selectInput(
"state_choice",
"Choose a state:",
choices = unique(bee_data$State)
)
Let the user pick the year. Place the following code into the sidebar.
sliderInput(
"year_choice", "Choose a year:",
min = 2015, max = 2023, step = 1,
value = 2023,
)
Server
Now, we need use the shiny framework to perform these computations in a reactive way. Create the following code chunk, and be sure to set #| context: server.
#| context: server
output$state_plot <- renderPlot({
bee_data |>
filter(State == input$state_choice) |>
mutate(num_colonies = Value / 1e3) |>
mutate(year_color = ifelse(Year == input$year_choice,
TRUE, FALSE)) |>
ggplot(aes(x = factor(Year), y = num_colonies)) +
geom_bar(aes(fill = year_color),
color = "#121212", ,
stat = "identity") +
labs(title = paste0(str_to_title(input$state_choice)),
subtitle = "Survey of bee colonies",
caption = "source: USDA NASS",
x = "year",
y = "bee colonies (in thousands)") +
scale_fill_manual(values = c("#121212", "#E77500")) +
theme_minimal() +
theme(legend.position = "none")
})
output$state_map <- renderPlot({
states_shp |>
mutate(this_selection = ifelse(
NAME == str_to_title(input$state_choice),
"focus", "other states")) |>
ggplot() +
geom_sf(aes(fill = this_selection)) +
labs(title = paste0(str_to_title(input$state_choice)),
caption = "Princeton Wintersession") +
scale_fill_manual(values = c(color_1, "gray80")) +
theme_minimal() +
theme(axis.title.x = element_blank(),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "none")
})
output$num_colonies <- renderText({
bee_data |>
filter(State == input$state_choice) |>
filter(Year == input$year_choice) |>
pull(Value)
})
output$state_choice <- renderText({input$state_choice})
output$year_choice <- renderText({input$year_choice})
Plots
Now we have have the output in place, we can use more shiny code to plot that output. Where the barchart code was intended to be, place the following
plotOutput("state_plot")
Do something similar for the geographic map.
Valueboxes
Now we have have the output in place, we can use more shiny code to display certain numbers. Inside the valueboxes’ divs, place the following
r textOutput("state_choice")
r textOutput("year_choice")
Do something similar for the number of bee colonies.
Additional Resources
For this workshop, I followed the materials presented by Mine Cetinkaya-Rundel and Posit
Folks that are interested in learning more about Quarto Dashboards can refer to resources such as the following:
How to Automate your Reporting by Isabella Velasquez
Easy Dashboards by The GRAPH Courses
Interactive Infographic using DisplayR by Melisaa Van Bussel
Quarto Dashboards by Charles Teague
Quarto Dashboards by Isabella Velasquez
How to Style your Quarto docs without HTML & CSS by Albert Rapp
Beyond Dashboards by Sean Nguyen