Альфа-эстетика показывает скелет стрелки вместо простой формы - как это предотвратить?

11

Я стремлюсь построить барный участок со стрелками в конце столбцов. Я пошел geom_segmentс arrowопределением. Я хочу отобразить один столбец на прозрачность, но эстетика альфа-канала не совсем подходит для объекта со стрелкой. Вот фрагмент кода:

tibble(y = c(10, 20, 30), n = c(300, 100, 200), transparency = c(10, 2, 4)) %>% 
  ggplot() + geom_segment(aes(x = 0, xend = n, y = y, yend = y, alpha = transparency), 
                          colour = 'red', size = 10, arrow = arrow(length = unit(1.5, 'cm'), type = 'closed')) +
  scale_y_continuous(limits = c(5, 35))

введите описание изображения здесь

Легко заметить, что arrowобъект не выглядит хорошо с более низкими значениями alpha, показывая его скелет вместо простой, прозрачной формы. Есть ли способ предотвратить это?

Джейкс
источник
Интересное наблюдение - я могу только придумать какой-то обходной путь, например, нарисовать отдельный сегмент с меньшей шириной, например, вот так:tibble(y = c(10, 20, 30), n = c(300, 100, 200), transparency = c(10, 2, 4)) %>% ggplot() + geom_segment(aes(x = 0, xend = n-10, y = y, yend = y, alpha = transparency), colour = 'red', size = 10) + geom_segment(aes(x = n-0.1, xend = n, y = y, yend = y, alpha = transparency), colour = 'red', size = 1, arrow = arrow(length = unit(1.5, 'cm'), type = 'closed')) + scale_y_continuous(limits = c(5, 35))
Вольфганг Арнольд
это действительно интересно. Я полагаю, что этого нельзя избежать без расчета точной площади для перекрывающихся «скелетов» и программной установки альфы для каждой области (это будет ужасный взлом). Если вам действительно нужны прозрачные стрелки, другой подход - нарисовать 1) сегмент и 2) соседний треугольник. (это также кажется довольно взломать для меня).
Тьебо
2
Вы бы точно были правы, что было бы неплохо иметь плоскую прозрачность для стрелок. Я полагаю, что это не вызвано каким-либо поведением на конце ggplot, но, похоже, связано с тем, как пакет 'grid' рисует стрелки (от которых зависит ggplot2).
teunbrand

Ответы:

13

Мы можем создать новый geom, geom_arrowbarкоторый мы можем использовать как любой другой geom, так что в вашем случае он даст желаемый график, просто выполнив:

tibble(y = c(10, 20, 30), n = c(300, 100, 200), transparency = c(10, 2, 4)) %>%
  ggplot() +
  geom_arrowbar(aes(x = n, y = y, alpha = transparency), fill = "red") +
  scale_y_continuous(limits = c(5, 35)) +
  scale_x_continuous(limits = c(0, 350))

введите описание изображения здесь

И она содержит 3 параметра, column_width, head_widthи head_lengthкоторые позволяют изменить форму стрелки , если вам не нравится по умолчанию. Мы также можем указать цвет заливки и другую эстетику:

tibble(y = c(10, 20, 30), n = c(300, 100, 200), transparency = c(10, 2, 4)) %>%
  ggplot() +
  geom_arrowbar(aes(x = n, y = y, alpha = transparency, fill = as.factor(n)),
                column_width = 1.8, head_width = 1.8, colour = "black") +
  scale_y_continuous(limits = c(5, 35)) +
  scale_x_continuous(limits = c(0, 350))

введите описание изображения здесь

Единственная загвоздка в том, что мы должны написать это первыми!

Следуя примерам в расширяющейся виньетке ggplot2 , мы можем определить наши так geom_arrowbarже, как и другие геомы, за исключением того, что мы хотим иметь возможность передавать наши 3 параметра, которые управляют формой стрелки. Они добавляются в paramsсписок результирующего layerобъекта, который будет использоваться для создания слоя со стрелками:

library(tidyverse)

geom_arrowbar <- function(mapping = NULL, data = NULL, stat = "identity",
                          position = "identity", na.rm = FALSE, show.legend = NA,
                          inherit.aes = TRUE, head_width = 1, column_width = 1,
                          head_length = 1, ...) 
{
  layer(geom = GeomArrowBar, mapping = mapping, data = data, stat = stat,
        position = position, show.legend = show.legend, inherit.aes = inherit.aes,
        params = list(na.rm = na.rm, head_width = head_width,
                      column_width = column_width, head_length = head_length, ...))
}

Теперь «все», что остается, это определить, что GeomArrowBarесть. Это фактически ggprotoопределение класса. Самая важная часть этого - draw_panelфункция-член, которая берет каждую строку нашего информационного кадра и преобразовывает ее в формы стрелок. После выполнения некоторых базовых математических операций из координат x и y, а также наших различных параметров формы, какой должна быть форма стрелки, она создает по одному grid::polygonGrobдля каждой строки наших данных и сохраняет ее в a gTree. Это формирует графическую составляющую слоя.

GeomArrowBar <- ggproto("GeomArrowBar", Geom,
  required_aes = c("x", "y"),
  default_aes = aes(colour = NA, fill = "grey20", size = 0.5, linetype = 1, alpha = 1),
  extra_params = c("na.rm", "head_width", "column_width", "head_length"),
  draw_key = draw_key_polygon,
  draw_panel = function(data, panel_params, coord, head_width = 1,
                        column_width = 1, head_length = 1) {
    hwidth <- head_width / 5
    wid <- column_width / 10
    len <- head_length / 10
    data2 <- data
    data2$x[1] <- data2$y[1] <- 0
    zero <- coord$transform(data2, panel_params)$x[1]
    coords <- coord$transform(data, panel_params)
    make_arrow_y <- function(y, wid, hwidth) {
      c(y - wid/2, y - wid/2, y - hwidth/2, y, y + hwidth/2, y + wid/2, y + wid/2)
    }
    make_arrow_x <- function(x, len){
      if(x < zero) len <- -len
      return(c(zero, x - len, x - len , x, x - len, x - len, zero))
    }
    my_tree <- grid::gTree()
    for(i in seq(nrow(coords))){
      my_tree <- grid::addGrob(my_tree, grid::polygonGrob(
        make_arrow_x(coords$x[i], len),
        make_arrow_y(coords$y[i], wid, hwidth),
        default.units = "native",
        gp = grid::gpar(
          col = coords$colour[i],
          fill = scales::alpha(coords$fill[i], coords$alpha[i]),
          lwd = coords$size[i] * .pt,
          lty = coords$linetype[i]))) }
    my_tree}
)

Эта реализация далека от совершенства. В нем отсутствуют некоторые важные функции, такие как разумные ограничения по умолчанию для осей и возможность coord_flip, и он даст неэстетичные результаты, если наконечники стрелок длиннее всего столбца (хотя вы, возможно, не захотите использовать такой график в такой ситуации) , Однако стрелка, указывающая налево, будет иметь смысл, если у вас отрицательное значение. Лучшая реализация может также добавить опцию для пустых наконечников стрелок.

Короче говоря, потребовалось бы множество настроек, чтобы сгладить эти (и другие) ошибки и сделать их готовыми к работе, но этого достаточно, чтобы создавать хорошие графики без особых усилий.

Создано в 2020-03-08 пакетом представлением (v0.3.0)

Аллан Кэмерон
источник
4

Вы можете использовать geom_gene_arrowизlibrary(gggenes)

data.frame(y=c(10, 20, 30), n=c(300, 100, 200), transparency=c(10, 2, 4)) %>% 
  ggplot() + 
  geom_gene_arrow(aes(xmin = 0, xmax = n, y = y, alpha = transparency), 
                  arrowhead_height = unit(6, "mm"), fill='red') +
  scale_y_continuous(limits = c(5, 35))

введите описание изображения здесь

DWW
источник
2
Это должно быть колесо, которое я только что изобрел! ;)
Аллан Кэмерон