Search code examples
rggplot2plotlegendfacet

How do I add a graphical legend to the bottom of a figure using ggplot?


I am interested in creating a figure using ggplot2 similar to the one drafted here. I have created the top section using ggplot and facet_wrap but have yet to find a way to add a tiled legend to the bottom of the figure. Listed below is code for a fake dataset and my attempt so far, which produces this.

data1 <- data.frame(lower = c(0, 2, 1, 0, 4,
                              3, 2, 2, 3, 0),
                    upper = c(7, 6, 9, 10, 9,
                              10, 6, 5, 6, 10),
                    point = c(3.5, 4, 5, 5, 6.5,
                              6.5, 4, 3.5, 4.5, 5),
                    variable = c("Var 1", "Var 1", "Var 1", "Var 1", "Var 1",
                                 "Var 2", "Var 2", "Var 2", "Var 2", "Var 2"),
                    specification = c("Study 1A", "Study 1B", "Study 1C", "Study 2A", "Study 2B",
                                      "Study 1A", "Study 1B", "Study 1C", "Study 2A", "Study 2B"),
                    treatment_size = c(1, 2, 3, 1, 4,
                                       1, 2, 3, 1, 4),
                    treatment_info = c("No", "Yes", "Yes", "No", "Yes",
                                       "No", "Yes", "Yes", "No", "Yes"))        

rect <- data.frame(xmin = c("Study 1A", 
                            "Study 1A"),
                   xmax = c("Study 2B", 
                            "Study 2B"),
                   ymin = c(data1$lower[3], 
                            data1$lower[6]),
                   ymax = c(data1$upper[3], 
                            data1$upper[6]),
                   alpha = c(0.1, 0.1),
                   fill = c("blue", "blue"))

plot <- ggplot(data1, aes(x=specification, y=point)) +
  geom_errorbar(width=0.1, aes(ymin=lower, ymax=upper)) +
  geom_point(shape=21, size=0.5, fill="black") +
  ylab("Effect") +
  xlab("") +
  theme(axis.text.x=element_text(angle=90, vjust=0.5, hjust=1)) +
  geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
            alpha = 0.1, fill = "blue",
            data = transform(rect, variable = c("Var 1", "Var 2")),
            inherit.aes = FALSE)

plot + facet_wrap(vars(variable), ncol = 1)

Solution

  • One option to achieve or at least come close to your desired result would be to create the legend as a second ggplot which could then be glued to your main plot using patchwork. Basically this involves some data wrangling as first step and more or less styling via theme options to achieve the table look.

    Note: I also adjusted the main plot slightly to achieve the look as in the image you added as an example of your desired result.

    library(dplyr)
    library(tidyr)
    library(ggplot2)
    library(patchwork)
    
    plot <- ggplot(data1, aes(x = specification, y = point)) +
      geom_errorbar(width = 0.1, aes(ymin = lower, ymax = upper)) +
      geom_point(shape = 21, size = 0.5, fill = "black") +
      ylab("Effect") +
      xlab("") +
      theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
      geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = ymin, ymax = ymax),
        alpha = 0.1, fill = "blue",
        data = transform(rect, variable = c("Var 1", "Var 2")),
        inherit.aes = FALSE
      )
    
    pmain <- plot + 
      facet_wrap(vars(variable), ncol = 1) +
      theme_void() +
      theme(strip.background.x = element_rect(fill = "grey95", color = NA),
            strip.text.x = element_text(margin = margin(t = 5, b = 5))
            )
    
    data2 <- data1 |> 
      distinct(specification, treatment_size, treatment_info) |> 
      mutate(across(everything(), as.character)) |> 
      pivot_longer(-specification, names_prefix = "^treatment_") |> 
      mutate(fill = if_else(value %in% c("Yes", "No"), value, "grey"),
             label = if_else(value %in% c("Yes", "No"), NA_character_, value))
    
    ptable <- ggplot(data2, aes(specification, name)) +
      geom_tile(aes(fill = fill), width = .975, height = .975, color = "black") +
      geom_text(aes(label = label)) +
      scale_fill_manual(values = c(Yes = "green", No = "red", grey = "grey95")) +
      scale_y_discrete(limits = c("size","info"), labels = c("Size", "Info"), expand = c(0, 0.5)) +
      scale_x_discrete(position = "top", expand = c(0, 0.5)) +
      theme_void() +
      guides(fill = "none") +
      theme(axis.text.x = element_text(margin = margin(t = 5, b = 5)),
            axis.text.y = element_text(margin = margin(l = 5, r = 5))) +
      coord_fixed(ratio = .5)
    
    pmain / ptable
    

    enter image description here