8

我正在研究一个自定义的 ggplot2 主题,并认为根据绘图对象的某些特征自动修改主题元素可能很不错。例如,有没有办法指定如果绘图包含构面,则为每个面板添加边框?

我想问题是真的,我可以从自定义 theme() 调用中访问当前的 gg 对象,然后有条件地应用某些主题元素吗?在我的脑海中,我会将我的主题功能定义为:

theme_custom <- function() {
  if (plot$facet$params > 0) {
  theme_minimal() +
    theme(panel.border = element_rect(color = "gray 50", fill = NA))
  }
  else {
    theme_minimal()
    }
}

如果这是可能的,它在使用中看起来像这样:

library(ggplot2)

# plot with facets automatically adds panel borders
ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  facet_wrap(vars(cyl)) +
  theme_custom()

# plot without facets no panel border
ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  theme_custom() 

注意:这最初是在RStudio 社区上发布的,但没有收到答案。

4

3 回答 3

7

我认为奥利弗的想法是正确的。

我不认为该theme_custom函数是检查条件主题图的正确位置,因为主题函数大多不知道它们被添加到的精确图。

相反,我认为检查情节的适当位置是在情节中添加主题时。您可以编写如下所示的主题函数,为输出设置不同的类。

theme_custom <- function() {
  out <- theme_minimal()
  class(out) <- c("conditional_theme", class(out))
  out
}

现在,每次将主题添加到情节时,都是通过函数完成的,我们可以为类ggplot_add.theme重写。conditional_theme在我看来,检查情节是否刻面的正确方法是检查插槽的类,当添加了适当的刻面时plot$facet,可以是等,并且默认为未设置刻面时。FacetGridFacetWrapFacetNull

ggplot_add.conditional_theme <- function(object, plot, object_name) {
  if (!inherits(plot$facet, "FacetNull")) {
    object <- object + theme(panel.border = element_rect(colour = "grey50", fill = NA))
  }
  plot$theme <- ggplot2:::add_theme(plot$theme, object, object_name)
  plot
}

现在用例应该按预期工作:

ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  facet_wrap(vars(cyl)) +
  theme_custom()

在此处输入图像描述

ggplot(mtcars, aes(mpg, wt)) +
  geom_point() +
  theme_custom() 

在此处输入图像描述

唯一的缺点是您每次都必须将主题添加到情节中,并且您不能使用 将theme_set(theme_custom())其应用于会话中的任何情节。

于 2019-09-30T13:46:37.920 回答
0

这需要比我目前在ggprotoggproto_method对象方面的专业知识水平更多的知识。所以这不是一个完整的答案,而是一个可能的方向。

如果您可以访问绘图ggproto对象,则此对象包含ggproto_method存储在ggproto$facet$compute_layout. 根据绘图是否包含对 的调用geom_facet,这将具有不同的函数长度,如下图所示

data(mtcars)
library(ggplot2)
p <- ggplot(mtcars, mapping = aes(x = hp, y = mpg)) + 
    geom_point()
pfacet <- p + facet_wrap(.~cyl)

nchar(format(p$facet$compute_layout))
[1] 139
nchar(format(pfacet$facet$compute_layout))
[1] 1107

(请注意,139 似乎是任何ggproto不包含刻面的标准)

这假设您可以在每次调用 plot 时访问 proto 对象,或者您将方法作为调用之后的调用facet_wrap或类似方法,并且由于我对复杂的知识缺乏了解,这实际上只是一个 hacky 方法gg,ggprotoggproto_method对象。

于 2019-09-29T10:46:49.253 回答
0

从一篇关于有条件地添加 ggplot 元素的相关文章中可以看出,可以使用{if(cond)expr}+格式添加元素,即将整个元素放入,{}然后使用+. 可以将其与主题元素替换格式结合起来,例如

theme_minimal() %+replace% theme(axis.title.y.right = element_text(angle = 90)) +

给予:

{if(cond) theme() %+replace% theme(element = value)} +

所以,无耻地从@teunbrand 的答案中窃取(/站在巨大的肩膀上):

{if (!inherits(plot$facet, "FacetNull")) theme() %+replace% theme(panel.border = element_rect(colour = "grey50", fill = NA))} +

这适用于我的代码,但我不能 100% 确定您的示例,很抱歉在一个巨大的函数编辑过程中没有进行测试,但想分享这种方法以使其具有普遍适用性。这种方法的一个好处是很容易在相同的条件下链接元素编辑,并在它们自己的{if}.

于 2020-01-31T20:58:02.927 回答