rggplot2patchwork

Why does wrap_plots() fail when combining sub-layouts with unit() widths and heights in patchwork?


I'm using the patchwork package in R to arrange 6 plots into a 3-row by 2-column layout, where each column has plots stacked vertically.

Each vertical column works when created independently, with fixed widths and heights using unit() from the grid package. But when I try to combine the two wrapped columns with wrap_plots(), I get this error:

Error: index out of bounds ("unit" subsetting)

library(ggplot2)
library(patchwork)
library(grid)

# Dummy plots
p_tmp <- lapply(1:6, function(i) {
  ggplot(
    data = mtcars, 
    aes(mpg, wt)
    ) + 
    geom_point() + 
    labs(
      subtitle = paste("Plot", i)
      )
  })

# Individual columns work fine
col1 <- wrap_plots(
  p_tmp[1:3],
  ncol = 1,
  heights = unit(rep(2.5, 3), "cm"),
  widths = unit(2.5, "cm")
) +
  plot_annotation(
    "col1"
  )

col2 <- wrap_plots(
  p_tmp[4:6],
  ncol = 1,
  heights = unit(rep(2.5, 3), "cm"),
  widths = unit(9 / 16 * 2.5, "cm")
) +
  plot_annotation(
    "col2"
  )

# Combining both results in error
wrap_plots(col1, col2, ncol = 2)

The individual column plots are working, but when combining them, I get the mentioned error.

col1 col2

How can I correctly combine multiple wrap_plots() columns that use fixed unit() widths and heights? Is there a way to keep fixed dimensions and still combine them in a larger layout without triggering this error?

Edit:

To clearify the aim of the question: The first 3 plots are identical geom_raster() plots and should share a common fill legend, while plots 4-6 remain the same. The position of the legend should be centered under the first column, which is not the case when combining all plots with one wrap_plots() command.

comb_all

library(ggplot2)
library(patchwork)

# Create raster plot
df <- expand.grid(x = 1:20, y = 1:20)
df$z <- as.vector(matrix(runif(400), nrow = 20, ncol = 20))

raster_plot <- ggplot(
  data = df, 
  aes(x, y, fill = z)
  ) +
  geom_raster(
    interpolate = FALSE
    ) +
  scale_fill_viridis_c() +
  theme_minimal() +
  theme(
    legend.position = "bottom"
  )

# Define plots
p_tmp <- c(
  rep(list(raster_plot), 3),
  lapply(1:3, function(i) {
    ggplot(
      data = mtcars, 
      aes(mpg, wt)
      ) +  
      geom_point() +
      theme_minimal()
}))

heights <- unit(rep(2, 3), "cm")

# Column 1 – works alone
col1 <- wrap_plots(
  p_tmp[1:3],
  ncol = 1,
  heights = heights,
  widths = unit(2, "cm"),
  guides = "collect"
)

# Column 2 – works alone
col2 <- wrap_plots(
  p_tmp[4:6],
  ncol = 1,
  heights = heights,
  widths = unit(9 / 16 * 2, "cm")
)

# Combining both → error
# wrap_plots(col1, col2, ncol = 2)

# Combining all → no error
wrap_plots(
  p_tmp,
  ncol = 2,
  byrow = FALSE,
  heights = heights,
  widths = unit(c(2, 9 / 16 * 2), rep("cm", 2)),
  guides = "collect",
  axis_titles = "collect"
)

Solution

  • Instead of using multiple wrap_plots one option would be to use just one:

    library(ggplot2)
    library(patchwork)
    
    p_tmp <- lapply(1:6, function(i) {
      ggplot(
        data = mtcars,
        aes(mpg, wt)
      ) +
        geom_point() +
        labs(
          subtitle = paste("Plot", i)
        )
    })
    
    wrap_plots(
      p_tmp,
      ncol = 2,
      byrow = FALSE,
      heights = unit(rep(2.5, 3), "cm"),
      widths = unit(c(2.5, 9 / 16 * 2.5), "cm")
    )
    

    EDIT To deal with the legend we can use guide_area and plot_spacer to add the legend to the first column:

    library(ggplot2)
    library(patchwork)
    
    wrap_plots(
      c(p_tmp[1:3], list(guide_area()), p_tmp[4:6], list(plot_spacer())),
      ncol = 2,
      nrow = 4,
      byrow = FALSE,
      heights = unit(c(rep(2.5, 3), 1), "cm"),
      widths = unit(c(2.5, 9 / 16 * 2.5), "cm"),
      guides = "collect"
    )