问题描述
这是 to my previous question 的后续内容,我一直在寻找一种解决方案,先绘制轴,然后绘制数据。答案适用于该特定问题和示例,但它提出了一个更一般的问题,即如何更改底层 grobs 的绘图顺序。首先是轴,然后是数据。
非常类似于可以在顶部绘制或不绘制面板网格。
面板网格和轴格布显然是不同的——轴更多地作为引导对象而不是“简单”格布。 (轴是用 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()
附言我不会接受甚至不赞成制造假轴的答案,因为这不是我正在寻找或试图理解的。
解决方法
一个 ggplot
可以用它的 gtable
来表示。 grob 的位置由 layout
元素和 "the z-column is used to define the drawing order of the grobs" 给出。
然后可以增加 z
的 panel
值,其中包含点 grob,以便最后绘制。
所以如果 p
是你的情节,那么
g <- ggplotGrob(p) ;
g$layout[g$layout$name == "panel","z"] <- max(g$layout$z) + 1L
grid::grid.draw(g)
然而,as noted in the comment 这会改变轴的外观,这可能是由于面板被绘制在一些轴上。
但是来自dww
的令人兴奋的新消息如果我们将 theme(panel.background = element_rect(fill = NA))
添加到图中,轴不再被部分遮挡。这既证明这是导致轴线变细的原因,也提供了一个合理的解决方法,前提是您不需要彩色面板背景。
既然您正在寻找更多“在绘制级别”的解决方案,那么开始的地方是询问“首先如何绘制 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_build
,然后在 ggplot_gtable
的输出上调用 ggplot_build
,您可以看到实际上绘制了 ggplot。
难点在于面板及其背景、网格线和数据被创建为一个独特的 grob 树。然后将其作为单个实体嵌套在由 ggplot_build
生成的最终 grob 表中。轴线绘制在该面板的“顶部”。如果先绘制这些线,它们的一部分粗细将被面板过度绘制。正如 user20650 的回答中提到的,如果您不需要您的绘图具有背景颜色,这不是问题。
据我所知,除非您自己将轴线添加为 grob,否则无法将轴线作为面板的一部分包含在内。
以下小功能套件允许您获取绘图对象,从中删除轴线并将轴线添加到面板中:
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,"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,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 年 5 月 7 日创建
现在,您可能会认为这是“创建假轴”,但我认为它更像是将轴线从 Grob 树中的一个位置“移动”到另一个位置。遗憾的是,该选项似乎没有内置到 ggplot 中,但我也可以看到,它需要对 ggplot 的构造方式进行重大改革以允许该选项。
,这里有一个 hack,不需要“深入了解”,而是使用 patchwork
在顶部添加另一个层,这只是 geom 层。
a <- [your plot above]
library(patchwork)
a + inset_element(a + them_void(),left = 0,bottom = 0,right = 1,top = 1)