是否可以在数据之前先绘制轴线?

Is it possible to draw the axis line first, before the data?

这是后续 ,我在其中寻找一种解决方案,先绘制轴,然后再绘制数据。答案适用于该特定问题和示例,但它提出了一个更普遍的问题,即如何更改底层 grob 的绘图顺序。首先是轴,然后是数据。

面板网格是否可以绘制在顶部的方式非常重要。

面板网格和轴 grob 的生成方式显然不同 - 轴更像是引导对象而不是“简单”的 grob。 (轴是用 ggplot2:::draw_axis() 绘制的,而面板网格是作为 ggplot2:::Layout 对象的一部分构建的)。

我想这就是轴绘制在顶部的原因,我想知道绘制顺序是否可以更改。

# An example to play with 

library(ggplot2)
df <- data.frame(var = "", val = 0)

ggplot(df) + 
  geom_point(aes(val, var), color = "red", size = 10) +
  scale_x_continuous(
    expand = c(0, 0),
    limits = c(0,1)
  ) +
  coord_cartesian(clip = "off") +
  theme_classic() 

这里有一个 hack,不需要“深入了解”,而是使用 patchwork 在顶部添加另一个层,即 geom 层。

a <- [your plot above]

library(patchwork)
a + inset_element(a + them_void(), left = 0, bottom = 0, right = 1, top = 1)

一个ggplot可以用它的gtable表示。 grob 的位置由 layout 元素和 "the z-column is used to define the drawing order of the grobs".

给出

包含点 grob 的 panelz 值可以增加,以便最后绘制。

所以如果 p 是你的情节那么

g <- ggplotGrob(p) ;
g$layout[g$layout$name == "panel", "z"] <-  max(g$layout$z) + 1L
grid::grid.draw(g)

但是, 这会改变轴的外观,可能 是由于面板被绘制在某些轴上。

但在

的令人振奋的新消息中

if we add theme(panel.background = element_rect(fill = NA)) to the plot, the axes are no longer partially obscured. This both proves that this is the cause of the thinner axis lines, and also provides a reasonable workaround, provided you don't need a colored panel background.

既然你正在寻找一个更“绘图级别”的解决方案,那么开始的地方就是问“首先如何绘制ggplot?”。答案可以在ggplot对象的print方法中找到:

ggplot2:::print.ggplot
#> function (x, newpage = is.null(vp), vp = NULL, ...) 
#> {
#>     set_last_plot(x)
#>    if (newpage) 
#>         grid.newpage()
#>     grDevices::recordGraphics(requireNamespace("ggplot2", 
#>         quietly = TRUE), list(), getNamespace("ggplot2"))
#>     data <- ggplot_build(x)
#>     gtable <- ggplot_gtable(data)
#>     if (is.null(vp)) {
#>         grid.draw(gtable)
#>     }
#>     else {
#>         if (is.character(vp)) 
#>             seekViewport(vp)
#>         else pushViewport(vp)
#>         grid.draw(gtable)
#>         upViewport()
#>     }
#>     invisible(x)
#> }

你可以看到 ggplot 实际上是通过在 ggplot 对象上调用 ggplot_build,然后在 ggplot_build.

的输出上调用 ggplot_gtable 绘制的

困难在于面板及其背景、网格线和数据被创建为不同的 grob 树。然后将其作为单个实体嵌套在 ggplot_build 生成的最终 grob table 中。轴线绘制在该面板的“顶部”。如果先绘制这些线,它们的部分粗细将被面板过度绘制。正如 user20650 的回答中提到的,如果你不需要你的情节有背景颜色,这不是问题。

据我所知,没有本地方法可以将轴线作为面板的一部分,除非您自己将它们添加为 grobs。

下面的小函数集允许您获取一个绘图对象,从中删除轴线并将轴线添加到面板中:

get_axis_grobs <- function(p_table)
{
  axes <- grep("axis", p_table$layout$name)
  axes[sapply(p_table$grobs[axes], function(x) class(x)[1] == "absoluteGrob")]
}

remove_lines_from_axis <- function(axis_grob)
{
  axis_grob$children[[grep("polyline", names(axis_grob$children))]] <- zeroGrob()
  axis_grob
}

remove_all_axis_lines <- function(p_table)
{
  axes <- get_axis_grobs(p_table)
  for(i in axes) p_table$grobs[[i]] <- remove_lines_from_axis(p_table$grobs[[i]])
  p_table
}

get_panel_grob <- function(p_table)
{
  p_table$grobs[[grep("panel", p_table$layout$name)]]
}

add_axis_lines_to_panel <- function(panel)
{
  old_order <- panel$childrenOrder
  panel <- grid::addGrob(panel, grid::linesGrob(x = unit(c(0, 0), "npc")))
  panel <- grid::addGrob(panel, grid::linesGrob(y = unit(c(0, 0), "npc")))
  panel$childrenOrder <- c(old_order[1], 
                           setdiff(panel$childrenOrder, old_order),
                           old_order[2:length(old_order)])
  panel
}

这些现在都可以整合到一个函数中,使整个过程变得更加容易:

underplot_axes <- function(p)
{
  p_built <- ggplot_build(p)
  p_table <- ggplot_gtable(p_built)
  p_table <- remove_all_axis_lines(p_table)
  p_table$grobs[[grep("panel", p_table$layout$name)]] <-
    add_axis_lines_to_panel(get_panel_grob(p_table))
  grid::grid.newpage()
  grid::grid.draw(p_table)
  invisible(p_table)
}

现在您只需在 ggplot 对象上调用 underplot_axes。我已经稍微修改了您的示例以创建一个灰色背景面板,以便我们可以更清楚地看到发生了什么:

library(ggplot2)

df <- data.frame(var = "", val = 0)

p <- ggplot(df) + 
  geom_point(aes(val, var), color = "red", size = 10) +
  scale_x_continuous(
    expand = c(0, 0),
    limits = c(0,1)
  ) +
  coord_cartesian(clip = "off") +
  theme_classic() +
  theme(panel.background = element_rect(fill = "gray90"))

p

underplot_axes(p)

reprex package (v0.3.0)

于 2021-05-07 创建

现在,您可能认为这是“创建假轴”,但我认为它更像是将轴线从 grob 树中的一个位置“移动”到另一个位置。遗憾的是,该选项似乎并未内置到 ggplot 中,但我也可以看到,需要对 ggplot 的构造方式进行重大改革才能允许该选项。