2016-03-30 15 views
13

Questo è in realtà due domande in una (non sono sicuro se va contro le regole SO, ma comunque).ggplot2: geom_text ridimensionare con la trama e forzare/inserire il testo all'interno di geom_bar

La prima domanda è: come posso forzare un geom_text per adattarlo a un geom_bar? (dinamicamente secondo i valori tracciati)

Guardandosi intorno, le soluzioni che ho trovato stavano cambiando le dimensioni dell'etichetta. Certamente funziona, ma non per tutti i casi. È possibile modificare le dimensioni per un tracciato specifico per rendere il testo adatto alla barra, ma quando i dati cambiano, potrebbe essere necessario modificare manualmente la dimensione del testo. Il mio problema di vita reale è che ho bisogno di generare la stessa trama per i dati che cambiano costantemente (ogni giorno), quindi non posso davvero regolare manualmente la dimensione per ogni trama.

Ho provato a impostare la dimensione dell'etichetta in funzione dei dati. Funziona un po ', non perfettamente, ma funziona per molti casi.

Ma ecco un altro problema, anche quando l'etichetta si adatta all'interno della barra, il ridimensionamento della trama incasina tutto. Guardando in esso, ho trovato anche nelle ggplot documentation che

etichette hanno altezza e larghezza, ma sono unità fisiche, non di dati unità. La quantità di spazio che occupano su quel grafico non è costante nelle unità di dati : quando ridimensioni un grafico, le etichette mantengono le stesse dimensioni, ma la dimensione degli assi degli assi cambia.

Quale mi porta alla mia seconda domanda: è possibile modificare questo comportamento predefinito e lasciare/rendere le etichette ridimensionate con la trama?

E vorrei anche perfezionare la mia prima domanda. È possibile forzare un geom_text per adattarsi a un geom_bar, impostando dinamicamente la dimensione del testo utilizzando una relazione intelligente tra unità fisiche e unità di dati?

Quindi, a seguire le buone pratiche, ecco il mio esempio riproducibile:

set.seed(1234567) 
data_gd <- data.frame(x = letters[1:5], 
         y = runif(5, 100, 99999)) 

ggplot(data = data_gd, 
     mapping = aes(x = x, y = y, fill = x)) + 
    geom_bar(stat = "identity") + 
    geom_text(mapping = aes(label = y, y = y/2)) 

Questo codice produce questa trama:

enter image description here

Se ho semplicemente ridimensionare la trama, "labels stay the same size, but the size of the axes changes" rendendo in tal modo le etichette si adattano alle barre (ora forse le etichette sono anche troppo piccole).

enter image description here

Quindi, questa è la mia seconda domanda. Sarebbe bello che anche le etichette si ridimensionassero e mantenessero la proporzione rispetto alle barre. Qualche idea su come realizzare questo o se è possibile a tutti?

Ok, ma tornando a come montare le etichette all'interno delle barre, la soluzione più semplice è quella di impostare la dimensione delle etichette.

Anche in questo caso, questo funziona come mostrato di seguito, ma non è mantenibile/né robusto alle modifiche nei dati.

enter image description here

Ad esempio, lo stesso codice per generare la trama con rese diverse di dati risultati catastrofici.

data_gd <- data.frame(x = letters[1:30], 
         y = runif(30, 100, 99999)) 
ggplot(data = data_gd, 
     mapping = aes(x = x, y = y, fill = x)) + 
    geom_bar(stat = "identity") + 
    geom_text(mapping = aes(label = y, y = y/2), size = 3) 

enter image description here

E posso andare avanti con gli esempi, impostando le dimensioni delle etichette in funzione del numero di categorie su ascisse e così via. Ma hai capito il punto, e forse uno di voi, gli esperti di ggplot2, può darmi delle idee.

risposta

9

Un'opzione potrebbe essere quella di scrivere una geom che utilizza un textGrob con un metodo drawDetails personalizzato per adattarsi allo spazio allocato, impostato in base alla larghezza della barra.

library(grid) 
library(ggplot2) 

fitGrob <- function(label, x=0.5, y=0.5, width=1){ 
    grob(x=x, y=y, width=width, label=label, cl = "fit") 
} 
drawDetails.fit <- function(x, recording=FALSE){ 
    tw <- sapply(x$label, function(l) convertWidth(grobWidth(textGrob(l)), "native", valueOnly = TRUE)) 
    cex <- x$width/tw 
    grid.text(x$label, x$x, x$y, gp=gpar(cex=cex), default.units = "native") 
} 


`%||%` <- ggplot2:::`%||%` 

GeomFit <- ggproto("GeomFit", GeomRect, 
        required_aes = c("x", "label"), 

        setup_data = function(data, params) { 
        data$width <- data$width %||% 
         params$width %||% (resolution(data$x, FALSE) * 0.9) 
        transform(data, 
           ymin = pmin(y, 0), ymax = pmax(y, 0), 
           xmin = x - width/2, xmax = x + width/2, width = NULL 
        ) 
        }, 
        draw_panel = function(self, data, panel_scales, coord, width = NULL) { 
        bars <- ggproto_parent(GeomRect, self)$draw_panel(data, panel_scales, coord) 
        coords <- coord$transform(data, panel_scales)  
        width <- abs(coords$xmax - coords$xmin) 
        tg <- fitGrob(label=coords$label, y = coords$y/2, x = coords$x, width = width) 

        grobTree(bars, tg) 
        } 
) 

geom_fit <- function(mapping = NULL, data = NULL, 
        stat = "count", position = "stack", 
        ..., 
        width = NULL, 
        binwidth = NULL, 
        na.rm = FALSE, 
        show.legend = NA, 
        inherit.aes = TRUE) { 

    layer(
    data = data, 
    mapping = mapping, 
    stat = stat, 
    geom = GeomFit, 
    position = position, 
    show.legend = show.legend, 
    inherit.aes = inherit.aes, 
    params = list(
     width = width, 
     na.rm = na.rm, 
     ... 
    ) 
) 
} 


set.seed(1234567) 
data_gd <- data.frame(x = letters[1:5], 
         y = runif(5, 100, 99999)) 

ggplot(data = data_gd, 
     mapping = aes(x = x, y = y, fill = x, label=round(y))) + 
    geom_fit(stat = "identity") + 
    theme() 

enter image description here

+0

impressionante !!!, ..., ancora cercando di capire come funziona perché conosco solo le basi di 'ggplot2', ma il codice funziona senza problemi e affronta entrambi i problemi: si adatta perfettamente al testo all'interno delle barre e al testo ridimensionare insieme alla trama. Davvero non pensavo fosse possibile. Grazie molto – elikesprogramming

6

Se i grafici a barre orizzontali sono OK, il problema non è la dimensione delle etichette ma il posizionamento. La mia soluzione sarebbe

enter image description here

creato da questo codice:

library(ggplot2) 
data_gd <- data.frame(x = letters[1:26], 
         y = runif(26, 100, 99999)) 
ymid <- mean(range(data_gd$y)) 
ggplot(data = data_gd, 
     mapping = aes(x = x, y = y, fill = x)) + 
    geom_bar(stat = "identity") + 
    geom_text(mapping = aes(label = y, y = y, 
      hjust = ifelse(y < ymid, -0.1, 1.1)), size = 3) + 
    coord_flip() 

Il trucco è fatto in tre fasi:

  1. coord_flip fa un grafico a barre orizzontali.
  2. La mappatura in geom_text si utilizza anche in base al valore di y. Se la barra è più corta della metà dell'intervallo di y, il testo viene stampato all'esterno della barra (a destra rispetto al valore y). Se la barra è più lunga della metà dell'intervallo di y, il testo viene stampato all'interno della barra (lasciato al valore y). Ciò assicura che il testo sia sempre stampato all'interno dell'area del grafico (se non troppo lungo).
  3. Ho aggiunto dello spazio aggiuntivo tra la barra e il testo. Se vuoi che il testo inizi o termini direttamente con il valore y, puoi usare hjust = ifelse(y < ymid, 0, 1)).
+0

Grazie. Immagino che questo sia buono come lo è, no ?, per me, le barre verticali sono un must, ma penso che sarebbe accettabile ruotare solo le etichette ('angle = 90') e usare il tuo trucco con' hjust' per assicurarsi che il testo sia stampato all'interno dell'area del grafico. – elikesprogramming

+0

Ancora chiedendo di ridimensionare la trama e le etichette allo stesso tempo, ..., ma sono quasi sicuro che non sia affatto possibile – elikesprogramming

+0

La dimensione del testo deve essere regolata a seconda del numero di barre, come 'text_size <- if (n_bar <= 20) 4 else 3' o qualcosa di più sofisticato usando 'scale_size'. – Uwe

Problemi correlati