使用patchwork的wrap_plots()组合带unit()尺寸子布局出错的解决方法
解决patchwork组合带unit()固定宽高的子布局时的索引越界错误及图例布局需求
问题描述
使用patchwork包将6个绘图排列为3行2列布局,单独创建每列时用grid::unit()设置固定宽高可正常运行,但组合两个已封装的列时触发错误:
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)
补充需求说明
前3个绘图为相同的geom_raster()图,需要共享一个居中显示在第一列下方的填充图例,直接用单次wrap_plots()组合所有绘图无法实现该图例布局,相关测试代码如下:
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" )
解决方案
方案1:移除子布局的全局尺寸参数,在最外层统一设置
错误根源是嵌套的wrap_plots()重复设置widths参数导致布局参数冲突,将子布局的widths移除,放到最外层的wrap_plots()中统一指定,同时保留子布局的guides="collect"实现第一列图例共享:
# 修改子布局定义,移除widths参数 col1 <- wrap_plots( p_tmp[1:3], ncol = 1, heights = heights, guides = "collect" # 收集第一列的图例到下方 ) col2 <- wrap_plots( p_tmp[4:6], ncol = 1, heights = heights ) # 最外层组合时设置两列的固定宽度 wrap_plots(col1, col2, ncol = 2, widths = unit(c(2, 9/16*2), "cm"))
方案2:用wrap_elements()封装子布局,单独控制尺寸
如果需要在子布局层面单独控制尺寸,使用wrap_elements()包裹子布局,避免unit参数冲突:
# 用wrap_elements封装子布局,单独设置宽度 col1 <- wrap_elements( wrap_plots( p_tmp[1:3], ncol = 1, heights = heights, guides = "collect" ), width = unit(2, "cm") ) col2 <- wrap_elements( wrap_plots( p_tmp[4:6], ncol = 1, heights = heights ), width = unit(9/16*2, "cm") ) # 组合两个封装后的布局 wrap_plots(col1, col2, ncol = 2)
两种方案都能解决索引越界错误,同时保留固定尺寸需求,且guides="collect"会将第一列的三个raster图的图例合并显示在第一列下方,满足布局需求。
内容的提问来源于stack exchange,提问作者Excelsior
相关产品推荐
相关产品推荐

