Search code examples
rggplot2patchwork

patchwork: how to have within-panel tags for a 3x3 combined plot?


Similar to this problem patchwork: align tags inside panels for side-by-side plots, I need to move tags inside panel borders, but now for a 3x3 grid.

library(devtools)
library(ggplot2)
library(patchwork)

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)

This code is to make sure one combined legend is shown per row:

p1 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p2 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p3 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position =  "right")
p4 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p5 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p6 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position =  "right")
p7 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p8 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "none")
p9 <- ggplot(data=plotData) + geom_boxplot(aes(x=d2,y=d1,fill=d2)) + theme(legend.position = "right")

This code produced a combined plot with tags, at default locations

(((p1 + p2 + p3) / 
(p4 + p5 + p6) / 
(p7 + p8 + p9)) + guide_area()) & plot_annotation(tag_levels = "A") 

enter image description here

Adding & plot_annotation(tag_levels = "A") + theme(legend.position = "right", plot.tag.position = c(.15, .95)) removed the tags.

enter image description here

Thanks for stopping by.


Solution

  • Perhaps there is a more straightforward approach to achieve your desired result but one option would be to fake your inner tags using annotate or annotation_custom as I do below which means to manually add the "tags" as annotations to each plot:

    library(ggplot2)
    library(patchwork)
    
    plot_list <- lapply(1:9, \(x) {
      legend_position <- if (x %% 3 == 0) {
        "right"
      } else {
        "none"
      }
      ggplot(data = plotData) +
        geom_boxplot(aes(x = d2, y = d1, fill = d2)) +
        theme(legend.position = legend_position)
    })
    
    add_tag <- function(label, x = 1, y = 1, padding.x = unit(5, "pt"), padding.y = padding.x, hjust = 1, vjust = 1, size = 13) {
      annotation_custom(
        grid::textGrob(
          x = unit(x, "npc") - padding.x,
          y = unit(y, "npc") - padding.y,
          hjust = hjust, vjust = vjust,
          label = label, gp = grid::gpar(fontsize = size)
        )
      )
    }
    
    # Default: topright corner
    lapply(
      seq_along(plot_list), \(x) plot_list[[x]] + add_tag(LETTERS[x])
    ) |>
      wrap_plots(ncol = 3)
    

    
    # Tags in "topleft" corner
    lapply(
      seq_along(plot_list), \(x) plot_list[[x]] + add_tag(
        LETTERS[x],
        x = 0, y = 1,
        hjust = 0, vjust = 1,
        padding.x = -unit(5, "pt"), padding.y = unit(5, "pt")
      )
    ) |>
      wrap_plots(ncol = 3)