Integrating JavaScript Libraries into R for Dynamic Visualizations

Authors
Affiliations

Sam Csik

National Center for Ecological Analysis and Synthesis

An Bui

Department of Ecology, Evolution & Marine Biology, University of California, Santa Barbara

Published

May 14, 2023

What is this document?

The following code was written as reference material for the May 2023 workshop, Integrating JavaScript Libraries into R for Dynamic Visualizations, led by Sam Csik and An Bui as part of the SORTEE Workshop and Webinar Series.

0. Setting up

# import packages ----
library(tidyverse) # data wrangling & viz (with {ggplot2})
library(plotly) # JS plots!
library(DT) # JS tables!
library(leaflet) # JS maps!
library(leaflet.extras) # leaflet add-ons!

# reading in data ----
1lobs <- readRDS(file = here::here("data", "lobsters.rds"))
1
RDS (R Data Serialization) is a data file format commonly used for saving R objects. RDS files are relatively small, 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. We’ve done some pre-processing of the original/raw data and wrote it out as an .rds file (using saveRDS()) for us to use in this workshop.

1. Summarizing the data

# creating new data frame ----
lobs_summary <- lobs %>% 
  
  # calculate total lobster counts by protection status, site, & year (each point will represent lobster counts at a single site for each year from 2012-2018) ----
  group_by(protection_status, site, year) %>% 
  # count the total number of lobsters 
  summarize(n = sum(total_count))

2. plotly

a. create a static plot

static <- lobs_summary %>% 
  
  # create boxplot of mpa vs non-mpa lobster counts ----
  ggplot(aes(x = protection_status, y = n)) +
  
  # geoms: a boxplot and points with jitter ----
  geom_boxplot(width = 0.5, outlier.shape = NA) +
  geom_point(aes(color = site, shape = site), size = 4, alpha = 0.8, 
             # turn the points into a jitter (with a little more control than geom_jitter)
             position = position_jitter(width = 0.25, height = 0, seed = 1)) +
  
  # update colors and shapes ----
  scale_color_manual(values = c("NAPL" = "#91B38A", 
                                "IVEE" = "#9565CC", 
                                "AQUE" = "#CCC065", 
                                "MOHK" = "#658ACC", 
                                "CARP" = "#CC6565")) +
  scale_shape_manual(values = c(15, 25, 17, 18, 19)) +
  
  # update labels ----
  labs(x = "Protection Status",
       y = "Lobster Counts",
       color = "Site", 
       shape = "Site") + 
  
  # theme ----
  theme_linedraw() +
  theme(axis.text = element_text(size = 10),
        axis.title = element_text(size = 13),
        legend.text = element_text(size = 10),
        legend.title = element_text(size = 11))

# print plot ----
static

b. create an interactive plot

ggplotly(static) # ta-da!

c. create a better interactive plot

i. create a marker

# adding a column to lobs_summary ----
lobs_summary_marker <- lobs_summary %>% 
  
  # create a new column called "marker" ----
  mutate(marker = paste0("Site: ", site, "<br>",
                      "Year: ", year, "<br>",
                      "Status: ", protection_status, "<br>",
                      "Lobster count: ", n))

ii. make a new static plot with text = marker aesthetic argument

# creating a new static plot ----
static_with_marker <- lobs_summary_marker %>% 
  
  # create boxplot of mpa vs non-mpa lobster counts ----
  ggplot(aes(x = protection_status, y = n, text = marker, group = protection_status)) +
  
  # geoms: boxplot and jitter ----
  geom_boxplot(width = 0.5, outlier.shape = NA) +
  geom_point(aes(color = site, shape = site), size = 4, alpha = 0.8, 
             position = position_jitter(width = 0.25, height = 0, seed = 1)) +
  
  # update colors and shapes ----
  scale_color_manual(values = c("#91B38A", "#9565CC", "#CCC065", "#658ACC", "#CC6565")) +
  scale_shape_manual(values = c(15, 25, 17, 18, 19)) +
  
  # update labels ----
  labs(x = "Protection Status",
       y = "Lobster Counts",
       color = "Site", 
       shape = "Site") + 
  
  # theme ----
  theme_linedraw() +
  theme(axis.text = element_text(size = 10),
        axis.title = element_text(size = 13),
        legend.text = element_text(size = 10),
        legend.title = element_text(size = 11))

# printing the ggplot object will give you a scary warning - that's ok! ----
static_with_marker
Warning: The following aesthetics were dropped during statistical transformation: text
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?

iii. create plot with markers

# tooltip = "text" corresponds to the aes() text call from above!
lobs_interactive <- ggplotly(static_with_marker, tooltip = "text") %>% 
  
  # layout: most formatting goes here! ----
  layout(
    font = list(family = "Times"),
  
    # editing the marker/tooltip/hoverlabel 
    hoverlabel = list(
      # editing the font: all goes in a list()
      font = list(
        family = "Times",
        size = 13,
        color = "#FFFFFF",
        align = "left"
      )
    )
  )

# print plot ----
lobs_interactive

iv. doing things in plot_ly

plot_ly(
  
  # call the data ----
  lobs_summary_marker,
  
  # axes ----
  x = ~ protection_status,
  y = ~ n,
  
  # type: plot_ly equivalent of "geom" ----
  type = "box",
  
  # show underlying data ----
  boxpoints = "all",
  
  # center points on boxplot ----
  pointpos = 0,
  
  # control width of jitter ----
  jitter = 0.25,
  
  # tooltip ----
  hoverinfo = "text", 
  text = ~ marker,
  
  # colors ----
  color = ~ protection_status,
  colors = c("cornflowerblue", "darkgreen")) %>% 
  
  layout(
    
    # global font option ----
    font = list(family = "Times", 
                size = 14),
    
    # changing axis labels 
    xaxis = list(title = list(text = "Protection status")),
    yaxis = list(title = list(text = "Lobster count")),
    
    # editing the marker/tooltip/hoverlabel 
    hoverlabel = list(
      # editing the font: all goes in a list()
      font = list(
        family = "Times",
        size = 13,
        color = "#FFFFFF",
        align = "left"
      )
    )
  )

3. DT

a. create a basic interactive table

datatable(data = lobs)

b. customizing your DT

lobs_dt <- datatable(data = lobs, 
                     # make the column names nice ----
                     colnames = c("Year", "Date", "Site", "Protection status", "Transect", "Replicate", 
                                  "Size (mm)", "Count", "Latitude", "Longitude"),
                     
                     # column filters: sliders and drop down menus ----
                     filter = "top", 
                     
                     # extensions: lots of these! ----
                     extensions = c("Buttons", "ColReorder"),
                     
                     # options ----
                     options = list(
                       # list 10 entries at once
                       pageLength = 10, 
                       # automatically size columns
                       autoWidth = TRUE,
                       # highlight entries that match search term
                       searchHighlight = TRUE,
                       # allow regular expressions and case insensitive searches
                       search = list(regex = TRUE, caseInsensitive = TRUE),
                       dom = "Bfrtip",
                       # buttons options
                       buttons = c("copy", "csv", "excel", "pdf", "print", "colvis"),
                       # links to extension call
                       colReorder = TRUE)
) %>% 
  
  # styling cells: coloring site background ----
  formatStyle("site",
            
            # styleEqual allows matches to column contents ----
            backgroundColor = styleEqual(
              levels = list("NAPL", "IVEE", "AQUE", "MOHK", "CARP"),
              values = c("NAPL" = "#91B38A", 
                         "IVEE" = "#9565CC", 
                         "AQUE" = "#CCC065", 
                         "MOHK" = "#658ACC", 
                         "CARP" = "#CC6565")
            )
          )

# print table ----
lobs_dt

4. leaflet

a. some cleaning and filtering

# create df of unique sites ----
sites <- lobs %>% 
  select(site, protection_status, lat, lon) %>% 
  distinct()

# just mpa sites ----
mpa <- sites %>% 
  filter(protection_status == "MPA")

# just non-mpa sites ----
non_mpa <- sites %>% 
  filter(protection_status == "non-MPA")

b. create a custom icon

lobster_icon <- makeIcon(
  iconUrl = here::here("media", "lobster.png"),
  iconWidth = 30, iconHeight = 30
)

c. create a map!

# initialize map ----
site_map <- leaflet() %>% 
  
  # add base map tiles (use `addTiles()` for Google maps tiles, OR `addProviderTiles()` for 3rd party base maps: https://leaflet-extras.github.io/leaflet-providers/preview/) ----
  addProviderTiles(providers$Esri.WorldImagery, group = "ESRI World Imagery") %>% 
  addProviderTiles(providers$Esri.OceanBasemap, group = "ESRI Oceans") %>% 
  
  # add mini map ----
  addMiniMap(toggleDisplay = TRUE, minimized = TRUE) %>% 
  
  # set view over Santa Barbara Channel ----
  setView(lng =  -119.83, lat = 34.44, zoom = 9) %>% 
  
  # add MPA markers ----
  addMarkers(data = mpa, group = "MPA Sites",
             icon = lobster_icon,
             lng = ~lon, lat = ~lat,
             popup = paste("Site Name:", mpa$site, "<br>",
                           "Coordinates (lat/long):", mpa$lat, ",", mpa$lon)) %>%
  
  # add non-MPA markers ----
  addMarkers(data = non_mpa, group = "Non-MPA Sites",
             icon = lobster_icon,
             lng = ~lon, lat = ~lat,
             popup = paste("Site Name:", non_mpa$site, "<br>",
                           "Coorinates (lat/long):", non_mpa$lat, ",", non_mpa$lon)) %>%

  # add layers control (toggle base map tiles & markers based on group IDs) ----
  addLayersControl(
    baseGroups = c("ESRI World Imagery", "ESRI Oceans"),
    overlayGroups = c("MPA Sites", "Non-MPA Sites")) %>% 
  
  # add reset map button ----
  leaflet.extras::addResetMapButton()

# print map ----
site_map

4. Saving the html objects

htmltools::save_html(lobs_interactive, here::here("outputs", "plotly.html"))
htmltools::save_html(lobs_dt, here::here("outputs", "dt.html"))
htmltools::save_html(site_map, here::here("outputs", "leaflet.html"))

5. Prepare a report with your interactive visualizations!

Dynamic visualizations are particularly effective when embedded in reports – check out this fictitious report, created using both .qmd and .rmd files (for a side-by-side comparison):

While you may notice some feature and formatting differences between the two, both Quarto documents and R Markdown documents are effective tools for generating reproducible reports that combine prose, code, and outputs.