ggplot2

Misc

  • Packages

    • {ggreveal} - Reveal a ggplot Incrementally (e.g. groups, facets)
    • {thematic} - Enables automatic styling of R plots in Shiny, R Markdown, and RStudio
    • {hrbrthemes} - hrbrmstr’s collection of themes
    • {{brand_yml}} - Create reports, apps, dashboards, plots and more that match your company’s brand guidelines with a single _brand.yml file
      • For Quarto dashboards, websites, reports, etc.
      • For Python shiny, seaborn, matplotlib, etc.
      • For R shiny, ggplot2, plot, RMarkdown
    • {ggblanket} - Similar ggplot ui but with simpler structure. Stuff that would require multiple geoms are replaced with one geom and basic arguments. Compatible with popular ggplot extensions
  • Resources

  • Don’t use stat calculating geoms and set axis limits with scale_y_continuous

    • See examples of the behavior in this thread
  • Defaults for any {ggplot2} geom using the default_aes field (i.e. GeomBlah$default_aes )

  • Basic Function

    viz_monthly <- 
      function(df, y_var, threshhold = NULL) {
    
        ggplot(df,
               aes(x = .data[["day"]],
                   y = .data[[y_var]])) +
          geom_line() +
          geom_hline(yintercept = threshhold, 
                     color = "red", 
                     linetype = 2) +
          scale_x_continuous(breaks = seq(1, 29, by = 7)) +
          theme_minimal()
    
    }
    • I’ve written a couple basic ones in the wild without tidyeval, but uses conditionals for theming: Fun1, Fun2
  • Storing geom settings in a dataframe (source)

    df <- data.frame(x = seq(from = 1, to = 20, by = 1),
                     y = rnorm(20, mean = 50, sd = 4),
                     limits = "c(0,30)")
    
    ggplot(data = df, aes(x = x, y = y)) +
      geom_line() +
      scale_y_continuous(limits= eval(parse(text=df$limits)))
  • FIltering Data in Geoms (source)

    mtcars |> 
      rownames_to_column() |> 
      ggplot(aes(mpg, hp)) +
      geom_point() +
      geom_label(
        aes(label = rowname),
        data = ~slice_max(.x,
                          hp,
                          n = 2),
        nudge_x = 3
      )
  • Dynamic Axis Limits (source)

    ggplot(...) +
      ... +
      scale_x_continuous(
        breaks = seq(2021, 2023, 1),
        limits = c(min(df$year) - .1, max(df$year) + .1),
        position = "top",
        labels = c("Baseline", "2022", "2023")
      )

Themes

  • Docs

  • Theme Elements Cheatsheet (source)

  • Use the same font family to all geom_text elements

    update_geom_defaults(
      geom = "text",
      aes(family = "Open Sans")
    )
    • geom_text is not impacted by theme settings, so setting this option keeps you from having to manually add the font family to each instance.
  • Setting color defaults (source)

    # Discrete with a list
    options(ggplot2.discrete.colour = list_of_colors)
    
    # Continuous with a function
    options(
      ggplot2.continuous.colour = \(...){
        scico::scale_color_scico(
          palette = "batlow", 
          ...
        )
      }
    )
    
    # Ordinal with a function
    options(
      ggplot2.ordinal.colour = \(...){
        scale_color_viridis_d(
          option = "G", 
          direction = -1, 
          ...
        )
      }
    )
    • If you have a particular set of palettes you like to use for projects, these options can be put into a R file and loaded via source e.g. source(here::here("_defaults.R"))
  • Basic Syntax

    theme_psc <- function() {
      theme_void(base_family = "Open Sans") +
        theme()
    }
  • Use Utility FIles

    • utils/base-theme.R (source)

      ## R/themes/base_theme.R
      
      #' Get default theme colors
      #' @param palette Optional custom color palette
      #' @return List of theme colors
      get_theme_colors <- function(palette = NULL) {
          list(
              background = "#f5f5f2",
              title      = "gray20",
              subtitle   = "gray30",
              text       = "gray30",
              caption    = "gray40",
              palette    = palette  # This can be overridden weekly
          )
      }
      
      #' Create base theme with consistent elements
      #' @param colors List of colors from get_theme_colors()
      #' @return ggplot theme object
      create_base_theme <- function(colors = get_theme_colors()) {
          # Only include the truly fixed elements that don't change weekly
          theme_minimal(base_size = 14, base_family = fonts$text) +
              theme(
                  plot.title.position   = "plot",
                  plot.caption.position = "plot",
                  legend.position       = "plot",
      
                  # Background elements
                  plot.background  = element_rect(fill = colors$background, color = colors$background),
                  panel.background = element_rect(fill = colors$background, color = colors$background),
      
                  # Common margins
                  plot.margin = margin(t = 10, r = 20, b = 10, l = 20),
      
                  # Text elements that stay consistent
                  axis.text  = element_text(
                      family = fonts$text,
                      size   = rel(0.79),
                      color  = colors$text
                  ),
                  axis.title = element_text(
                      family = fonts$text,
                      size   = rel(0.93),
                      face   = "bold",
                      color  = colors$text
                  )
      
                  # Note: specific theme elements should be added in the scripts
              )
      }
      
      #' Extend base theme with customizations
      #' @param base_theme Existing theme to extend
      #' @param new_theme_elements List of theme elements specific to this project
      #' @return Modified ggplot theme
      extend_base_theme <- function(base_theme, new_theme_elements) {
          base_theme + new_theme_elements
      }
    • script.R (source)

      source(here::here("utils/base_theme.R"))
      
      # Get base colors with custom palette
      colors <- 
        get_theme_colors(palette = c("#f7fbff", "#9ecae1", "#2171b5", "#084594"))
      
      base_theme <- create_base_theme(colors)
      
      project_theme <- extend_base_theme(
          base_theme,
          theme(
              # project-specific modifications
              axis.line.x           = element_line(color = "#252525", linewidth = .2),
      
              panel.spacing.x       = unit(2, 'lines'),
              panel.spacing.y       = unit(1, 'lines'),
              panel.grid.major.x    = element_blank(),
              panel.grid.major.y    = element_line(color = alpha(colors[5], 0.2), linewidth = 0.2),
              panel.grid.minor      = element_blank(),
          )
      )
      
      theme_set(project_theme)
  • Transparent Background

    p +
      theme(
        panel.background = element_rect(fill='transparent'), #transparent panel bg
        plot.background = element_rect(fill='transparent', color=NA), #transparent plot bg
        panel.grid.major = element_blank(), #remove major gridlines
        panel.grid.minor = element_blank(), #remove minor gridlines
        legend.background = element_rect(fill='transparent'), #transparent legend bg
        legend.box.background = element_rect(fill='transparent') #transparent legend panel
      )
    ggsave('myplot.png', p, bg='transparent')
  • theme_notebook

    theme_notebook <- function(...) {
      theme_minimal() %+replace%
        theme(
          panel.background = element_rect(fill='#FFFDF9FF'),
          panel.grid.minor = element_blank(),
          plot.background = element_rect(fill='#FFFDF9FF', color=NA),
          legend.background = element_rect(fill='#FFFDF9FF'),
          legend.box.background = element_rect(fill='#FFFDF9FF'),
          ...
        )
    }
  • Cedric Sherer (article)

    theme_set(theme_minimal(base_size = 15, base_family = "Anybody"))
    theme_update(
      axis.title.x = element_text(margin = margin(12, 0, 0, 0), color = "grey30"),
      axis.title.y = element_text(margin = margin(0, 12, 0, 0), color = "grey30"),
      panel.grid.minor = element_blank(),
      panel.border = element_rect(color = "grey45", fill = NA, linewidth = 1.5),
      panel.spacing = unit(.9, "lines"),
      strip.text = element_text(size = rel(1)),
      plot.title = element_text(size = rel(1.4), face = "bold", hjust = .5),
      plot.title.position = "plot"
    )
  • Eric Bickel

    ggplot2::theme_set(
      theme_bw(base_size = 12) +
        theme(
              plot.title = element_text(face = 'bold', hjust = 0),
              text = element_text(colour = '#4e5c65'),
              panel.background = element_rect('#ffffff'),
              strip.background = element_rect('#ffffff', colour = 'white'),
              plot.background = element_rect('#ffffff'),
              panel.border = element_rect(colour = '#ffffff'),
              panel.grid.major.x = element_blank(),
              panel.grid.major.y = element_blank(),
              panel.grid.minor.y = element_blank(),
              legend.background = element_rect('#ffffff'),
              legend.title = element_blank(),
              legend.position = 'right',
              legend.direction = 'vertical',
              legend.key = element_blank(),
              strip.text = element_text(face = 'bold', size = 10),
              axis.text = element_text(face = 'bold', size = 9),
              axis.title = element_blank(),
              axis.ticks = element_blank()
        )

Bespoke

  • Comparison Plot (source)
    • Example

      Code

      library(tidyverse)
      library(scales)
      
      comparison_plot <- 
        function(
          df,
          highlight_town,
          value_type,
          highlight_color
        ) {
      
          plot <- 
            df |>
              ggplot(
                aes(
                  x = value,
                  y = 1
                )
              ) +
              # Light gray lines for all towns
              geom_point(
                shape = 124,
                color = "gray80"
              ) +
              # Line for town to highlight
              geom_point(
                data = df |> filter(location == highlight_town),
                shape = 124,
                color = highlight_color,
                size = 10
              )
      
          if (value_type == "percent") {
              final_plot <- plot +
                scale_x_continuous(
                  labels = percent_format(accuracy = 1)
                )
          }
      
          if (value_type == "number") {
            final_plot <- plot +
              scale_x_continuous(
                labels = comma_format(accuracy = 1)
              )
          }
        }
      
      comparison_plot(
        df = single_family_homes,
        highlight_town = "Hartford",
        value_type = "percent",
        highlight_color = psc_blue
      ) +
        theme_whatever()
      
      
      big_number_plot <- 
        function(value, 
                 text,
                 value_color
        ) {
          ggplot() +
            # Add value
            geom_text(
              aes(
                x = 1,
                y = 1,
                label = value
              ),
              color = value_color,
              fontface = "bold",
              size = 20,
              hjust = 0
            ) +
            # Add text
            geom_text(
              aes(
                x = 1,
                y = 2,
                label = str_to_upper(text)
              ),
              color = "gray70",
              size = 7,
              hjust = 0
            )
      }
      
      big_number_plot(
        value = "19%",
        text = "Single-Family Homes as\nPercent of All Homes",
        value_color = psc_blue
      ) +
        theme_whatever() # should start with theme_void