是否可以在数据之前先绘制轴线?
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 的 panel
的 z
值可以增加,以便最后绘制。
所以如果 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 的构造方式进行重大改革才能允许该选项。
这是后续
面板网格是否可以绘制在顶部的方式非常重要。
面板网格和轴 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 的 panel
的 z
值可以增加,以便最后绘制。
所以如果 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 的构造方式进行重大改革才能允许该选项。