手动调整`geom_*`层的Z顺序

Bes*_*irl 5 r ggplot2

这是我之前关于如何根据给定条件是否满足对绘图区域进行着色的问题的后续问题。

当向图中添加多个geom_*图层ggplot2时,默认情况下,这些图层显然是按照它们添加到图中的顺序绘制的。例如,在我对上一个问题的回答geom_rect()中,在一层之后添加了一个半透明层geom_line()。结果,矩形重叠处的线条颜色略有变化;放大时这一点尤其明显:

样本图像

我想通过将geom_rect()图层放在图层后面(下方)来避免这种情况geom_line()

显而易见的解决方案是按该顺序添加层,即ggplot(...) + geom_rect(...) + geom_line()。这适用于我之前问题中的人工示例。

然而,在我的实际应用程序中,这并不那么容易。我有一个相对复杂的情节,我想使用几组不同的条件通过阴影来突出显示所述情节中的时期。我正在做的是

  1. 创建没有任何阴影的(复杂的)绘图,
  2. 将其保存到一个变量(例如g),然后,对于我想单独突出显示的每组条件,
  3. 执行g + geom_rect(...) + scale_fill_discrete(...)以添加所需的阴影。

在剩余图层之前添加geom_rect(...)将需要我为每个新版本重新创建具有不同突出显示的复杂绘图,我宁愿避免这种情况。

在我看来,显而易见的答案是明确地告诉ggplot2各层的 Z 顺序,例如ggplot(...) + geom_line(z.order = 0, ...) + geom_rect(z.order = -1, ...)在我的人工示例中。但我不知道如何做到这一点,或者是否可能。我在 Google 和 StackOverflow 中搜索了解决方案,但没有发现任何真正有帮助的东西,除了一些建议,这些建议实际上使用当前版本可能无法实现ggplot2

任何帮助将不胜感激。一如既往,谢谢!

ste*_*fan 6

这不是你的想法。但作为另一种方法,您可以使用绘图函数来创建复杂的绘图,并且可以将背景图层作为可选参数传递给该绘图。这样做可以避免重复代码来创建复杂的绘图。

根据您之前的问题和答案的数据和代码,这种方法可能看起来像这样。

注意:当然,您也可以创建第二个函数来创建背景图层。

library(ggplot2)

plot_fun <- function(layer_rect = NULL) {
  ggplot(dl, mapping = aes(x = x, y = bound_vals, color = Bounds)) +
    layer_rect +
    geom_line(linewidth = 1) +
    theme_light()
}

plot_fun()
#> Warning: Removed 9 rows containing missing values (`geom_line()`).
Run Code Online (Sandbox Code Playgroud)


rect <- list(
  geom_rect(
    data = iD, mapping = aes(xmin = iS, xmax = iE, fill = "Inversion"),
    ymin = -Inf, ymax = Inf, alpha = 0.3, inherit.aes = FALSE
  ),
  scale_fill_manual(name = "Inversions", values = "darkgray")
)

plot_fun(rect)
#> Warning: Removed 9 rows containing missing values (`geom_line()`).
Run Code Online (Sandbox Code Playgroud)

数据

library(tidyverse)
library(RcppRoll)

set.seed(42)
N <- 100
l <- 5
a <- rgamma(n = N, shape = 2)
d <- tibble(x = 1:N, upper = roll_maxr(a, n = l), lower = roll_minr(a + lag(a), n = l)) %>%
  mutate(
    inversion = upper < lower,
    inversionLag = if_else(is.na(lag(inversion)), FALSE, lag(inversion)),
    inversionLead = if_else(is.na(lead(inversion)), FALSE, lead(inversion)),
    inversionStart = inversion & !inversionLag,
    inversionEnd = inversion & !inversionLead
  )
dl <- pivot_longer(d, cols = c("upper", "lower"), names_to = "Bounds", values_to = "bound_vals")

iS <- d %>%
  filter(inversionStart) %>%
  select(x) %>%
  rowid_to_column() %>%
  rename(iS = x)
iE <- d %>%
  filter(inversionEnd) %>%
  select(x) %>%
  rowid_to_column() %>%
  rename(iE = x)
iD <- iS %>% full_join(iE, by = c("rowid"))
Run Code Online (Sandbox Code Playgroud)


Tje*_*ebo 5

我认为斯特凡的解决方案很棒。这只是为了表明您实际上可以手动更改几何图层的基本顺序。它要求你真的只有 geom_rect 层!

## make your plot
g <- ggplot(dl, mapping = aes(x = x, y = bound_vals, color = Bounds)) +
  geom_line(linewidth = 1) +
  geom_rect(data = iD, mapping = aes(xmin = iS, xmax = iE, fill = "Inversion"), ymin = -Inf, ymax = Inf, alpha = 0.3, inherit.aes = FALSE) +
  scale_fill_manual(name = "Inversions", values = "darkgray") +
  theme_light()

## get index of geom rect layer 
rect_ind <- which(lapply(g$layers, function(x) class(x$geom)[1]) == "GeomRect")

## manually change the order to put geom rect first
g$layers <- c(g$layers[rect_ind], g$layers[-rect_ind])
g
#> Warning: Removed 9 rows containing missing values (`geom_line()`).
Run Code Online (Sandbox Code Playgroud)

此处放大: 在此输入图像描述


Bes*_*irl 3

我最终使用了这个答案中的 Ricardo Saporta 的“方便的小函数” (h/t MrFlick 指出了这一点)——请注意,我删除了 的默认值after,因为我发现无论如何都需要传递它:

library(tidyverse)
library(RcppRoll)

# /sf/answers/1417512981/
insertLayer <- function(plotObj, after, ...) {
    if (after < 0)
        after <- after + length(P$layers)

    if (!length(plotObj$layers))
        plotObj$layers <- list(...)
    else
        plotObj$layers <- append(plotObj$layers, list(...), after)

    return(plotObj)
}

set.seed(42)
N <- 100
l <- 5
a <- rgamma(n = N, shape = 2)
d <- tibble(x = 1:N, upper = roll_maxr(a, n = l), lower = roll_minr(a + lag(a), n = l)) %>%
    mutate(inversion = upper < lower,
           inversionLag = if_else(is.na(lag(inversion)), FALSE, lag(inversion)),
            inversionLead = if_else(is.na(lead(inversion)), FALSE, lead(inversion)),
        inversionStart = inversion & !inversionLag,
        inversionEnd = inversion & !inversionLead
    )
dl <- pivot_longer(d, cols = c("upper", "lower"), names_to = "Bounds", values_to = "bound_vals")

iS <- d %>% filter(inversionStart) %>% select(x) %>% rowid_to_column() %>% rename(iS = x)
iE <- d %>% filter(inversionEnd) %>% select(x) %>% rowid_to_column() %>% rename(iE = x)
iD <- iS %>% full_join(iE, by = c("rowid"))

g <- ggplot(dl, mapping = aes(x = x, y = bound_vals, color = Bounds)) +
    geom_line(linewidth = 1)

g %>%
    insertLayer(
        after = 0,
        geom_rect(data = iD, mapping = aes(xmin = iS, xmax = iE, fill = "Inversion"), ymin = -Inf, ymax = Inf, alpha = 0.3, inherit.aes = FALSE)
    ) +
    scale_fill_manual(name = "Inversions", values = "darkgray") +
    theme_light() -> g
g
Run Code Online (Sandbox Code Playgroud)

这给出了

样本图像

我认为这是一种相当直观和灵活的方式,没有什么大惊小怪。唯一的缺点是您必须使用%>%(或|>) 而不是+,并且您可能需要括号,因为管道运算符比 结合得更紧密+。但这也是ggplot2选择使用的结果+,仅代表轻微的不便。