Die Alpha-Ästhetik zeigt das Skelett des Pfeils anstelle einer einfachen Form - wie kann man das verhindern?

11

Ich möchte ein Bar-Grundstück mit Pfeilen am Ende der Bars bauen. Ich ging geom_segmentmit arrowdefiniert. Ich möchte eine Spalte auf Transparenz abbilden, aber die Alpha-Ästhetik scheint mit Pfeilobjekten nicht gut zu funktionieren. Hier ist das Code-Snippet:

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

Geben Sie hier die Bildbeschreibung ein

Es kann leicht beobachtet werden, dass ein arrowObjekt mit niedrigeren Werten von nicht gut aussieht alphaund sein Skelett anstelle einer einfachen, transparenten Form zeigt. Gibt es eine Möglichkeit, dies zu verhindern?

jakes
quelle
Interessante Beobachtung - Ich kann mir nur eine Problemumgehung vorstellen, wie das Zeichnen eines separaten Segments mit kleinerer Breite, z. B.: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
das ist in der Tat interessant. Ich denke, dies ist nicht vermeidbar, ohne den genauen Bereich für die überlappenden "Skelette" zu berechnen und das Alpha für jeden Bereich programmgesteuert festzulegen (dies wird ein schrecklicher Hack sein). Wenn Sie wirklich wirklich transparente Pfeile wollen, besteht ein anderer Ansatz darin, 1) das Segment und 2) daneben ein Trianlge zu zeichnen. (Das scheint mir auch ein ziemlicher Hack zu sein).
Tjebo
2
Sie hätten definitiv Recht, dass es schön wäre, eine flache Transparenz für Pfeile zu haben. Ich glaube, dies wird nicht durch ein Verhalten am Ende von ggplot verursacht, sondern scheint damit zu tun zu haben, wie das 'grid'-Paket Pfeile zeichnet (von denen ggplot2 abhängt).
Teunbrand

Antworten:

13

Wir können ein neues Geom erstellen geom_arrowbar, das wir wie jedes andere Geom verwenden können. In Ihrem Fall würde es also den gewünschten Plot ergeben, indem Sie einfach Folgendes tun:

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

Geben Sie hier die Bildbeschreibung ein

Und es enthält drei Parameter column_width, head_widthund head_lengthdass können Sie die Form des Pfeils ändern , wenn Sie nicht wie die Standardwerte. Bei Bedarf können wir auch die Füllfarbe und andere Ästhetik festlegen:

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

Geben Sie hier die Bildbeschreibung ein

Der einzige Haken ist, dass wir es zuerst schreiben müssen!

Anhand der Beispiele in der erweiterten ggplot2-Vignette können wir unsere geom_arrowbarauf dieselbe Weise definieren wie andere Geome, außer dass wir unsere 3 Parameter übergeben möchten, die die Form des Pfeils steuern. Diese werden der paramsListe des resultierenden layerObjekts hinzugefügt, das zum Erstellen unserer Pfeilebene verwendet wird:

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

Jetzt bleibt nur noch zu definieren, was a GeomArrowBarist. Dies ist effektiv eine ggprotoKlassendefinition. Der wichtigste Teil davon ist die draw_panelElementfunktion, die jede Zeile unseres Datenrahmens in Pfeilformen konvertiert. Nach einigen grundlegenden Berechnungen aus den x- und y-Koordinaten sowie unseren verschiedenen Formparametern, wie die Form des Pfeils aussehen soll, wird grid::polygonGrobfür jede Zeile unserer Daten eine erstellt und in a gespeichert gTree. Dies bildet die grafische Komponente der Ebene.

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

Diese Implementierung ist alles andere als perfekt. Es fehlen einige wichtige Funktionen, wie z. B. sinnvolle Standardachsenbeschränkungen und die Fähigkeit dazu coord_flip, und es werden unästhetische Ergebnisse erzielt, wenn die Pfeilspitzen länger als die gesamte Spalte sind (obwohl Sie in dieser Situation möglicherweise ohnehin kein solches Diagramm verwenden möchten). . Es wird jedoch sinnvoll sein, dass der Pfeil nach links zeigt, wenn Sie einen negativen Wert haben. Eine bessere Implementierung könnte auch eine Option für leere Pfeilspitzen hinzufügen.

Kurz gesagt, es würde eine Menge Verbesserungen erfordern, um diese (und andere) Fehler auszubügeln und produktionsbereit zu machen, aber es ist gut genug, um in der Zwischenzeit ohne allzu großen Aufwand einige schöne Charts zu erstellen.

Erstellt am 08.03.2020 durch das reprex-Paket (v0.3.0)

Allan Cameron
quelle
4

Sie könnten geom_gene_arrowvon verwendenlibrary(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))

Geben Sie hier die Bildbeschreibung ein

dww
quelle
2
Dies muss das Rad sein, das ich gerade neu erfunden habe! ;)
Allan Cameron