如何获取包括越界对象的图的尺寸

How to get dimensions of a plot including out of bounds objects

我可以这样计算地块的高度:

library(ggplot2)
library(egg)
library(gridExtra)

g <- ggplot(iris, aes(x = Species, y = Petal.Length)) +
  stat_summary(geom = 'bar', fun.y = mean) +
  geom_point() +
  scale_y_continuous(limits = c(0,8), expand = c(0,0), oob = function(x, ...) x)

gt <- egg::set_panel_size(g)
gt$layout$clip[gt$layout$name=="panel"] <- "off"
gridExtra::grid.arrange(gt)

sum(as.numeric(grid::convertUnit(gt$heights, "mm")))

但是如果我有一些越界的 geom 对象,它 returns 相同的高度:

g <- ggplot(iris, aes(x = Species, y = Petal.Length)) +
  stat_summary(geom = 'bar', fun.y = mean) +
  geom_point() +
  scale_y_continuous(limits = c(0,8), expand = c(0,0), oob = function(x, ...) x) +
  geom_text(label = 'obText', aes(x = 2, y = 8.5))

gt <- egg::set_panel_size(g)
gt$layout$clip[gt$layout$name=="panel"] <- "off"
gridExtra::grid.arrange(gt)

sum(as.numeric(grid::convertUnit(gt$heights, "mm")))

即使现在有文本的位置高于 53.35411mm。

有没有办法获取包含此越界文本的图的高度?

我不确定你的用例是什么,但是是的,这是可能的。

问题的症结在于文本(或任何其他geom层)的高度不是gt$heights中捕获的,而是在高度参数中相应的 grob(表示为 .$x$height)在嵌套层次结构的下方。

# same code as yours, except that I positioned the label ever further up, to increase the contrast
g <- ggplot(iris, aes(x = Species, y = Petal.Length)) +
  stat_summary(geom = 'bar', fun.y = mean) +
  geom_point() +
  scale_y_continuous(limits = c(0,8), expand = c(0,0), oob = function(x, ...) x) +
  geom_text(label = 'obText', aes(x = 2, y = 18.5))    

gt <- egg::set_panel_size(g)

看看gt$heights。我们可以验证所有高度值都保持不变,无论面板的裁剪是否已关闭:

> gt$heights
 [1] 5.5pt                    0cm                      0cm                     
 [4] 0cm                      0cm                      0cm                     
 [7] 4cm                      sum(2.75pt, 1grobheight) 1grobheight             
[10] 0cm                      0pt                      5.5pt    

gt$layout$clip[gt$layout$name=="panel"] <- "off"

> gt$heights
 [1] 5.5pt                    0cm                      0cm                     
 [4] 0cm                      0cm                      0cm                     
 [7] 4cm                      sum(2.75pt, 1grobheight) 1grobheight             
[10] 0cm                      0pt                      5.5pt   

在上述所有值中,您应该关心的是 [7] 4cm,因为那是面板的高度。我们知道,因为根据 gt 的布局,面板位于第 7 行和第 5 列,可以通过检查 gt 本身的控制台打印输出或通过 gtable_show_layout() 来验证来自 gtable 包:

> gt
TableGrob (12 x 9) "layout": 18 grobs
    z         cells       name                                           grob
1   0 ( 1-12, 1- 9) background               rect[plot.background..rect.3020]
2   5 ( 6- 6, 4- 4)     spacer                                 zeroGrob[NULL]
3   7 ( 7- 7, 4- 4)     axis-l           absoluteGrob[GRID.absoluteGrob.3008]
4   3 ( 8- 8, 4- 4)     spacer                                 zeroGrob[NULL]
5   6 ( 6- 6, 5- 5)     axis-t                                 zeroGrob[NULL]
6   1 ( 7- 7, 5- 5)      panel                      gTree[panel-1.gTree.2994]
7   9 ( 8- 8, 5- 5)     axis-b           absoluteGrob[GRID.absoluteGrob.3001]
8   4 ( 6- 6, 6- 6)     spacer                                 zeroGrob[NULL]
9   8 ( 7- 7, 6- 6)     axis-r                                 zeroGrob[NULL]
10  2 ( 8- 8, 6- 6)     spacer                                 zeroGrob[NULL]
11 10 ( 5- 5, 5- 5)     xlab-t                                 zeroGrob[NULL]
12 11 ( 9- 9, 5- 5)     xlab-b titleGrob[axis.title.x.bottom..titleGrob.3011]
13 12 ( 7- 7, 3- 3)     ylab-l   titleGrob[axis.title.y.left..titleGrob.3014]
14 13 ( 7- 7, 7- 7)     ylab-r                                 zeroGrob[NULL]
15 14 ( 4- 4, 5- 5)   subtitle         zeroGrob[plot.subtitle..zeroGrob.3016]
16 15 ( 3- 3, 5- 5)      title            zeroGrob[plot.title..zeroGrob.3015]
17 16 (10-10, 5- 5)    caption          zeroGrob[plot.caption..zeroGrob.3018]
18 17 ( 2- 2, 2- 2)        tag              zeroGrob[plot.tag..zeroGrob.3017]

> gtable::gtable_show_layout(gt)

要获取单个 geom 层的高度,我们可以深入挖掘以查看面板 grob 的 children grob:

> gt$grobs[[which(gt$layout$name == "panel")]]$children
(gTree[grill.gTree.2992], zeroGrob[NULL], rect[geom_rect.rect.2978], 
points[geom_point.points.2980], text[GRID.text.2981], zeroGrob[NULL], 
zeroGrob[panel.border..zeroGrob.2982]) 

在这种情况下,我们知道(因为示例是以这种方式创建的)有问题的 geom 是文本层,所以我们可以直接转到第 5 个 children grob 并查看高度那里:

> gt$grobs[[which(gt$layout$name == "panel")]]$children[[5]]$y
[1] 2.3125native 2.3125native 2.3125native 2.3125native 2.3125native 2.3125native
[7] 2.3125native 2.3125native 2.3125native 2.3125native 2.3125native 2.3125native
...

参考网格包中的 ?unit 帮助文件,"native" 坐标系表示测量值与视口的 xscale 和 yscale 相关。因此 2.3125native 可以解释为 2.3125 x 面板高度 (4cm) = 9.25cm。

更一般地说,要获得两个方向的高度限制:

# rect grobs such as those created by geom_bar() have "height" / "width" measurements,
# while point & text grobs have "y" / "x" measurements, & we look for both
max.grob.heights <- sapply(gt$grob[[which(gt$layout$name == "panel")]]$children,
                       function(x) ifelse(!is.null(x$height) & "unit" %in% class(x$height),
                                          max(as.numeric(x$height)),
                                          ifelse(!is.null(x$y) & "unit" %in% class(x$y),
                                                 max(as.numeric(x$y)),
                                                 0)))
max.grob.heights = max(max.grob.heights)

min.grob.heights <- sapply(gt$grob[[which(gt$layout$name == "panel")]]$children,
                           function(x) ifelse(!is.null(x$height) & "unit" %in% class(x$height),
                                              min(as.numeric(x$height)),
                                              ifelse(!is.null(x$y) & "unit" %in% class(x$y),
                                                     min(as.numeric(x$y)),
                                                     0)))
min.grob.heights = min(min.grob.heights)

# identify panel row & calculate panel height
panel.row <- gt$layout[gt$layout$name == "panel", "t"] # = 7
panel.height <- as.numeric(grid::convertUnit(gt$heights[panel.row],"mm"))

如果您只想要面板组件的高度,包括所有几何层(并且不关心它们与整体 grob object 的组合高度的关系),您可以使用 max / min grob 高度作为面板高度的乘数:

panel.multiplier <- max(1, max.grob.heights) + abs(min.grob.heights)
result <- panel.multiplier * panel.height

如果你想计算地块的整体高度,你必须分别比较顶部/底部的高度out-of-bounds objects:如果它们在范围内情节,使用原来的高度;如果他们超过那个,请改用他们的身高。

# calculate height of all the grobs above the panel
height.above.panel <- gt$heights[1:(panel.row - 1)]
height.above.panel <- sum(as.numeric(grid::convertUnit(height.above.panel, "mm")))

# check whether the out-of-bound object (if any) exceeds this height, & replace if necessary
if(max.grob.heights > 1){
  oob.height.above.panel <- (max.grob.heights - 1) * panel.height
  height.above.panel <- max(height.above.panel, oob.height.above.panel)
}

# as above, calculate the height of all the grobs below the panel
height.below.panel <- gt$heights[(panel.row + 1):length(gt$heights)]
height.below.panel <- sum(as.numeric(grid::convertUnit(height.below.panel, "mm")))

# as above
if(min.grob.heights < 0){
  oob.height.below.panel <- abs(min.grob.heights) * panel.height
  height.below.panel <- max(height.below.panel, oob.height.below.panel)
}

# sum the result
result <- height.above.panel + panel.height + height.below.panel