如何将 edges/borders 添加到 geom_link2 ? - 跟进问题

How to add edges/borders to geom_link2 ? - follow up question

这是来自 我想知道是否有办法将 edge/border(不确定正确的词)添加到使用 ggforce::geom_link2 创建的 link?类似于 pch >20 的点。

@tjebo 给出的解决方案是制作 2 geom_link/path 层,第一层比第二层宽一点,让它看起来像一个边框(见下面的代码)。

所以我这里有 2 个问题:

  1. 有交叉时边缘不明显。在有很多点的排序的情况下,这可能会相当混乱。有什么解决办法吗?

  2. 为什么我的尺码没有得到尊重?黑色边框 link 应该始终比彩色边框 link 宽 1(即每边 0.5)。这里情况不同。我错过了什么吗?

library(ggforce)
#> Loading required package: ggplot2

df <- data.frame(x = c(5, 10, 5, 10), 
                 y = c(5, 10, 10, 5), 
                 width = c(1, 10, 6, 2), 
                 colour = letters[1:4], 
                 group = c(1, 1, 2, 2), 
                 width_border = c(2, 11, 7, 3))

ggplot(df) +
  geom_link2(aes(x = x, y = y,  group = group, size = width_border),
             lineend = 'round') +
  geom_link2(aes(x = x, y = y, colour = colour, group = group, size = width), 
             lineend = 'round', n = 500)

reprex package (v1.0.0)

于 2021-02-13 创建

对于您的第一个问题,这是一个半令人满意的解决方法。我正在使用 ggplot 的列表字符 - 每个 object/layer 实际上都可以添加为实际列表(而不是添加 +)。因此,您可以循环遍历组,仅按正确顺序绘制图层(首先是背景,然后是前景),这将正确重叠。这在有很多组的情节中可能会非常慢 - 另一方面,在这种情况下,我不确定所选的可视化是否是最佳选择。

第二个问题可能是由于对两个宽度应用了不同的比例造成的。一种解决方案是设置相互比例,例如,通过添加 scale_size_identity.

library(tidyverse)
library(ggforce)
df <- data.frame( x = c(5, 10, 5, 10), y = c(5, 10, 10, 5), width = c(1, 10, 6, 2), colour = letters[1:4], group = c(1, 1, 2, 2), width_border = c(2, 11, 7, 3))

ggplot(df) +
  scale_size_identity()+
  df %>% 
  split(., .$group) %>%
  map(., ~list(l1 = geom_link2(data = ., aes(x = x, y = y,  group = group, size = width_border), lineend = 'round'),
               l2 = geom_link2(data = ., aes(x = x, y = y, colour = colour, group = group, size = width), lineend = 'round', n = 500))
  )

reprex package (v1.0.0)

于 2021-02-14 创建

P.S。我对 geom 的实现很好奇 - 请参阅 Z.Lin 的惊人答案。谢谢Z.Lin!

这是 @tjebo 提出的基本上相同的 hack 的快速实现,在底层 ggproto 对象中内化了两个 grob-creation 步骤。

ggplot(df, aes(x = x, y = y, group = group)) +
  geom_link3(aes(colour = colour, size = width,
                 border_width = width_border),
             lineend = 'round', n = 500) +
  scale_size_identity() + ggtitle("1")

# border colour defaults to black, but can be changed to other colours as well
ggplot(df, aes(x = x, y = y, group = group)) +
  geom_link3(aes(colour = colour, size = width,
                 border_width = width_border),
             border_colour = "blue",
             lineend = 'round', n = 500) +
  scale_size_identity() + ggtitle("2")

# behaves just like geom_link2 if border_width / colour are not specified
ggplot(df, aes(x = x, y = y, group = group)) +
  geom_link3(aes(colour = colour, size = width),
             lineend = 'round', n = 500) +
  scale_size_identity() + ggtitle("3")

# also works with constant link colour/size & visibly varying border width 
ggplot(df, aes(x = x, y = y, group = group)) +
  geom_link3(aes(border_width = width_border*2),
             colour = "white", size = 2, 
             lineend = 'round', n = 500) +
  scale_size_identity() + ggtitle("4")

(为保护而删除的图例 space)

代码:

GeomPathInterpolate3 <- ggproto(
  "GeomPathInterpolate3",
  ggforce:::GeomPathInterpolate,
  default_aes = aes(colour = "black",
                    size = 0.5,
                    linetype = 1,
                    alpha = NA,
                    border_colour = "black",
                    border_width = 0),
  draw_panel = environment(Geom$draw_panel)$f,
  draw_group = function (data, panel_scales, coord, arrow = NULL, 
                         lineend = "butt", linejoin = "round", linemitre = 1, 
                         na.rm = FALSE)   {
    if (!anyDuplicated(data$group)) {
      message("geom_path_interpolate: Each group consists of only one observation. ", 
              "Do you need to adjust the group aesthetic?")
    }
    data <- data[order(data$group), , drop = FALSE]
    data <- interpolateDataFrame(data)
    munched <- coord_munch(coord, data, panel_scales)
    rows <- stats::ave(seq_len(nrow(munched)), 
                       munched$group, FUN = length)
    munched <- munched[rows >= 2, ]
    if (nrow(munched) < 2) {
      return(zeroGrob())
    }
    attr <- ggplot2:::dapply(data, "group", function(df) {
      ggplot2:::new_data_frame(list(solid = identical(unique(df$linetype), 1), 
                          constant = nrow(unique(df[, 
                                                    c("alpha", "colour", 
                                                      "size", "linetype",
                                                      "border_width")])) == 1))
    })
    solid_lines <- all(attr$solid)
    constant <- all(attr$constant)
    if (!solid_lines && !constant) {
      stop("geom_path_interpolate: If you are using dotted or dashed lines", 
           ", colour, size and linetype must be constant over the line", 
           call. = FALSE)
    }
    n <- nrow(munched)
    group_diff <- munched$group[-1] != munched$group[-n]
    start <- c(TRUE, group_diff)
    end <- c(group_diff, TRUE)
    if (!constant) {
      ggplot2:::ggname("geom_link_border",
                       grid::grobTree(grid::segmentsGrob(munched$x[!end], munched$y[!end], munched$x[!start],
                                                         munched$y[!start], default.units = "native", arrow = arrow,
                                                         gp = grid::gpar(col = munched$border_colour[!end],
                                                                         fill = munched$border_colour[!end],
                                                                         lwd = munched$border_width[!end] * .pt,
                                                                         lty = munched$linetype[!end],
                                                                         lineend = lineend, linejoin = linejoin, linemitre = linemitre)),
                                      grid::segmentsGrob(munched$x[!end], munched$y[!end], munched$x[!start],
                                                         munched$y[!start], default.units = "native", arrow = arrow,
                                                         gp = grid::gpar(col = alpha(munched$colour, munched$alpha)[!end],
                                                                         fill = alpha(munched$colour, munched$alpha)[!end],
                                                                         lwd = munched$size[!end] * .pt,
                                                                         lty = munched$linetype[!end],
                                                                         lineend = lineend, linejoin = linejoin, linemitre = linemitre))))
    }
    else {
      ggplot2:::ggname("geom_link_border",
                       grid::grobTree(grid::polylineGrob(munched$x, munched$y, default.units = "native", 
                                                         arrow = arrow, 
                                                         gp = grid::gpar(col = munched$border_colour[!end],
                                                                         fill = munched$border_colour[!end], 
                                                                         lwd = munched$border_width[start] * .pt, 
                                                                         lty = munched$linetype[start], lineend = lineend, 
                                                                         linejoin = linejoin, linemitre = linemitre)),
                                      grid::polylineGrob(munched$x, munched$y, default.units = "native", 
                                                         arrow = arrow, 
                                                         gp = grid::gpar(col = alpha(munched$colour, munched$alpha)[start],
                                                                         fill = alpha(munched$colour, munched$alpha)[start], 
                                                                         lwd = munched$size[start] * .pt, 
                                                                         lty = munched$linetype[start], lineend = lineend, 
                                                                         linejoin = linejoin, linemitre = linemitre))))
      
    }
  }
)

geom_link3 <- function (mapping = NULL, data = NULL, stat = "link2", 
                        position = "identity", arrow = NULL, lineend = "butt", 
                        na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, n = 100, 
                        ...) {
  layer(data = data, mapping = mapping, stat = stat, geom = GeomPathInterpolate3, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(arrow = arrow, lineend = lineend, na.rm = na.rm, 
                      n = n, ...))
}

基本思路是在 draw_group 而不是 draw_panel 中创建 grob,这样每条线的边框 grob 和 link grob 都是按顺序绘制的。

引入了两个新参数:

  1. border_width:默认为0;可以映射到数字美学。

  2. border_colour:默认为“黑色”;可以更改为另一种颜色,但不打算在层内变化,因为我认为这会使事情变得太混乱。

注意:border_color没有校验,所以如果您使用该函数,请使用英式拼写,或自行修改函数。 =P