8
votes

I have a list of data frames that I use to make a list of ggplots, and then assemble into a grid of plots with cowplot. I need to then attach a shared title, subtitle, and caption. I want to do this in a way that these labels will have the same theme elements (size, fontface, etc) as if they were created by labs instead of cowplot::draw_label.

In my real situation, I have several choropleths that each have their own units and scales, which is why I can't just facet, but instead need to build the plots independent of one another.

Here's a simplified version of the data, and the packages I have loaded:

library(tidyverse)
library(cowplot)

dfs <- list(
  adults_no_diploma = structure(list(
    tract = c("09003405100", "09003405200", "09003405300", "09003405401", "09003405402", "09003405500", "09003405600", "09003405700", "09003405800", "09003405900"), 
    value = c(0.08, 0.108, 0.095, 0.099, 0.105, 0.103, 0.161, 0.279, 0.056, 0.055)), 
    row.names = c(NA, -10L), class = c("tbl_df", "tbl", "data.frame")), 
  severe_cost_burden = structure(list(
    tract = c("09003405100", "09003405200", "09003405300", "09003405401", "09003405402", "09003405500", "09003405600", "09003405700", "09003405800", "09003405900"), 
    value = c(0.128, 0.147, 0.165, 0.1, 0.151, 0.11, 0.179, 0.184, 0.14, 0.038)), 
    row.names = c(NA, -10L), class = c("tbl_df", "tbl", "data.frame"))
)

I'm working with a custom theme (again, a simplified version):

theme_georgia <- function(...) {
  theme_gray(base_family = "Georgia", ...) + 
    theme(plot.title = element_text(face = "bold"))
}

plots <- dfs %>%
  imap(~{
    ggplot(.x, aes(x = value)) + 
      geom_density() + 
      ggtitle(.y) +
      theme_georgia()
    })

gridded <- plot_grid(plotlist = plots, nrow = 1)

Following the cowplot annotations vignette, I can make a title with ggdraw() + draw_label("Socio-economic measures") and manually set things like font size, but what I'd prefer is to somehow define that label as a title; that is, the theme would apply everything in plot.title to it, and the same for creating a subtitle and caption.

My current workaround is to make an empty ggplot with labs for the title and subtitle, do the same for a caption, and stack them vertically with cowplot::plot_grid.

title_gg <- ggplot() + 
  labs(title = "Socio-economic measures", subtitle = "By census tract, 2016") + 
  theme_georgia()
plot_grid(title_gg, gridded, ncol = 1, rel_heights = c(0.15, 1))

The workaround is okay, but I like the neatness and alignment you get with draw_label. I could instead add in these theme elements one by one to mimic a title:

title_theme <- ggdraw() +
  draw_label("Socio-economic measures", 
             fontfamily = theme_georgia()$text$family, 
             fontface = theme_georgia()$plot.title$face, x = 0.05, hjust = 0)
plot_grid(title_theme, gridded, ncol = 1, rel_heights = c(0.2, 1))

My question is whether there's any way I might combine these approaches to pull all the relevant theme elements and feed them to draw_label quickly, or somehow tell draw_label that this thing is a title and should get title theme elements, and this other thing is a subtitle, and so on. I imagine some sort of magic like this:

ggdraw() + 
  draw_label("Socio-economic measures", theme_georgia()$plot.title_elements)

or:

ggdraw() + 
  draw_label("Socio-economic measures", type = "title") + theme_georgia()
2
Great answer but is there a way to get rid of the grey horizontal bar?Tea Tree

2 Answers

6
votes

Like so?

library(ggplot2)
library(cowplot)

theme_georgia <- function(...) {
  theme_gray(base_family = "Georgia", ...) + 
    theme(plot.title = element_text(face = "bold"))
}


title_theme <- calc_element("plot.title", theme_georgia())

ggdraw() + 
  draw_label(
    "Socio-economic measures",
    fontfamily = title_theme$family,
    fontface = title_theme$face,
    size = title_theme$size
  )

Created on 2018-06-21 by the reprex package (v0.2.0).

5
votes

@Claus Wilke's solution got the job done, but I was inspired to write a wrapper function around draw_label to get the styling necessary to mimic a theme element. I'm posting here in case it's of use to anybody else, though this might be a weird use case.

This function takes a theme function or, if theme is omitted, gets the current theme from theme_get. It also takes the name of a theme element, such as "plot.title", from which it gets the styling with calc_element. All the requisite arguments are passed to cowplot::draw_label, along with anything else in ... such as x or hjust.

library(tidyverse)
library(cowplot)

draw_label_theme <- function(label, theme = NULL, element = "text", ...) {
  if (is.null(theme)) {
    theme <- ggplot2::theme_get()
  }
  if (!element %in% names(theme)) {
    stop("Element must be a valid ggplot theme element name")
  }

  elements <- ggplot2::calc_element(element, theme)

  cowplot::draw_label(label, 
                      fontfamily = elements$family,
                      fontface = elements$face,
                      colour = elements$color,
                      size = elements$size,
                      ...
  )
}

title <- ggdraw() +
  draw_label_theme("Socio-economic measures", 
                   theme = theme_georgia(), element = "plot.title",
                   x = 0.05, hjust = 0, vjust = 1)
subtitle <- ggdraw() +
  draw_label_theme("By census tract, 2016",
                   theme = theme_georgia(), element = "plot.subtitle",
                   x = 0.05, hjust = 0, vjust = 1)

Now the only hard part is lining things up neatly in the rel_heights, which I could mess around with more here. There might be positioning information to pull out of the theme and use to set the heights.

plot_grid(title, subtitle, gridded, ncol = 1, rel_heights = c(0.1, 0.1, 1))