Example 07: Themes

Setup

library(here)       # manage file paths
here() starts at /Users/kjhealy/Documents/courses/socdata.co
library(socviz)     # data and some useful functions
library(tidyverse)  # your friend and mine
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Style a plot with one of the built-in themes:

p <- ggplot(data = mtcars, 
            mapping = aes(x = wt, 
                          y = mpg, 
                          color = factor(cyl))) + 
  geom_point(size = rel(3)) + 
  labs(x = "Weight", y = "MPG", color = "Cylinders", 
       title = "mtcars Plot", 
       subtitle = "The subtitle", 
       caption = "The caption")

p + theme_gray()

p + theme_bw()

p + theme_dark()

With a dark background we also need to think about the palettes we’re using on the color and fill scales.

p + 
  scale_color_brewer(type = "qual", palette = "Pastel1") + 
  theme_dark()  

The theme() function

The many pieces of the theme function are covered in the slides. We can make adjustments to them either by setting a value (for options like legend.position) or by asking, of other elements, whether it’s text, a line, or a rectangle and adjusting the element with element_text(), element_line(), or element_rect(). Remember that a number of elements inherit from a parent, like axis.

p + theme(plot.title = element_text(face = "bold", 
                                    color = "red", 
                                    size = rel(3)), 
          
          panel.grid.major = element_line(color = "black"), 
          panel.grid.minor = element_line(color = "green", 
                                          linewidth = rel(1.4),
                                          linetype = "dotted"), 
          plot.background = element_rect(fill = "steelblue1"))

Themes are just functions that make a bunch of these adjustments

E.g. the built-in theme_bw() modifies theme_grey() with a bunch of additional options, expressed in a theme() call:

theme_bw
function (base_size = 11, base_family = "", base_line_size = base_size/22, 
    base_rect_size = base_size/22) 
{
    theme_grey(base_size = base_size, base_family = base_family, 
        base_line_size = base_line_size, base_rect_size = base_rect_size) %+replace% 
        theme(panel.background = element_rect(fill = "white", 
            colour = NA), panel.border = element_rect(fill = NA, 
            colour = "grey20"), panel.grid = element_line(colour = "grey92"), 
            panel.grid.minor = element_line(linewidth = rel(0.5)), 
            strip.background = element_rect(fill = "grey85", 
                colour = "grey20"), complete = TRUE)
}
<bytecode: 0x113fd6ed8>
<environment: namespace:ggplot2>

but with the special %+replace% operator. You can use + if you like. The difference (read the help for theme_get()) is that %+replace% swaps out the entire element being updated, whereas + does not:

+ updates the elements of e1 that differ from elements specified (not NULL) in e2. Thus this operator can be used to incrementally add or modify attributes of a ggplot theme.

In contrast, ⁠%+replace%⁠ replaces the entire element; any element of a theme not specified ine2 will not be present in the resulting theme (i.e. NULL). Thus this operator can be used to overwrite an entire theme.

To make a theme yourself, you can begin entirely from scratch or you can adapt an existing theme (like theme_bw() does. For example:

theme_duke <- function(base_size = 14, 
                          base_family = "", 
                          base_line_size = base_size/22, 
                          base_rect_size = base_size/22) {
  
  ## See https://brand.duke.edu/colors/
  dukeblue1 <- "#012169" # Really too dark to be of use for much tbh
  dukeblue2 <- "#00539B"
  textcolor <- "#FFFFFF"
  half_line <- base_size / 2
  
  ggplot2::theme_minimal(base_size = base_size, base_family = base_family, 
        base_line_size = base_line_size, base_rect_size = base_rect_size) %+replace%
    theme(
      axis.text = element_text(color = textcolor, 
                               face = "bold"), 
      axis.ticks = element_line(color = textcolor),
      axis.title = element_text(color = textcolor),
      plot.title = element_text(color = textcolor, 
                                face = "bold",
                                size = rel(2),
                                # left-justified text
                                hjust = 0, vjust = 1),
      plot.subtitle = element_text(color = textcolor, size = rel(1.2), 
                                   hjust = 0, vjust = 1, 
                                   margin = margin(t = half_line, b = half_line)),
      plot.caption = element_text(color = textcolor, hjust = 1), # right-justified
      plot.background = element_rect(fill = dukeblue2, color = "white"),
      panel.background = element_rect(fill = "white", color = NA),
      panel.border = element_rect(fill = NA, color = dukeblue2),
      panel.grid = element_line(color = dukeblue2, 
                                linetype = "dotted", 
                                linewidth = rel(0.4)), 
      panel.grid.minor = element_blank(), 
      strip.background = element_rect(fill = "#FCF7E5"), 
      legend.text = element_text(color = textcolor), 
      legend.title = element_text(color = textcolor),
      legend.position = "top", 
      legend.key.size = unit(2,"line"),
      legend.title.position = "top"
    )
}

Now we can do, e.g.,

bd_colors <- c("#C84E00", "#A1B70D", "#E89923")

p + 
  scale_color_manual(values = bd_colors) + 
  theme_duke(base_family = "Open Sans") # See https://brand.duke.edu

This is ugly! But you see how much control you have over how things look. Also it serves as a reminder that you will likely have to do quite a bit of detail-oriented work if you want a properly good custom theme. Choose a theme and stick with it.