代码之家  ›  专栏  ›  技术社区  ›  John Gagnon

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

  •  3
  • John Gagnon  · 技术社区  · 7 年前

    我可以计算出这样一个图的高度:

    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对象,它将返回相同的高度:

    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.35411毫米。

    有没有办法得到包括这个越界文本在内的绘图高度?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Z.Lin    7 年前

    我不知道你的用例是什么,但是是的,这是可能的。

    问题的关键在于文本(或任何其他geom层)的高度是 捕获 gt$heights ,但在各个Grobs的高度参数中(表示为 .$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$高度 是的。我们可以验证所有高度值是否保持不变,无论面板的剪裁是否已关闭:

    > 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列,可以通过检查控制台打印输出来验证 燃气轮机 本身,或通过 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)
    

    plot

    为了得到各个geom层的高度,我们可以更深入地研究panel grob的子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个孩子的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 网格包中的帮助文件“本机”坐标系意味着测量是相对于视口的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对象的组合高度之间的关系),可以使用最大/最小Grob高度作为面板高度的倍增:

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

    如果要计算绘图的总高度,则必须分别比较顶部/底部越界对象的高度:如果它们在绘图的范围内,则使用原始高度;如果它们超过该范围,则使用它们的高度。

    # 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