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?


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)


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


2
To musi być koło, które właśnie wymyśliłem na nowo! ;)
Allan Cameron
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.