Estetyka alfa pokazuje szkielet strzały zamiast prostego kształtu - jak temu zapobiec?

11

Zamierzam zbudować wykres słupkowy ze strzałkami na końcu taktów. Poszedłem geom_segmentz arrowokreślonym. Chcę zmapować jedną kolumnę na przezroczystość, ale estetyka alfa wydaje się nie działać dobrze z obiektem strzałki. Oto fragment kodu:

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))

wprowadź opis zdjęcia tutaj

Łatwo można zauważyć, że arrowobiekt nie wygląda dobrze przy niższych wartościach alpha, pokazując szkielet zamiast zwykłego, przezroczystego kształtu. Czy istnieje sposób, aby temu zapobiec?

jakes
źródło
Ciekawe spostrzeżenie - mogę tylko wymyślić jakieś obejście, takie jak narysowanie oddzielnego segmentu o mniejszej szerokości, np. Takie: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))
Wolfgang Arnold
to jest naprawdę interesujące. Myślę, że nie da się tego uniknąć bez obliczenia dokładnego obszaru dla nakładających się „szkieletów” i ustawienia programowego alfa dla każdego obszaru (będzie to okropny włamanie). Jeśli naprawdę chcesz przezroczystych strzał, innym podejściem byłoby narysowanie 1) segmentu i 2) przylegającego do niego trójkąta. (to też wydaje mi się dość hack).
Tjebo
2
Na pewno miałbyś rację, że byłoby miło mieć płaską przezroczystość strzał. Uważam, że nie jest to spowodowane jakimikolwiek zachowaniami po stronie ggplot, ale wydaje się, że jest to związane z tym, jak pakiet „grid” rysuje strzałki (od których zależy ggplot2).
teunbrand

Odpowiedzi:

13

Możemy stworzyć nowy geom, z geom_arrowbarktórego będziemy mogli korzystać jak każdy inny geom, więc w twoim przypadku dałoby to pożądaną fabułę, po prostu wykonując:

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))

wprowadź opis zdjęcia tutaj

I zawiera 3 parametry column_width, head_widtha head_lengthktóre pozwalają zmienić kształt strzałki, jeśli nie podoba ci się domyślne. W razie potrzeby możemy również określić kolor wypełnienia i inne elementy estetyczne:

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))

wprowadź opis zdjęcia tutaj

Jedyną przeszkodą jest to, że najpierw musimy to napisać!

Postępując zgodnie z przykładami w rozszerzającej się winiecie ggplot2 , możemy zdefiniować naszą geom_arrowbarw ten sam sposób, w jaki zdefiniowane są inne geomy, z wyjątkiem tego, że chcemy móc przekazać nasze 3 parametry kontrolujące kształt strzałki. Są one dodawane do paramslisty wynikowego layerobiektu, który zostanie użyty do utworzenia naszej warstwy strzał:

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, ...))
}

Teraz „wszystko”, co pozostaje, to zdefiniowanie, czym GeomArrowBarjest a . Jest to faktycznie ggprotodefinicja klasy. Najważniejszą jego częścią jest draw_panelfunkcja członka, która pobiera każdą linię naszej ramki danych i przekształca ją w kształty strzałek. Po kilku podstawowych obliczeniach matematycznych dotyczących współrzędnych xiy oraz różnych parametrów kształtu, jaki powinien być kształt strzałki, tworzy jeden grid::polygonGrobdla każdej linii naszych danych i przechowuje go w gTree. To tworzy komponent graficzny warstwy.

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}
)

Ta implementacja jest daleka od ideału. Brakuje niektórych ważnych funkcji, takich jak rozsądne domyślne limity osi i zdolność do coord_flip, i przyniesie nieestetyczne wyniki, jeśli strzały są dłuższe niż cała kolumna (choć i tak nie chcesz używać takiego wykresu w tej sytuacji) . Jednak rozsądnie będzie mieć strzałkę skierowaną w lewo, jeśli masz wartość ujemną. Lepsza implementacja może również dodać opcję pustych strzałek.

Krótko mówiąc, wymagałoby to wielu poprawek, aby wyeliminować te (i inne) błędy i sprawić, że będzie gotowy do produkcji, ale w międzyczasie jest wystarczająco dobry, aby wygenerować fajne wykresy.

Utworzono 2020-03-08 przez pakiet reprezentx (v0.3.0)

Allan Cameron
źródło
4

Możesz użyć geom_gene_arrowzlibrary(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))

wprowadź opis zdjęcia tutaj

dww
źródło
2
To musi być koło, które właśnie wymyśliłem na nowo! ;)
Allan Cameron