Skip to content
Snippets Groups Projects
app.R 13.77 KiB
# This is a Shiny web application.
# Dashboard to visualize data presented in review paper Asmi et al. (2025).
# See README for more details.

library(shiny)
library(shinythemes)
library(bslib)
library(ggiraph)
library(ggsci)
library(ggiraphExtra)
library(stringr)
library(reshape2)
library(scales)
library(tidyverse)
library(DT)
library(gdtools)
# library(systemfonts)
# library(hrbrthemes)

register_gfont("IBM Plex Sans")

# Define UI for application
ui <- page_sidebar(
  title = "⚫ Atmospheric black carbon MAC review",
  theme = bs_theme(bootswatch = "minty"),
  addGFontHtmlDependency(family = c("IBM Plex Sans")),
  # tags$head(tags$style(type="text/css", "text {font-family: sans-serif}")),
  
  sidebar = sidebar(
    h4("Chart settings"),
    radioButtons("plot_category", "Categories", 
      c("Aerosol Type" = "Aerosol_type",
        "Absorption measurement" = "Abs_meas", 
        "Mass measurement" = "Mass_meas"),
      selected = "Aerosol_type"
    ),
    p("For more details and a reference to this review study, see the About section."),
    tags$a(
      href="https://stanbc.com", 
      tags$img(src="stanBC_logo_square_transp.png", 
               width="100%",
               )
    )
  ),
  
  navset_card_underline(
    # title = "Title",
    nav_panel("List of studies", girafeOutput("StudyPlot")),
    nav_panel("MAC vs. λ", girafeOutput("LambdaPlot")),
    nav_panel("Table", DT::dataTableOutput('MACtable')),
    nav_panel("About", uiOutput("README"))
  )
)

# Define server logic required to output plots and tables
server <- function(input, output) {
  
  register_gfont("IBM Plex Sans")
  
# Data processing
  MAC_data <- read_delim("data/MAC_table.csv", ",", skip = 1,
                         col_names = c("MAC_m2g", "MAC_sd", "MAC_method", "Wavelength_nm",
                                       "AAE", "Season", "Continent", "Time_period",
                                       "Place", "Aerosol_source", "Aerosol_type",
                                       "Mass_ref", "Abs_ref", "TD", "Thermal_protocol",
                                       "Abs_correction", "C_factor", "Other_info",
                                       "Reference_study", "id", "duration_code"))
  

  
  # Reduce the amount of aerosol types
  MAC_data$Aerosol_type_red <- ifelse(MAC_data$Aerosol_type %in% c("arctic",
                                                                   "remote",
                                                                   "regional background"), 
                                      "remote & regional background", MAC_data$Aerosol_type)
  
  MAC_data$Aerosol_type_red <- ifelse(MAC_data$Aerosol_type %in% c("polluted", 
                                                                   "urban", 
                                                                   "suburban", 
                                                                   "urban background",
                                                                   "traffic"),
                                      "urban", MAC_data$Aerosol_type_red)
  
  
  MAC_data$Aerosol_type_red <- ifelse(MAC_data$Aerosol_type %in% c("outflow", 
                                                                   "high-altitude" 
  ),
  "high altitude & outflow", MAC_data$Aerosol_type_red)
  
  MAC_data$Aerosol_type_red <- ifelse(MAC_data$Aerosol_type %in% c("rural", 
                                                                   "mixed" 
  ),
  "rural & mixed", MAC_data$Aerosol_type_red)
  
  
  
  # Reduce the amount of abs reference options
  MAC_data$Abs_ref_red <- NA
  MAC_data$Abs_ref_red <- ifelse(MAC_data$Abs_ref %in% c("AE", "AE16", "AE21",
                                                         "AE22", "AE31", "AE31, AE33",
                                                         "AE33", "AE42", "ATN",
                                                         "MAAP", "PSAP", "PSAP,CLAP",
                                                         "CLAP"), 
                                 "Filter-based", MAC_data$Abs_ref_red)
  
  MAC_data$Abs_ref_red <- ifelse(MAC_data$Abs_ref %in% c("PA", "PAS"), 
                                 "Photoacoustic", MAC_data$Abs_ref_red)
  
  # Reduce the amount of mass reference options
  MAC_data$Mass_ref_red <- NA
  MAC_data$Mass_ref_red <- ifelse(MAC_data$Mass_ref %in% c("SP2", "NRC-LII"), 
                                  "SP2", MAC_data$Mass_ref_red)
  MAC_data$Mass_ref_red <- ifelse(MAC_data$Mass_ref %in% c("Raman Spectroscopy"), 
                                  NA, MAC_data$Mass_ref_red)
  MAC_data$Mass_ref_red <- ifelse(MAC_data$Mass_ref %in% c("EC"), 
                                  "EC", MAC_data$Mass_ref_red)
  MAC_data$Mass_ref_red <- ifelse(MAC_data$Mass_ref %in% c("SP2-AMS"), 
                                  "SP2-AMS", MAC_data$Mass_ref_red)
  
  # Calculate MAC at 550 nm
  MAC_data <- MAC_data |> 
    mutate(
      MAC_m2g_550 = MAC_m2g * (550/Wavelength_nm)^(-1), # Calculate abs coeff for 550 nm
      MAC_sd_550 = MAC_sd * (550/Wavelength_nm)^(-1), # Calculate MAC SD for 550 nm
      MAC_sd_550_durn = ifelse(duration_code > 2, MAC_sd * (550/Wavelength_nm)^(-1), NA), # Same as above but only for studies with duration of months and years
      Eabs = MAC_m2g_550 / 8.0, # Calculate abs enhancement compared to the standard abs coeff at 550 nm (8.0 m2g-1)
      Study_durn = case_when(
        duration_code == 1 ~ paste("Days"), # Adds extra character to the study name according to duration
        duration_code == 2 ~ paste("Weeks"),
        duration_code == 3 ~ paste("Months"),
        duration_code == 4 ~ paste("Years"),
        
      )
    )
  
# Plot MAC550 as list of studies 
    output$StudyPlot <- renderGirafe({
      # Plot MAC 550 using facet for aerosol type
      MAC_MAC550 <- ggplot(MAC_data, aes(x = Reference_study, y = MAC_m2g_550))
      
      MAC_MAC550.gg <- MAC_MAC550 + 
        # theme_ipsum(base_family = "IBM Plex Sans") +
        theme_minimal(base_family = "IBM Plex Sans") +
        theme(axis.text.y = element_text(size = 10),
              axis.text.x = element_text(size = 12),
              axis.title.x = element_text(size=16),
              axis.title.y = element_blank(),
              panel.grid.major.y = element_blank(),
              panel.grid.minor.y = element_blank(),
              strip.text.y = element_text(angle = 0, size = 16),
              strip.background = element_rect(colour="#FFFFFF", fill="#f7f7f9"),
              legend.background = element_rect(fill = "#f7f7f9", colour = "#FFFFFF"),
              legend.text = element_text(size = 12),
              legend.title=element_blank()
        ) +
        xlab("Study") + 
        ylab(expression(paste(italic(E)[abs], " • ", MAC[550], "  [m²/g]"))) + 
        scale_color_manual(values = c("#0072B5", "#7cb518", "#ffb703", "#e63946")) +
        coord_flip() +
        scale_x_discrete(limits=rev) +
        geom_rect(aes(ymin=7, ymax=9, xmin=0, xmax=Inf), fill  = "#EEEEEE") + 
        geom_hline(yintercept = 8, linetype = 2, linewidth = 1) +
        geom_linerange_interactive(aes(ymin = MAC_m2g_550 - MAC_sd_550,
                                       ymax =MAC_m2g_550 + MAC_sd_550,
                                       colour = case_when(
                                         input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
                                         input$plot_category == "Abs_meas" ~ Abs_ref_red,
                                         input$plot_category == "Mass_meas" ~ Mass_ref_red
                                       ),
                                       data_id = case_when(
                                         input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
                                         input$plot_category == "Abs_meas" ~ Abs_ref_red,
                                         input$plot_category == "Mass_meas" ~ Mass_ref_red
                                       )),
                                   linewidth=4, alpha = 0.15) +
        geom_text(family = "Arial", size = 9,label = "\u2727", 
                  colour = "#FFFFFF", show.legend = FALSE, 
                  position = position_nudge(x = 0.1)) + # Simulation of stroke line
        geom_text(family = "Arial", size = 6,label = "\u2727", 
                  colour = "#777777", show.legend = FALSE, 
                  position = position_nudge(x = 0.1)) + # Simulation of stroke line
        geom_text_interactive(aes(colour = case_when(
                                    input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
                                    input$plot_category == "Abs_meas" ~ Abs_ref_red,
                                    input$plot_category == "Mass_meas" ~ Mass_ref_red
                                  ),
                                  data_id = case_when(
                                    input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
                                    # input$plot_category == "Abs_meas" ~ Abs_ref_red,
                                    input$plot_category == "Mass_meas" ~ Mass_ref_red
                                  ),
                                  tooltip = paste(Reference_study, "\n", round(MAC_m2g_550, digits = 2), " m²/g")),
                              family = "Arial", size = 6, label = "\u2726",
                              alpha = 0.7, show.legend = TRUE,
                              position = position_nudge(x = 0.1)) + # Fill character in front
        facet_grid(Study_durn ~ ., scales = "free", space = "free")
      
      girafe(ggobj = MAC_MAC550.gg, width_svg = 14, height_svg = 10,
             # fonts = list(sans = "Arial"),
             options = list(
               opts_hover(css = "fill:grey80;"),
               opts_hover_inv(css = "opacity:0.1;"),
               opts_selection(type = "single", only_shiny = FALSE)
             ))
      
      
    })
    
  
    # Plot MAC as function of wavelength
    output$LambdaPlot <- renderGirafe({
      # Plot MAC 550 using facet for aerosol type
      MAC_lambda <- ggplot(MAC_data, aes(x = Wavelength_nm, y = MAC_m2g))
      
      MAC_lambda.gg <- MAC_lambda + 
        # theme_ipsum(base_family = "IBM Plex Sans") +
        theme_minimal(base_family = "IBM Plex Sans") +
        theme(axis.text.y = element_text(size = 10),
              axis.text.x = element_text(size = 12),
              axis.title.x = element_text(size=16),
              axis.title.y = element_text(size=16),
              strip.text.y = element_text(angle = 0, size = 16),
              strip.background = element_rect(colour="#FFFFFF", fill="#f7f7f9"),
              legend.background = element_rect(fill = "#f7f7f9", colour = "#FFFFFF"),
              legend.text = element_text(size = 12),
              legend.title=element_blank()
        ) +
        xlab("Wavelength [nm]") + 
        ylab(expression(paste(italic(E)[abs], " • ", MAC[λ], "  [m²/g]"))) +
        scale_x_continuous(breaks = seq(100, 1000, 100), limits = c(300, 1000)) +
        coord_trans(x = "log10") +
        scale_color_manual(values = c("#0072B5", "#7cb518", "#ffb703", "#e63946")) +
        stat_function(fun=function(x) 4400 * (x)^(-1), linetype = 2) +
        stat_function(fun=function(x) 8800 * (x)^(-1), linetype = 2) +
        geom_text(family = "Arial", size = 10,label = "\u2727", 
                  colour = "#FFFFFF", show.legend = FALSE, 
                  position = position_nudge(x = 0.1)) + # Simulation of stroke line
        geom_text(family = "Arial", size = 7,label = "\u2727", 
                  colour = "#777777", show.legend = FALSE, 
                  position = position_nudge(x = 0.1)) + # Simulation of stroke line
        geom_text_interactive(aes(colour = case_when(
          input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
          input$plot_category == "Abs_meas" ~ Abs_ref_red,
          input$plot_category == "Mass_meas" ~ Mass_ref_red
        ),
        data_id = case_when(
          input$plot_category == "Aerosol_type" ~ Aerosol_type_red,
          input$plot_category == "Abs_meas" ~ Abs_ref_red,
          input$plot_category == "Mass_meas" ~ Mass_ref_red
        ),
        tooltip = paste(Reference_study, "\n", round(MAC_m2g, digits = 2), " m²/g")),
        family = "Arial", size = 7, label = "\u2726",
        alpha = 0.7, show.legend = TRUE,
        position = position_nudge(x = 0.1)) + # Fill character in front
        annotate("text", x=335, y=14.8, family = "IBM Plex Sans", label= expression(paste(italic(E)[abs], " = 1; AAE = 1"))) +
        annotate("text", x=335, y=29, family = "IBM Plex Sans", label= expression(paste(italic(E)[abs], " = 2; AAE = 1")))
                                                          

      girafe(ggobj = MAC_lambda.gg, width_svg = 14, height_svg = 10,
             # fonts = list(sans = "Arial"),
             options = list(
               opts_hover(css = "fill:grey80;"),
               opts_hover_inv(css = "opacity:0.1;"),
               opts_selection(type = "single", only_shiny = FALSE)
             ))
      
      
    })
    
    output$MACtable <- DT::renderDataTable({
      DT::datatable(MAC_data[, c("MAC_m2g", "MAC_sd", "MAC_method", "Wavelength_nm",
                                "AAE", "Season", "Continent", "Time_period",
                                "Place", "Aerosol_source", "Aerosol_type",
                                "Mass_ref", "Abs_ref", "TD", "Thermal_protocol",
                                "Abs_correction", "C_factor", "Reference_study")],
                    rownames = FALSE,
                    style = "bootstrap",
                    options = list(pageLength = 300))
      
      
      
    })
    
    output$README <- renderUI({
      withMathJax(HTML(readLines(rmarkdown::render(input = "README.md",
                                                   output_format = rmarkdown::html_fragment(),
                                                   quiet = TRUE
      ))))
    })
}

# Run the application 
shinyApp(ui = ui, server = server)