Search code examples
rggplot2patchwork

ggplot colorbar to panel height with patched graphs


Let's assume I have a set of charts using ggplot that I patch together using patchwork. All charts are having the same legend and I want to have one legend on the right-hand side that is as high as my patched plots. This works using a custom function for a single chart, but once it is patched together, it no longer works. Are there any work arounds to achieve the same behavior for patched charts?

For simplicity, I use the same chart four times.

library(reshape2)
library(ggplot2)
library(patchwork)

set.seed(123)
t <- seq(0, 1, by = 0.001)
p <- length(t) - 1
n <- 5
I <- matrix(rnorm(n * p, 0, 1 / sqrt(p)), n, p)
df <- data.frame(apply(I, 1, cumsum))
df <- data.frame(x = seq_along(df[, 1]), df)
df <- melt(df, id.vars = "x")
df$variable <- as.numeric(df$variable)

gg <- ggplot(df, aes(x = x, y = value, color = variable, group = variable)) +
  geom_line() +
  viridis::scale_color_viridis() +
  theme(panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5))

gg + gg + gg + gg

This creates the following chart:

example

However, ideally I want to have the colorbar as high as the plot. For a single chart this looks as follows:

make_fullsize <- function() structure("", class = "fullsizebar")

ggplot_add.fullsizebar <- function(obj, g, name = "fullsizebar") {
  h <- ggplotGrob(g)$heights
  panel <- which(grid::unitType(h) == "null")
  panel_height <- unit(1, "npc") - sum(h[-panel])
  
  g + 
    guides(color = guide_colorbar(barheight = panel_height,
                                  title.position = "right")) +
    theme(legend.title = element_text(angle = -90, hjust = 0.5))
}

gg +
  make_fullsize()

desired output

Collecting the legend in patchwork works as intended. However, this does not work in conjunction with make_fullsize().

gg + gg + gg + gg +
  plot_layout(guides = "collect")

patched charts with common legend


Solution

  • Here is an adaption of the original code by @AllanCameron which also accounts for the case of a patchwork with collected (!!) guides:

    library(reshape2)
    library(ggplot2)
    library(patchwork)
    
    set.seed(123)
    t <- seq(0, 1, by = 0.001)
    p <- length(t) - 1
    n <- 5
    I <- matrix(rnorm(n * p, 0, 1 / sqrt(p)), n, p)
    df <- data.frame(apply(I, 1, cumsum))
    df <- data.frame(x = seq_along(df[, 1]), df)
    df <- melt(df, id.vars = "x")
    df$variable <- as.numeric(df$variable)
    
    gg <- ggplot(df, aes(x = x, y = value, color = variable, group = variable)) +
      geom_line() +
      viridis::scale_color_viridis() +
      theme(panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5))
    
    make_fullsize <- function() {
      structure(
        "",
        class = "fullsizebar"
      )
    }
    
    ggplot_add.fullsizebar <- function(obj, g, name = "fullsizebar") {
      is_patch <- inherits(g, "patchwork")
      if (is_patch) {
        p <- patchworkGrob(g)
        ix_panel <- grep("^panel\\-area", p$layout$name)
      } else {
        p <- ggplotGrob(g)
        ix_panel <- grep("^panel", p$layout$name)
      }
      h <- p$heights
    
      panel_t <- p$layout[ix_panel, "t"]
      panel_b <- p$layout[ix_panel, "b"]
    
      panel_height <- unit(1, "npc") - sum(h[-seq(panel_t, panel_b)])
    
      layers <- list(
        guides(color = guide_colorbar(
          barheight = panel_height,
          title.position = "right"
        )),
        theme(legend.title = element_text(angle = -90, hjust = 0.5))
      )
      
      if (is_patch) {
        g & layers
      } else {
        g + layers
      }
    }
    
    gg + gg + gg + gg +
      plot_layout(guides = "collect") +
      make_fullsize()
    

    
    (gg / gg) +
      plot_layout(guides = "collect") +
      make_fullsize()
    

    
    gg +
      make_fullsize()