如何使用 R(最好是 ggplot2)生成堆叠平面或重叠菱形的图形?

How to produce a graphic of stacked planes or overlapping diamonds using R (and ideally ggplot2)?

在提高自己技能的同时,我看到了非常出色的 ggplot2 workshop 通过了解它在基本层面上的工作原理,让自己更好地使用这个包。

作为该研讨会的一部分,我对研讨会中使用的一种可视化效果感到震惊,因为它对于解释分层的依赖关系特别有用,我想弄清楚如何生成这样的图片(最好使用 R)。

这两张图片显示了我试图重现的可视化的两个部分: 带标签的堆叠平面:

堆叠平面,大部分为透明胶片和标签(适当突出显示):

我已经能够使用 rgl 生成类似的东西,但它远没有那么好。鉴于我正在尝试提高 ggplot2 的技能,我希望能够使用 ggplot2(或其扩展之一)来制作它,因为这将使我能够更轻松地控制图形的一些“细节”。

是否可以使用 ggplot2 或扩展包?

在rgl中生成它的代码是:

library(rgl)
# Create some dummy data
dat <- replicate(2, 1:3)

# Initialize the scene, no data plotted
# hardcoded user matrix of a particular view (so I can go straight to that view each time)
userMatrix_orig <- matrix(c(-0.7069399, -0.2729415, 0.6524867, 0.0000000, 0.7072651, -0.2773000, 0.6502926, 0.0000000, 0.003442926, 0.921199083, 0.389076293, 0.000000000, 0, 0, 0, 1), nrow = 4 )

plot3d(dat, type = 'n', xlim = c(-1, 1), ylim = c(-1, 1), zlim = c(-10, 10), 
       xlab = '', ylab = '', zlab = '', axes=FALSE) 
view3d(userMatrix=userMatrix_orig)
material3d(alpha=1.0)
# Add planes
planes3d(1, 1, 1, -2, col = 'paleturquoise', alpha = 0.8, name="hello")
planes3d(1, 1, 1, -4, col = 'palegreen', alpha = 0.8)
planes3d(1, 1, 1, -6, col = 'palevioletred', alpha = 0.8)
planes3d(1, 1, 1, -8, col = 'midnightblue', alpha = 0.8)
planes3d(1, 1, 1, 0, col = 'red', alpha = 0.8)
planes3d(1, 1, 1, 2, col = 'green', alpha = 0.8)
planes3d(1, 1, 1, 4, col = 'orange', alpha = 0.8)
planes3d(1, 1, 1, 6, col = 'blue', alpha = 0.8)

# Label the planes
family_val <- c("sans")
adj_val <- 1
cex_val <- 2.5
text3d(x=1, y =-1, z = -6, texts="data", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = -4, texts="mapping", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = -2, texts="statistics", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 0, texts="scales", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 2, texts="geometries", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 4, texts="facets", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 6, texts="coordinates", adj = adj_val, family = family_val, cex = cex_val )
text3d(x=1, y =-1, z = 8, texts="theme", adj = adj_val, family = family_val, cex = cex_val )

我用它制作的图形是:

我将使用如下函数在 ggplot 中重新创建图像:

make_graphic <- function(highlight = 1:8) {
  
  library(ggplot2)
  alpha_vals <- c(0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2)
  
  alpha_vals[highlight] <- 1
  
  df <- data.frame(x = rep(c(0.5, 0.75, 1, 0.75, 0.5), 8),
                   y = rep(c(0.5, 0, 0.5, 1, 0.5), 8) + rep(0:7, each = 5)/2,
                   z = rep(LETTERS[1:8], each = 5))
  
  ggplot(df, aes(x, y)) +
    geom_polygon(aes(fill = z, alpha = z)) +
    geom_text(data = data.frame(x = 0.48, y = rev(0.5 + (0:7)/2),
                                z = rev(LETTERS[1:8]),
                                a = c("THEME", "COORDINATES", "FACETS",
                                      "GEOMETRIES", "SCALES", "STATISTICS", 
                                      "MAPPING", "DATA")), fontface = 2,
              family = "opencondensed",
              aes(label = a, alpha = z), colour = "white", size = 10, hjust = 1) +
    scale_x_continuous(limits = c(0.2, 1)) +
    scale_fill_manual(values = c("#a6aaa9", "#ef4e47", "#34a5da", "#ff9d35", 
                                 "#8abe5e", "#ffe989", "#c52060", "#3f969a")) +
    scale_alpha_manual(values = alpha_vals) +
    theme_void() +
    theme(legend.position = "none",
          plot.background = element_rect(fill = "#222222"))
}

这样可以通过以下操作轻松地重新创建图形:

make_graphic()

如果你只想突出显示第二个底部项目,你可以这样做:

make_graphic(2)

这是一个尝试。

数据

library(dplyr)
mydata <- data.frame(
  label = c("THEME", "COORDINATES", "FACETS", "GEOMETRIES", "SCALES", "STATISTICS", "MAPPING", "DATA"),
  ybase = 8:1,
  color = c("#3f969a", "#c52060", "#ffe989", "#8abe5e", "#ff9d35", "#34a5da", "#ef4e47", "#a6aaa9")
) %>%
  rowwise() %>%
  mutate(
    xs = list(c(0, 2, 0, -2)),
    ys = lapply(ybase, `+`, c(1.1, 0, -1.1, 0)),
    ord = list(1:4)
  ) %>%
  ungroup() %>%
  tidyr::unnest(c(xs, ys, ord)) %>%
  arrange(ybase, ord)
spldata <- split(mydata, mydata$label)
spldata <- spldata[order(sapply(spldata, function(z) z$ybase[1]))]

我创建 spldata 的原因是因为 ggplot2 不允许 (afaik) 轻松设置 z-order,所以我将诉诸(下一个块)迭代地绘制多边形。

剧情,无亮点

library(ggplot2)
ggplot(mydata, aes(xs, ys, group = label)) +
  lapply(spldata, function(dat) {
    geom_polygon(aes(fill = I(color)), data = dat)
  }) +
  geom_text(aes(x = -2.2, y = ybase, label = label),
            hjust = 1, color = "white", size = 7,
            data = ~ filter(., ord == 1)) +
  guides(fill = "none", color = "none", alpha = "none") +
  scale_x_continuous(expand = expansion(add = c(2.5, 0.2))) +
  theme(
    plot.background = element_rect(colour = "black", fill = "black"),
    panel.background = element_rect(colour = "black", fill = "black"),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.text = element_blank(), axis.ticks = element_blank()
  )

情节,高亮显示

这里的变化:

  • alpha = if ... 添加到 geom_polygons
  • geom_text 分成两个调用,因为我不想在多边形和文本之间发现 colour= 美学
this <- c("THEME", "MAPPING")
ggplot(mydata, aes(xs, ys, group = label)) +
  lapply(spldata, function(dat) {
    geom_polygon(aes(fill = I(color)),
                 alpha = if (dat$label[1] %in% this) 1 else 0.2,
                 data = dat)
  }) +
  {
    if (any(!mydata$label %in% this))
      geom_text(aes(x = -2.2, y = ybase, label = label),
                hjust = 1, color = "gray50", size = 7,
                data = ~ filter(., ord == 1, !label %in% this))
  } +
  {
    if (any(this %in% mydata$label))
      geom_text(aes(x = -2.2, y = ybase, label = label),
                hjust = 1, color = "white", size = 7,
                data = ~ filter(., ord == 1, label %in% this))
  } +
  guides(fill = "none", color = "none", alpha = "none") +
  scale_x_continuous(expand = expansion(add = c(2.5, 0.2))) +
  theme(
    plot.background = element_rect(colour = "#222222", fill = "#222222"),
    panel.background = element_rect(colour = "#222222", fill = "#222222"),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.text = element_blank(), axis.ticks = element_blank()
  )

(我从 AllanCameron 那里借用了 “一个或多个” 的想法 this 以便能够突出显示多个(或者 none).

我自己研究了一段时间后,想出了以下函数:

library(ggplot2)

generate_layer_diagram <- function(highlight_layers = "all", num_layers = 8, 
                                   overwrite_layer_labels = c('DATA','MAPPING','SCALES','STATISTICS','GEOMETRIES','FACETS','COORDINATES','THEMES'), 
                                   overwrite_colours = c('grey','blue','red','orange','paleturquoise','palegreen','palevioletred','midnightblue'),
                                   base_colour_set_name="Set3",
                                   base_num_colours=12L,
                                   save_path="",
                                   transparent_background=FALSE) {
  base_image_height <- 20.32
  base_image_width <- 21.77
  scaling_factor <- 0.69
  alpha_highlight <- 1.0
  alpha_mute <- 0.2
  font_size <- 8*scaling_factor
  font_weight <- "bold"
  if(transparent_background) {
    font_colour <- "black"
    background_color <- "transparent"
  } else {
    font_colour <- "white"
    background_color <- "black"
  }
  
  diamond <- function(side_length, centre) {
    base <- matrix(c(1, 0, 0, 1, -1, 0, 0, -1), nrow = 2) * sqrt(2) / 2
    trans <- (base * side_length) + centre
    as.data.frame(t(trans))
  }
  
  if(is.character(highlight_layers) && highlight_layers == "all") {
    highlight_layers = c(1:num_layers)
  }
  highlights <- c(rep(FALSE,num_layers))
  highlights[highlight_layers] <- TRUE
  
  layer_labels <- paste0(c("layer_"),c(1:num_layers)) %>% data.table
  layer_labels[,labels:=.][,.:=NULL]
  if(length(overwrite_layer_labels) > num_layers) {
    overwrite_layer_labels <- overwrite_layer_labels[1:num_layers]
  }
  layer_labels[1:length(overwrite_layer_labels),labels:=overwrite_layer_labels]
  
  base_colour_set <- RColorBrewer::brewer.pal(base_num_colours,base_colour_set_name) %>% data.table()
  base_colour_set <- base_colour_set[,colours:=.][,.:=NULL]
  if(num_layers > base_num_colours) {
    base_colour_set <- base_colour_set[rep(seq_len(nrow(base_colour_set)), ceiling(num_layers/base_num_colours)), ]
  }
  base_colour_set <- base_colour_set[1:num_layers]
  colour_set <- base_colour_set
  if(length(overwrite_colours) > num_layers) {
    overwrite_colours <- overwrite_colours[1:num_layers]
  }
  colour_set[1:length(overwrite_colours),colours:=overwrite_colours]
  
  dt <- data.table(side_lengths = rep(c(2),num_layers),
                   centres = matrix(c(1 + rep(0,num_layers),2 + 0:(num_layers-1)),nrow=num_layers),
                   colours = colour_set,
                   labels = layer_labels,
                   highlights = highlights)
  
  dt[,alphas:=(as.numeric(highlights)*alpha_highlight + as.numeric(!highlights)*alpha_mute)]
  
  
  myplot <- ggplot() + lapply(c(1:num_layers),function(x) {geom_polygon(data = diamond(dt$side_lengths[x], c(dt$centres.V1[x],dt$centres.V2[x])), mapping = aes(x = V1, y = V2), fill = dt$colours[x], alpha = dt$alphas[x])}) +
    lapply(c(1:num_layers),function(z) {annotate("text", x = -(dt$centres.V1[z]/2)*1.1, y = dt$centres.V2[z], label = dt$labels[z], alpha = dt$alphas[z], size=font_size, 
                                        fontface = font_weight, hjust=1, colour=font_colour)}) + 
    coord_cartesian(xlim = c(-2,3), ylim =c(-1, (num_layers+4) )) +
    theme_void() + 
    #theme_classic() + # gets rid of the ugly bounding box
    theme( plot.background = element_rect(fill = background_color)
           ,axis.line = element_blank(), axis.ticks = element_blank(), axis.title = element_blank(), axis.text = element_blank()
           #,plot.margin = element_blank()
           ,panel.grid = element_blank()
           ,panel.grid.major = element_blank()
           ,panel.grid.minor = element_blank()
           ,panel.background = element_rect(fill=NA)
           ,panel.border = element_rect(fill=NA)
           ,validate=TRUE) # sets the background and removes the various axes
  
  option_markers <- c(rep(0,num_layers))
  option_markers[highlight_layers] <- 1
  
  suffix <- paste0(option_markers,collapse = "_")
  
  if(save_path != ""){
    ggsave(paste0(save_path,"\pic",suffix,".png"), myplot, height=base_image_height*scaling_factor, width=base_image_width*scaling_factor, units = "cm")
  } else {
    myplot
  }
  
  return(myplot)
}

highlight_layers = "all"
num_layers <- 5
overwrite_layer_labels <- c('DATA','MAPPING','SCALES','STATISTICS','GEOMETRIES','FACETS','COORDINATES','THEMES')
overwrite_colours <- c('grey','blue','red','orange','paleturquoise','palegreen','palevioletred','midnightblue')

Ans 各种示例函数调用产生:

generate_layer_diagram()

generate_layer_diagram(c(1:3),num_layers = num_layers)

generate_layer_diagram(1)

generate_layer_diagram(2)

# Data Mapping and Geometries
generate_layer_diagram(c(1,2,5))

产生:

感谢@AllenCameron 的 传递矢量以一次突出显示多个图层的灵感。