如何为ggplot2水平色条添加超界值指示三角形?求通用方案
为水平色条添加超界值指示三角形的解决方案
问题背景
此前Stack Overflow上已有为垂直色条添加超界值指示三角形的方案,但直接套用在水平色条上会失效。参考建议修改代码后已实现功能,但不清楚代码原理,同时希望了解更优雅的实现方式。
修改后的可运行代码
以下是适配水平色条的自定义色条代码,搭配示例使用:
library(ggplot2) library(gtable) library(grid) my_triangle_colourbar <- function(...) { guide <- guide_colourbar(...) class(guide) <- c("my_triangle_colourbar", class(guide)) guide } guide_gengrob.my_triangle_colourbar <- function(...) { # 先生成默认色条 guide <- NextMethod() # 提取色条组件与首尾颜色 is_bar <- grep("^bar$", guide$layout$name) bar <- guide$grobs[[is_bar]] extremes <- c(bar$raster[1], bar$raster[length(bar$raster)]) # 获取色条尺寸,确定三角形大小 width <- guide$widths[guide$layout$l[is_bar]] height <- guide$heights[guide$layout$t[is_bar]] short <- min(convertUnit(width, "cm", valueOnly = TRUE), convertUnit(height, "cm", valueOnly = TRUE)) # 为三角形预留左右空间 guide <- gtable_add_cols(guide, unit(short, "cm"), guide$layout$l[is_bar] - 1) guide <- gtable_add_cols(guide, unit(short, "cm"), guide$layout$l[is_bar]) # 绘制左右三角形(对应下限/上限超界) left <- polygonGrob( x = unit(c(0, 1, 1), "npc"), y = unit(c(.5, 0, 1), "npc"), gp = gpar(fill = extremes[1], col = NA) ) right <- polygonGrob( x = unit(c(0, 0, 1), "npc"), y = unit(c(0, 1, .5), "npc"), gp = gpar(fill = extremes[2], col = NA) ) # 将三角形添加到色条对应位置 guide <- gtable_add_grob( guide, left, t = guide$layout$t[is_bar], l = guide$layout$l[is_bar] - 1 ) guide <- gtable_add_grob( guide, right, t = guide$layout$t[is_bar], l = guide$layout$l[is_bar] + 1 ) return(guide) } # 示例使用 df <- data.frame(x = c(1:10), y = c(1:10), z = seq(1,20,2)) ggplot(df) + geom_point(mapping = aes(x = x, y = y, color = z)) + scale_color_viridis_c(limits = c(1,10), oob = scales::squish, guide = my_triangle_colourbar()) + theme(legend.position = 'bottom')
代码原理拆解
- 自定义引导类:
my_triangle_colourbar函数基于默认guide_colourbar创建,通过添加自定义类my_triangle_colourbar,触发后续的自定义渲染方法。 - 生成基础色条:
NextMethod()调用默认逻辑生成标准水平色条的grob(图形对象)。 - 提取关键信息:从色条的布局中定位色条组件,获取色条首尾的颜色(对应超界值的填充色),并通过色条的宽高计算三角形的尺寸(取宽高中较小值,保证三角形比例协调)。
- 预留空间:使用
gtable_add_cols在色条左右两侧各添加一列,为放置三角形腾出位置。 - 绘制三角形:用
polygonGrob基于归一化坐标(npc)绘制三角形,左侧三角形对应下限超界,右侧对应上限超界,确保三角形与色条高度对齐。 - 组装色条:将绘制好的三角形grob添加到预留的列中,与色条的行位置对齐,最终组合成完整的带三角形的色条。
更优雅的实现方式
方案1:使用ggh4x包(推荐)
ggh4x包提供了扩展的色条引导功能,无需手动操作底层gtable和grid元素,直接支持为色条添加超界指示符号:
library(ggplot2) library(ggh4x) df <- data.frame(x = c(1:10), y = c(1:10), z = seq(1,20,2)) ggplot(df) + geom_point(mapping = aes(x = x, y = y, color = z)) + scale_color_viridis_c(limits = c(1,10), oob = scales::squish, guide = guide_colourbar_extend( extend = c("left", "right"), # 左右都添加扩展符号 shape = c(24, 25), # 左侧下三角、右侧上三角 fill = scales::viridis_pal()(2) # 自动取色条首尾颜色 )) + theme(legend.position = 'bottom')
该方案优势:代码简洁、易维护,可灵活调整符号的形状、颜色、大小,同时支持水平/垂直色条。
方案2:优化自定义代码(通用水平/垂直色条)
修改原代码,自动识别色条方向(水平/垂直),实现通用的超界三角形添加:
library(ggplot2) library(gtable) library(grid) my_triangle_colourbar <- function(...) { guide <- guide_colourbar(...) class(guide) <- c("my_triangle_colourbar", class(guide)) guide } guide_gengrob.my_triangle_colourbar <- function(...) { guide <- NextMethod() # 定位色条组件 is_bar <- grep("^bar$", guide$layout$name) bar <- guide$grobs[[is_bar]] extremes <- c(bar$raster[1], bar$raster[length(bar$raster)]) # 判断色条方向 width <- convertUnit(guide$widths[guide$layout$l[is_bar]], "cm", valueOnly = TRUE) height <- convertUnit(guide$heights[guide$layout$t[is_bar]], "cm", valueOnly = TRUE) is_horizontal <- width > height tri_size <- min(width, height) # 按方向处理空间与三角形 if (is_horizontal) { # 水平色条:左右加列 guide <- gtable_add_cols(guide, unit(tri_size, "cm"), guide$layout$l[is_bar] - 1) guide <- gtable_add_cols(guide, unit(tri_size, "cm"), guide$layout$l[is_bar]) # 绘制三角形 left_tri <- polygonGrob( x = unit(c(0, 1, 1), "npc"), y = unit(c(.5, 0, 1), "npc"), gp = gpar(fill = extremes[1], col = NA) ) right_tri <- polygonGrob( x = unit(c(0, 0, 1), "npc"), y = unit(c(0, 1, .5), "npc"), gp = gpar(fill = extremes[2], col = NA) ) # 添加到色条 guide <- gtable_add_grob(guide, left_tri, t = guide$layout$t[is_bar], l = guide$layout$l[is_bar]-1) guide <- gtable_add_grob(guide, right_tri, t = guide$layout$t[is_bar], l = guide$layout$l[is_bar]+1) } else { # 垂直色条:上下加行 guide <- gtable_add_rows(guide, unit(tri_size, "cm"), guide$layout$t[is_bar]-1) guide <- gtable_add_rows(guide, unit(tri_size, "cm"), guide$layout$t[is_bar]) # 绘制三角形 bottom_tri <- polygonGrob( x = unit(c(.5, 0, 1), "npc"), y = unit(c(0, 1, 1), "npc"), gp = gpar(fill = extremes[1], col = NA) ) top_tri <- polygonGrob( x = unit(c(0, 1, .5), "npc"), y = unit(c(0, 0, 1), "npc"), gp = gpar(fill = extremes[2], col = NA) ) # 添加到色条 guide <- gtable_add_grob(guide, bottom_tri, t = guide$layout$t[is_bar]-1, l = guide$layout$l[is_bar]) guide <- gtable_add_grob(guide, top_tri, t = guide$layout$t[is_bar]+1, l = guide$layout$l[is_bar]) } return(guide) }
该方案优势:一套代码同时支持水平和垂直色条,适配性更强。
内容的提问来源于stack exchange,提问作者Riccardo
相关产品推荐
相关产品推荐

