See next slide for a solution!
EDS 430: Part 3
Building Shiny dashboards
Building dashboards with {shinydashboard}
Learning Objectives - App #3 (shinydashboard)
After this section, you should:
understand the general workflow for pre-processing, saving & reading data into an app
be comfortable building out a dashboard UI using {shinydashboard}
layout functions
understand how to add static images to your app
feel comfortable creating a basic reactive leaflet
map
Packages introduced:
{shinydashboard}
: provides an alternative UI framework for easily building dashboard-style shiny applications
{leaflet}
: for building interactive maps
Roadmap for App #3
In this section, we’ll be building a shinydashboard using data downloaded from the Arctic Data Center. We’ll be building out the following features:
(a) a dashboardHeader
with the name of your app
(b) a dashboardSidebar
with two menuItem
s
(c) a landing page with background information about your app
(d) an interactive and reactive leaflet
map
But first, what do we mean by a shiny “dashboard”?
The {shinydashboard}
package provides additional UI layout functions that make building apps with a more classic “dashboard” feel a bit easier. You’ll always need to import {shiny}
alongside {shinydashboard}
(i.e. it’s not a full replacement for {shiny}
).
The most basic shinydashboard is made up of a header, a sidebar, and a body
The main difference between a shiny app and a shinydashboard are the UI elements. Rather than a fluidPage()
(as used in our previous shiny apps), we’ll create a dashboardPage()
, which expects three main parts: a header, a sidebar, and a body. Below is the most minimal possible UI for a {shinydashboard}
page (you can run this code in an app.R
file, if you wish).
#..............................setup.............................
library(shiny)
library(shinydashboard)
#...............................ui...............................
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(),
dashboardBody()
)
#.............................server.............................
server <- function(input, output) {}
#......................combine ui & server.......................
shinyApp(ui, server)
Example shiny dashboards built by some familiar folks
Live dashboards:
Bren Student Data Explorer (source code), by MEDS 2022 alum, Halina Do-Linh, during her Bren Summer Fellowship (and continued by future MEDS students!) – explore Bren school student demographics and career outcomes
Sam’s Strava Stats (source code), by yours truly, Sam Csik – a new and ongoing side project exploring my Strava hiking/biking/walking data
Visualizing human impacts on at-risk marine biodiversity (source code, developed by MESM 2022 alum, Ian Brunjes & Dr. Casey O’Hara) – explore how human activities and climate change impact marine biodiversity worldwide
Dashboards which are no longer deployed:
Channel Islands National Park’s Kelp Forest Monitoring Program (source code), by MEDS 2022 alum, Cullen Molitor – explore subtidal monitoring data collected from our closest National Park
The Outdoor Equity App (source code), developed by MEDS 2022 alumni Halina Do-Linh & Clarissa Boyajian as part of their MEDS capstone project – analyze patterns in the access and demand of visitors at reservable overnight sites
Setup your shiny dashboard
First, create a subdirectory called shinydashboard/
and add a ui.R
, server.R
, and global.R
file.
Add the server function to server.R
and the three main UI components (header, sidebar, and body) to our dashboard page – I typically recommend splitting the UI into separate pieces, then combining them into a dashboardPage
the end of ui.R
(as shown below) – this can help with organization as you app grows in complexity.
We’ll set our dashboard aside for now while we work on downloading and pre-processing our data, as well as practice creating our data visualization outside of our app.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader()
#........................dashboardSidebar........................
sidebar <- dashboardSidebar()
#..........................dashboardBody.........................
body <- dashboardBody()
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Be sure to run your app after each new addition!
From here on out, you should be running your app after each new addition to check that things work as expected – aim to do so after each slide where code is added.
I don’t explicitly call to do so on each slide, but this is a super important practice to get into!
Remember to give yourself proper space between lines and add annotations at the start and end of each parentheses – I highly recommend following along with the formatting I use throughout these materials.
As always, let’s start with the data
Building an app doesn’t make much sense if we don’t know what we’re going to put in it. So, just like the last two apps, we’ll start with some data wrangling and practice data visualization.
Unlike our last two apps, however, we’ll be working with tabular data from the Arctic Data Center, which we’ll download, process, save, then finally, read into our application. This process will likely be more similar to what you’ll encounter when working on your own applications moving forward. Take a few minutes to review the metadata record for the following data set, and download FCWO_lakemonitoringdata_2011_2022_daily.csv:
Christopher Arp, Matthew Whitman, Katie Drew, and Allen Bondurant. 2022. Water depth, surface elevation, and water temperature of lakes in the Fish Creek Watershed in northern Alaska, USA, 2011-2022. Arctic Data Center. doi:10.18739/A2JH3D41P.
Pre-processing data is critical
Where you choose to store the data used by your Shiny app will depend largely on the type and size of the file(s) and who “owns” those data. It is likely that you’ll be working with data stored in a database or on a server. This is outside the scope of this workshop, but I suggest reading Nathan Stephens’ article, Where to store your Shiny application data to start. Because we are going to be working with a relatively small data set today, we’ll bundle our data file with our dashboard inside our repository.
Regardless of where you choose to store your data, you can help your application more quickly process inputs / outputs by providing it only as much data as needed to run. This means pre-processing your data.
FCWO_lakemonitoringdata_2011_2022_daily.csv
contains 8 attributes (variables) and 18,994 observations collected from a set of 11 lakes located in the Fish Creek Watershed in northern Alaska between 2011-2022. We’ll download and save the file to a raw_data/
folder in the root directory of our repository. We’ll then pre-process the data in a separate script(s) saved to scratch/
and save a cleaned / processed version of the data to our app’s directory, /shinydashboard/data/lake_data_processed.csv
. Your repository structure should look similar to example on the right:
The goal:
Our goal is to create a leaflet
map with makers placed on each of the 11 unique lakes where data were collected. When clicked, a marker should reveal the lake name, elevation (in meters, above sea level), average depth of the lake (in meters), and average lake bed temperature (in degrees Celsius). To do so, we’ll need a data frame that looks like the example below:
Process lake data & save new file
~/scratch/data_processing_app3_shinydashboard.R
#...............................................................................
# .
# For simplicity, I've removed all rows with missing values (i.e. `NaN`s .
# in the `Depth` column & `NA`s in the `BedTemperature` column) before .
# calculating averages. However, exploring and thinking critically about .
# missing data is an important part of data analysis, and in a real-life .
# scenario, you should consider the most appropriate method for handling them .
# .
#...............................................................................
#....................SETUP & DATA PROCESSING.....................
# load packages ----
library(tidyverse)
# read in raw data ----
lake_raw <- read_csv(here::here("raw_data", "FCWO_lakemonitoringdata_2011_2022_daily.csv"))
# calculate avg depth & temp ----
avg_depth_temp <- lake_raw |>
select(Site, Depth, BedTemperature) |>
filter(Depth != "NaN") |> # remove NaN ("not a number") from Depth
drop_na(BedTemperature) |> # remove NAs (missing data) from BedTemperature
group_by(Site) |>
summarize(
AvgDepth = round(mean(Depth), 1),
AvgTemp = round(mean(BedTemperature), 1)
)
# join avg depth & temp to original data (match rows based on 'Site') ---
joined_dfs <- full_join(lake_raw, avg_depth_temp)
# get unique lakes observations (with corresponding lat, lon, elev, avgDepth, avgTemp) for mapping ----
unique_lakes <- joined_dfs |>
select(Site, Latitude, Longitude, Elevation, AvgDepth, AvgTemp) |>
distinct()
# save processed data to your app's data directory ----
write_csv(x = unique_lakes, file = here::here("shinydashboard", "data", "lake_data_processed.csv"))
A note on file types
Oftentimes, you may choose to save your processed data frame as a .rds
file (a data file format, native to R, which stores a single R object). .rds
file are relatively small (and therefore take up little storage space), take less time to import / export, and preserve data types and classes (e.g. factors and dates), eliminating the need to redefine data types after loading the file. Bear in mind that this increased speed and space-saving may come at the cost of generality – you can’t open a .rds
file outside of R or read it in with another programming language (e.g. Python).
While we’ll be sticking to .csv
files in this workshop, it’s worth experimenting with .rds
when you begin working with your own (likely larger) data. You can read in (readRDS()
) and write out to (saveRDS()
) .rds
files as easily as .csv
files.
Draft leaflet
map
There are lots of ways to customize leaflet
maps. We’ll be keeping ours relatively simple, but check out the Leaflet for R documentation for more ways to get creative with your maps.
~/scratch/practice_script_app3_shinydashboard.R
#..............................SETUP.............................
# load packages ----
library(tidyverse)
library(leaflet)
# read in data ----
lake_data <- read_csv(here::here("shinydashboard", "data", "lake_data_processed.csv"))
#..........................PRACTICE VIZ..........................
leaflet() |>
# add tiles
addProviderTiles(providers$Esri.WorldImagery) |>
# set view over AK
setView(lng = -152.048442, lat = 70.249234, zoom = 6) |>
# add mini map
addMiniMap(toggleDisplay = TRUE, minimized = FALSE) |>
# add markers
addMarkers(data = lake_data,
lng = lake_data$Longitude, lat = lake_data$Latitude,
popup = paste0("Site Name: ", lake_data$Site, "<br>",
"Elevation: ", lake_data$Elevation, " meters (above SL)", "<br>",
"Avg Depth: ", lake_data$AvgDepth, " meters", "<br>",
"Avg Lake Bed Temperature: ", lake_data$AvgTemp, "\u00B0C")) # NOTE: Unicode for degree symbol icon
Practice filtering leaflet
observations
We’ll eventually build three sliderInput
s to filter lake makers by Elevation
, AvgDepth
, and AvgTemp
. Practice filtering here first (and be sure to update the data frame name in your leaflet code!):
~/scratch/practice_script_app3_shinydashboard.R
#..............................SETUP.............................
# load packages ----
library(tidyverse)
library(leaflet)
# read in data ----
lake_data <- read_csv(here::here("shinydashboard", "data", "lake_data_processed.csv"))
#.......................PRACTICE FILTERING.......................
filtered_lakes <- lake_data |>
filter(Elevation >= 8 & Elevation <= 20) |>
filter(AvgDepth >= 2 & AvgDepth <= 3) |>
filter(AvgTemp >= 4 & AvgTemp <= 6)
#..........................PRACTICE VIZ..........................
leaflet() |>
# add tiles
addProviderTiles(providers$Esri.WorldImagery, # make note of using appropriate tiles
options = providerTileOptions(maxNativeZoom = 19, maxZoom = 100)) |>
# add mini map
addMiniMap(toggleDisplay = TRUE, minimized = TRUE) |>
# set view over AK
setView(lng = -152.048442, lat = 70.249234, zoom = 6) |>
# add markers
addMarkers(data = filtered_lakes,
lng = filtered_lakes$Longitude, lat = filtered_lakes$Latitude,
popup = paste0("Site Name: ", filtered_lakes$Site, "<br>",
"Elevation: ", filtered_lakes$Elevation, " meters (above SL)", "<br>",
"Avg Depth: ", filtered_lakes$AvgDepth, " meters", "<br>",
"Avg Lake Bed Temperature: ", filtered_lakes$AvgTemp, "\u00B0C")) # NOTE: Unicode for degree symbol icon
Sketch out our dashboard UI
I want my dashboard to have two menu items: a welcome page with some background information, and a dashboard page with my reactive map. All elements will be placed inside box
es, the primary building blocks of shinydashboards (more on that soon).
Add a title
& menuItem
s
First, add a title to dashboardHeader()
and make more space using titleWidth
, if necessary.
Next, we’ll build our dashboardSidebar()
. Add a sidebarMenu()
that contains two menuItem
s. Be sure to provide each menuItem()
with text
as you’d like it to appear in your app (for me, that’s Welcome and Dashboard), and a tabName
which will be used to place dashboardBody()
content in the appropriate menuItem()
. Optionally, you can provide an icon. By default, icon()
uses icons from FontAwesome.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody()
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Add tabItems
to your dashboardBody
Next, we’ll create tabItems
in our dashboardBody
– we’ll make a tabItem
(singular) for each menuItem
in our dashboardSidebar
. In order to match a menuItem
and a tabItem
, ensure that they have matching a tabName
(e.g. any content added to the dashboard tabItem
will appear under the dashboard menuItem
).
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
"background info here"
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
"dashboard content here"
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Add box
es to contain UI content (part 1)
Boxes are the primary building blocks of shinydashboards and can contain almost any Shiny UI element (e.g. text, inputs, outputs). Start by adding two side-by-side boxes to our dashboard tab inside a fluidRow()
. Together, their widths will add up to 12 (the total width of a browser page). These boxes will eventually contain our sliderInput
s and our leafletOutput
.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
"background info here"
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
"sliderInputs here"
), # END input box
# leaflet box ----
box(width = 8,
"leafletOutput here"
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Add box
es to contain UI content (part 2)
Lastly, add boxes to our welcome tab We’ll use column
s to place one box on the left-hand side of our page, and two stacked boxes on the right-hand side. Each column
will take up half the page (Note: For column-based layouts, use NULL
for the box width, as the width is set by the column that contains the box). We can create two fluidRow
s within the right-hand column to stack two boxes vertically.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background info box ----
box(width = NULL,
"background info here"
), # END background info box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
"data citation here"
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
"disclaimer here"
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
"sliderInputs here"
), # END input box
# leaflet box ----
box(width = 8,
"leaflet output here"
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Read data into global.R
& add necessary packages
Remember to load your pre-processed data, which should live in the data/
folder within your app’s directory.
Some important notes:
here::here()
in your shiny apps, as it can cause some unexpected issues (read more about it in this discussion)ui.R
, server.R
). It’s important to note, however, that you won’t be able to run your code line-by-line (like in a typical script; the file path won’t be recognized) – this is expected!.Add a sliderInput
& leafletOutput
to the UI
Start by adding just one sliderInput
(for selecting a range of lake Elevation
s) to the left-hand box in the dashboard tab. Then, add a leafletOutput
to create a placeholder space for our map, along with a Spinner animation (from the {shinycssloaders}
package). While we’re here, we can also add title
s to each box.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
"background info here"
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
"data citation here"
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
"disclaimer here"
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider_input", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map_output") |>
withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Assemble inputs & outputs in server.R
Remember to reference your practice data viz script and to follow our three steps for creating reactive outputs. And don’t forget to add ()
following each reactive data frame called in your leaflet map!
~/shinydashboard/server.R
server <- function(input, output) {
# filter lake data ----
filtered_lakes_df <- reactive ({
lake_data |>
filter(Elevation >= input$elevation_slider_input[1] & Elevation <= input$elevation_slider_input[2])
})
# build leaflet map ----
output$lake_map_output <- renderLeaflet({
leaflet() |>
# add tiles
addProviderTiles(providers$Esri.WorldImagery) |>
# set view over AK
setView(lng = -152.048442, lat = 70.249234, zoom = 6) |>
# add mini map
addMiniMap(toggleDisplay = TRUE, minimized = TRUE) |>
# add markers
addMarkers(data = filtered_lakes_df(),
lng = filtered_lakes_df()$Longitude, lat = filtered_lakes_df()$Latitude,
popup = paste("Site Name:", filtered_lakes_df()$Site, "<br>",
"Elevation:", filtered_lakes_df()$Elevation, "meters (above SL)", "<br>",
"Avg Depth:", filtered_lakes_df()$AvgDepth, "meters", "<br>",
"Avg Lake Bed Temperature:", filtered_lakes_df()$AvgTemp, "deg Celsius"))
})
}
Run your app & test out your first widget
If all is good, you should see something similar to this:
Exercise 5: Add two more sliderInput
s to filter for AvgDepth
& AvgTemp
To Do:
Add two more sliderInput
s, one for AvgDepth
and one for AvgTemp
beneath our first Elevation sliderInput
in the UI
Update our reactive data frame so that all three widgets filter the leaflet map
See next slide for a solution!
Exercise 5: A solution
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
"background info here"
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
"data citation here"
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
"disclaimer here"
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider_input", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation))),
sliderInput(inputId = "depth_slider_input", label = "Average depth (meters):",
min = min(lake_data$AvgDepth), max = max(lake_data$AvgDepth),
value = c(min(lake_data$AvgDepth), max(lake_data$AvgDepth))),
sliderInput(inputId = "temp_slider_input", label = "Average lake bed temperature (degrees C):",
min = min(lake_data$AvgTemp), max = max(lake_data$AvgTemp),
value = c(min(lake_data$AvgTemp), max(lake_data$AvgTemp)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map_output") |>
withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
~/shinydashboard/server.R
server <- function(input, output) {
# filter lake data ----
filtered_lakes_df <- reactive ({
lake_data |>
filter(Elevation >= input$elevation_slider_input[1] & Elevation <= input$elevation_slider_input[2]) |>
filter(AvgDepth >= input$depth_slider_input[1] & AvgDepth <= input$depth_slider_input[2]) |>
filter(AvgTemp >= input$temp_slider_input[1] & AvgTemp <= input$temp_slider_input[2])
})
# build leaflet map ----
output$lake_map_output <- renderLeaflet({
leaflet() |>
# add tiles
addProviderTiles(providers$Esri.WorldImagery) |>
# set view over AK
setView(lng = -152.048442, lat = 70.249234, zoom = 6) |>
# add mini map
addMiniMap(toggleDisplay = TRUE, minimized = TRUE) |>
# add markers
addMarkers(data = filtered_lakes_df(),
lng = filtered_lakes_df()$Longitude, lat = filtered_lakes_df()$Latitude,
popup = paste("Site Name:", filtered_lakes_df()$Site, "<br>",
"Elevation:", filtered_lakes_df()$Elevation, "meters (above SL)", "<br>",
"Avg Depth:", filtered_lakes_df()$AvgDepth, "meters", "<br>",
"Avg Lake Bed Temperature:", filtered_lakes_df()$AvgTemp, "deg Celsius"))
})
}
& Exercise 6: Add titles & text to Welcome page boxes
To Do:
Add titles to each box on the Welcome page
Create a text/
folder within your app’s directory and add three markdown (.md
) files. Write / format text for the background info (left), data citation (top-right), and disclaimer (bottom-right) boxes. Example text below:
~/shinydashboard/text/intro.md
The [Fish Creek Watershed Observatory (FCWO)](http://www.fishcreekwatershed.org/) is a focal watershed within the [National Petroleum Reserve in Alaska (NPR-A)](https://www.blm.gov/programs/energy-and-minerals/oil-and-gas/about/alaska/NPR-A). Targeted lake and stream monitoring within the watershed provide site-specific data prior to and after the establishment of new petroleum development, as well as insight into dynamics related to climate change and variability. Eleven lakes of interest (Harry Potter, Hipbone, Iceshove, L9817, L9819, L9820, Little Alaska, Lower Snowman, M9925, Middle Snowman, and Serenity) are featured in this dashboard.
~/shinydashboard/text/citation.md
Data presented in this dashboard were collected as part of the [Fish Creek Watershed Observatory](http://www.fishcreekwatershed.org/) are archived and publicly accessible on the NSF [Arctic Data Center](https://arcticdata.io/). **Citation:**
*Christopher Arp, Matthew Whitman, Katie Drew, and Allen Bondurant. 2022. Water depth, surface elevation, and water temperature of lakes in the Fish Creek Watershed in northern Alaska, USA, 2011-2022. Arctic Data Center [doi:10.18739/A2JH3D41P](https://arcticdata.io/catalog/view/doi%3A10.18739%2FA2JH3D41P).*
Tips:
Titles can include icons! For example: title = tagList(icon("icon-name"), strong("title text here"))
Exercise 6: A solution
Press the right arrow key to advance through the newly added lines of code.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
title = tagList(icon("water"), strong("Monitoring Fish Creek Watershed")),
includeMarkdown("text/intro.md")
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
title = tagList(icon("table"), strong("Data Source")),
includeMarkdown("text/citation.md")
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
title = tagList(icon("triangle-exclamation"), strong("Disclaimer")),
includeMarkdown("text/disclaimer.md")
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation))),
sliderInput(inputId = "depth_slider", label = "Average depth (meters):",
min = min(lake_data$AvgDepth), max = max(lake_data$AvgDepth),
value = c(min(lake_data$AvgDepth), max(lake_data$AvgDepth))),
sliderInput(inputId = "temp_slider", label = "Average lake bed temperature (degrees C):",
min = min(lake_data$AvgTemp), max = max(lake_data$AvgTemp),
value = c(min(lake_data$AvgTemp), max(lake_data$AvgTemp)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map") |> withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Add a static image
As a final touch, let’s add an image to the Welcome page, inside the left-hand box beneath our intro text. First, create a www/
folder inside your app’s directory (refer back to lecture 1.2 for a description of this special directory). Download the map of the Fish Creek Watershed from FCWO’s website here and save it to your www/
directory.
Next, use the img
tag to add your image. Supply a file path, relative to your www/
directory, using the src
argument, and add alt text using the alt
argument.
~/shinydashboard/ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
title = tagList(icon("water"), strong("Monitoring Fish Creek Watershed")),
includeMarkdown("text/intro.md"),
tags$img(src = "FishCreekWatershedSiteMap_2020.jpeg",
alt = "A map of Northern Alaska, showing Fish Creek Watershed located within the National Petroleum Reserve.")
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
title = tagList(icon("table"), strong("Data Source")),
includeMarkdown("text/citation.md")
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
title = tagList(icon("triangle-exclamation"), strong("Disclaimer")),
includeMarkdown("text/disclaimer.md")
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation))),
sliderInput(inputId = "depth_slider", label = "Average depth (meters):",
min = min(lake_data$AvgDepth), max = max(lake_data$AvgDepth),
value = c(min(lake_data$AvgDepth), max(lake_data$AvgDepth))),
sliderInput(inputId = "temp_slider", label = "Average lake bed temperature (degrees C):",
min = min(lake_data$AvgTemp), max = max(lake_data$AvgTemp),
value = c(min(lake_data$AvgTemp), max(lake_data$AvgTemp)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map") |> withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Our image doesn’t look so great as-is…
Use in-line CSS to adjust the image size
We can use in-line CSS to style our image element, as shown below (see style
argument). It’s okay if you don’t fully understand what’s going on here for now – we’ll talk in greater detail about how CSS (and Sass) can be used to customize the appearance of your apps in just a bit.
I’ve also added a caption below our image that links to the image source, and used in-line CSS to center my text within the box.
ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
title = tagList(icon("water"), strong("Monitoring Fish Creek Watershed")),
includeMarkdown("text/intro.md"),
tags$img(src = "FishCreekWatershedSiteMap_2020.jpeg",
alt = "A map of Northern Alaska, showing Fish Creek Watershed located within the National Petroleum Reserve.",
style = "max-width: 100%;"),
tags$h6(tags$em("Map Source:", tags$a(href = "http://www.fishcreekwatershed.org/", "FCWO")),
style = "text-align: center;")
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
title = tagList(icon("table"), strong("Data Source")),
includeMarkdown("text/citation.md")
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
title = tagList(icon("triangle-exclamation"), strong("Disclaimer")),
includeMarkdown("text/disclaimer.md")
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation))),
sliderInput(inputId = "depth_slider", label = "Average depth (meters):",
min = min(lake_data$AvgDepth), max = max(lake_data$AvgDepth),
value = c(min(lake_data$AvgDepth), max(lake_data$AvgDepth))),
sliderInput(inputId = "temp_slider", label = "Average lake bed temperature (degrees C):",
min = min(lake_data$AvgTemp), max = max(lake_data$AvgTemp),
value = c(min(lake_data$AvgTemp), max(lake_data$AvgTemp)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map") |> withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
Check out your finished dashboard!
There’s a ton more to learn about building shinydashboards. Check out the documentation to find instructions on adding components like infoBox
es and valueBox
es, building inputs in the sidebar, easy ways to update the color theme using skins, and more.
Complete code for our dashboard thus far:
ui.R
#........................dashboardHeader.........................
header <- dashboardHeader(
# add title ----
title = "Fish Creek Watershed Lake Monitoring",
titleWidth = 400
) # END dashboardHeader
#........................dashboardSidebar........................
sidebar <- dashboardSidebar(
# sidebarMenu ----
sidebarMenu(
menuItem(text = "Welcome", tabName = "welcome", icon = icon("star")),
menuItem(text = "Dashboard", tabName = "dashboard", icon = icon("gauge"))
) # END sidebarMenu
) # END dashboardSidebar
#..........................dashboardBody.........................
body <- dashboardBody(
# tabItems ----
tabItems(
# welcome tabItem ----
tabItem(tabName = "welcome",
# left-hand column ----
column(width = 6,
# background box ----
box(width = NULL,
title = tagList(icon("water"), strong("Monitoring Fish Creek Watershed")),
includeMarkdown("text/intro.md"),
tags$img(src = "FishCreekWatershedSiteMap_2020.jpeg",
alt = "A map of Northern Alaska, showing Fish Creek Watershed located within the National Petroleum Reserve.",
style = "max-width: 100%;"),
tags$h6(tags$em("Map Source:", tags$a(href = "http://www.fishcreekwatershed.org/", "FCWO")),
style = "text-align: center;")
) # END background box
), # END left-hand column
# right-hand column ----
column(width = 6,
# first fluidRow ----
fluidRow(
# data source box ----
box(width = NULL,
title = tagList(icon("table"), strong("Data Source")),
includeMarkdown("text/citation.md")
) # END data source box
), # END first fluidRow
# second fluidRow ----
fluidRow(
# disclaimer box ----
box(width = NULL,
title = tagList(icon("triangle-exclamation"), strong("Disclaimer")),
includeMarkdown("text/disclaimer.md")
) # END disclaimer box
) # END second fluidRow
) # END right-hand column
), # END welcome tabItem
# dashboard tabItem ----
tabItem(tabName = "dashboard",
# fluidRow ----
fluidRow(
# input box ----
box(width = 4,
title = tags$strong("Adjust lake parameter ranges:"),
# sliderInputs ----
sliderInput(inputId = "elevation_slider_input", label = "Elevation (meters above SL):",
min = min(lake_data$Elevation), max = max(lake_data$Elevation),
value = c(min(lake_data$Elevation), max(lake_data$Elevation))),
sliderInput(inputId = "depth_slider_input", label = "Average depth (meters):",
min = min(lake_data$AvgDepth), max = max(lake_data$AvgDepth),
value = c(min(lake_data$AvgDepth), max(lake_data$AvgDepth))),
sliderInput(inputId = "temp_slider_input", label = "Average lake bed temperature (degrees C):",
min = min(lake_data$AvgTemp), max = max(lake_data$AvgTemp),
value = c(min(lake_data$AvgTemp), max(lake_data$AvgTemp)))
), # END input box
# leaflet box ----
box(width = 8,
title = tags$strong("Monitored lakes within Fish Creek Watershed:"),
# leaflet output ----
leafletOutput(outputId = "lake_map_output") |>
withSpinner(type = 1, color = "#4287f5")
) # END leaflet box
) # END fluidRow
) # END dashboard tabItem
) # END tabItems
) # END dashboardBody
#..................combine all in dashboardPage..................
dashboardPage(header, sidebar, body)
server.R
server <- function(input, output) {
# filter lake data ----
filtered_lakes_df <- reactive ({
lake_data |>
filter(Elevation >= input$elevation_slider_input[1] & Elevation <= input$elevation_slider_input[2]) |>
filter(AvgDepth >= input$depth_slider_input[1] & AvgDepth <= input$depth_slider_input[2]) |>
filter(AvgTemp >= input$temp_slider_input[1] & AvgTemp <= input$temp_slider_input[2])
})
# build leaflet map ----
output$lake_map_output <- renderLeaflet({
leaflet() |>
# add tiles
addProviderTiles(providers$Esri.WorldImagery) |>
# set view over AK
setView(lng = -152.048442, lat = 70.249234, zoom = 6) |>
# add mini map
addMiniMap(toggleDisplay = TRUE, minimized = TRUE) |>
# add markers
addMarkers(data = filtered_lakes_df(),
lng = filtered_lakes_df()$Longitude, lat = filtered_lakes_df()$Latitude,
popup = paste("Site Name:", filtered_lakes_df()$Site, "<br>",
"Elevation:", filtered_lakes_df()$Elevation, "meters (above SL)", "<br>",
"Avg Depth:", filtered_lakes_df()$AvgDepth, "meters", "<br>",
"Avg Lake Bed Temperature:", filtered_lakes_df()$AvgTemp, "deg Celsius"))
})
}
End part 3
05:00