如何在ggplot2的多个Geom对象间对齐尺度变换?
自定义geom与尺度变换结合时图层坐标不匹配的解决方法
首先得搞清楚你遇到问题的核心原因——你对ggplot图层的处理顺序理解错了!
你把数据变换(foo()函数)放在了GeomFoo的setup_data方法里,但ggplot的setup_data是在尺度变换之后执行的,它处理的是已经转换到「绘图空间」的坐标值,而不是原始的「数据空间」数值。这就导致了:
- 对于
geom_foo图层:原始y值(1、2)先经过尺度变换foo_trans变成1/5=0.2、2/5=0.4(绘图空间),然后setup_data里再加2,最终绘图坐标是2.2、2.4 - 对于
geom_text图层:你传入的是已经在数据空间做了foo()变换的y值(3、4),经过尺度变换后变成3/5=0.6、4/5=0.8(绘图空间)
两者的绘图坐标完全错位,自然就不匹配了。
解决方案:把数据变换移到Stat里
ggplot的Stat组件是在尺度变换之前处理原始数据的,正好符合你的需求:先在数据空间做foo()变换,再统一应用尺度变换。我们只需要调整一下自定义geom的结构:
1. 定义自定义Stat
把原来setup_data里的foo()变换逻辑移到Stat的compute_group方法中:
StatFoo <- ggproto("StatFoo", StatIdentity, compute_group = function(data, scales) { cols_to_keep <- setdiff(names(data), c("x", "y")) cbind(foo(data$x, data$y), data[, cols_to_keep]) } )
2. 简化Geom并更新geom_foo构造函数
现在GeomFoo不需要再做数据变换了,直接继承GeomPoint即可;同时把图层的stat参数换成我们自定义的StatFoo:
GeomFoo <- ggproto("GeomFoo", GeomPoint) geom_foo <- function(mapping = NULL, data = NULL, ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) { layer( data = data, mapping = mapping, stat = StatFoo, # 替换为自定义Stat geom = GeomFoo, position = "identity", show.legend = show.legend, inherit.aes = inherit.aes, params = list(na.rm = na.rm, ...) ) }
3. 测试验证
用你原来的代码测试,不管有没有尺度变换,两个图层的坐标都会完美匹配:
先准备好基础数据和变换函数:
foo <- function(x, y) { data.frame( x = x + 2, y = y + 2 ) } foo_trans <- scales::trans_new( name = "foo", transform = function(x) x / 5, inverse = function(x) x * 5 ) df1 <- data.frame(x = c(1, 2), y = c(1, 2)) df_label <- foo(df1$x, df1$y) df_label$label <- c("A", "B")
无尺度变换的情况:
library(ggplot2) p <- ggplot(df1, aes(x = x, y = y)) + geom_foo(color = "red", size = 6) + geom_text(data = df_label, aes(x, y, label = label)) p
应用尺度变换的情况:
p + scale_y_continuous(trans = foo_trans)
现在两个图层的处理逻辑完全对齐了:
geom_foo:原始数据 → Stat做foo()变换 → 尺度变换到绘图空间 → 绘制geom_text:已做foo()变换的数据 → 尺度变换到绘图空间 → 绘制
最终绘图空间的坐标完全一致,自然就匹配了。
内容的提问来源于stack exchange,提问作者markus
相关产品推荐
相关产品推荐

