Search code examples
rggplot2ggtextpatchwork

Multipanel plots with spanning figure legend in R with ggtext and gridtext in R


I am close to achieving this multipanel plot with a spanning textgrob figure legend below. But I continue to get an unexpected amount of space between the figures and the figure legend. Attempt in the reprex below.

# Library calls
library(tidyverse)
library(grid)
library(gridtext)
library(ggtext)
library(patchwork)

# make dummy figures
d1 <- runif(500)
d2 <- rep(c("Treatment", "Control"), each=250)
d3 <- rbeta(500, shape1=100, shape2=3)
d4 <- d3 + rnorm(500, mean=0, sd=0.1)
plotData <- data.frame(d1, d2, d3, d4)
str(plotData)
#> 'data.frame':    500 obs. of  4 variables:
#>  $ d1: num  0.0177 0.2228 0.5643 0.4036 0.329 ...
#>  $ d2: Factor w/ 2 levels "Control","Treatment": 2 2 2 2 2 2 2 2 2 2 ...
#>  $ d3: num  0.986 0.965 0.983 0.979 0.99 ...
#>  $ d4: num  0.876 0.816 1.066 0.95 0.982 ...

p1 <- ggplot(data=plotData) + geom_point(aes(x=d3, y=d4)) +
  theme(plot.background = element_rect(color='black'))
p2 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2, y=d1, fill=d2))+
  theme(legend.position="none") +
  theme(plot.background = element_rect(color='black'))
p3 <- ggplot(data=plotData) +
  geom_histogram(aes(x=d1, color=I("black"), fill=I("orchid"))) +
  theme(plot.background = element_rect(color='black'))
p4 <- ggplot(data=plotData) +
  geom_histogram(aes(x=d3, color=I("black"), fill=I("goldenrod"))) +
  theme(plot.background = element_rect(color='black'))


fig_legend <- textbox_grob(
  "**Figure 1.**  Testing Control vs. Treatment.   A. Scatterplot. 
  B. The outcomes in the control arm were significantly better than 
  the Treatment Arm. C. Histogram. D. Another Histogram.",
  gp = gpar(fontsize = 11),
  box_gp = gpar(col = "black",   linetype = 1),
  padding = unit(c(3, 3, 3, 3), "pt"),
  margin = unit(c(0,0,0,0), "pt"),
  height = unit(0.6, "in"),
  width = unit(1, "npc"),
  #x = unit(0.5, "npc"), y = unit(0.7, "npc"),
  r = unit(0, "pt")
)


p1 + {
  p2 + {
    p3 +
      p4 +
      plot_layout(ncol=1)
  }
} + fig_legend +
  plot_layout(ncol=1)
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Created on 2020-02-09 by the reprex package (v0.3.0)


Solution

  • The correct approach is to use plot_annotation(). The reason why there's a little horizontal gap on either side of the caption is that the plot margins are still applied to the caption, just like in regular ggplot2. If you want to avoid that, you have to set the plot margins to 0 and create spacing by adding appropriate margins to the axis titles etc.

    # Library calls
    library(tidyverse)
    library(ggtext)
    library(patchwork)
    
    # make dummy figures
    d1 <- runif(500)
    d2 <- rep(c("Treatment", "Control"), each=250)
    d3 <- rbeta(500, shape1=100, shape2=3)
    d4 <- d3 + rnorm(500, mean=0, sd=0.1)
    plotData <- data.frame(d1, d2, d3, d4)
    
    p1 <- ggplot(data=plotData) + geom_point(aes(x=d3, y=d4)) +
      theme(plot.background = element_rect(color='black'))
    p2 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2, y=d1, fill=d2))+
      theme(legend.position="none") +
      theme(plot.background = element_rect(color='black'))
    p3 <- ggplot(data=plotData) +
      geom_histogram(aes(x=d1, color=I("black"), fill=I("orchid"))) +
      theme(plot.background = element_rect(color='black'))
    p4 <- ggplot(data=plotData) +
      geom_histogram(aes(x=d3, color=I("black"), fill=I("goldenrod"))) +
      theme(plot.background = element_rect(color='black'))
    
    
    fig_legend <- plot_annotation(
      caption = "**Figure 1.**  Testing Control vs. Treatment.   A. Scatterplot. 
      B. The outcomes in the control arm were significantly better than 
      the Treatment Arm. C. Histogram. D. Another Histogram.",
      theme = theme(
        plot.caption = element_textbox_simple(
          size = 11,
          box.colour = "black",
          linetype = 1,
          padding = unit(c(3, 3, 3, 3), "pt"),
          r = unit(0, "pt")
        )
      )
    )
    
    
    p1 + {
      p2 + {
        p3 +
          p4 +
          plot_layout(ncol=1)
      }
    } + fig_legend +
      plot_layout(ncol=1)
    #> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
    #> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
    

    Created on 2020-02-09 by the reprex package (v0.3.0)

    Actually, you can use negative margins on the caption to counteract the plot margins.

    fig_legend <- plot_annotation(
      caption = "**Figure 1.**  Testing Control vs. Treatment.   A. Scatterplot. 
      B. The outcomes in the control arm were significantly better than 
      the Treatment Arm. C. Histogram. D. Another Histogram.",
      theme = theme(
        plot.caption = element_textbox_simple(
          size = 11,
          box.colour = "black",
          linetype = 1,
          padding = unit(c(3, 3, 3, 3), "pt"),
          margin = unit(c(0, -5.5, 0, -5.5), "pt"),
          r = unit(0, "pt")
        )
      )
    )
    
    
    p1 + {
      p2 + {
        p3 +
          p4 +
          plot_layout(ncol=1)
      }
    } + fig_legend +
      plot_layout(ncol=1)
    #> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
    #> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
    

    Created on 2020-02-09 by the reprex package (v0.3.0)