ggplot colorbar to panel height with patched graphs

106 views Asked by At

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

1

There are 1 answers

6
stefan On BEST ANSWER

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()