Search code examples
rggplot2geom-bargeom-text

positioning labels on geom_bar


I am trying to create a horizontal bar chart with category labels using ggplot.

I have been able to create the plot without hassles, and can put labels on, however I suffer issues with the formatting. Ultimately I would like to have my label within the bar if it fits, otherwise just outside the bar without truncating the label.

The following are what I have tried so far.

Data

dt1 <- data.table(x=c("a","b","c","d","e"), y=c(43,52,296,102,157), y2=c(50,10,100,45,80))

Chart 1

ggplot() + geom_bar(data=dt1, aes(x=x, y=y), stat="identity",fill="red") + coord_flip() +
  geom_text(data=dt1, aes(x=x, y=y, label=paste0("$",y," from ",y2," records")),hjust=0)

As you can see below the labels get truncated. Chart 1

Chart 2

I then came across this question which was helpful and made me realise that I was setting the label position based on my y variable so I have hardcoded it now and use hjust to pad it from the axis.

ggplot() + geom_bar(data=dt1, aes(x=x, y=y), stat="identity",fill="red") + coord_flip() +
  geom_text(data=dt1, aes(x=x, y=0, label=paste0("$",y," from ",y2," records")),hjust=-0.1)

But you can see below that only 2 of the labels fit within the bar, so I would prefer the others to be placed at the end, on the outside of the bar like in chart 1. chart 2

Is there a programatic way I can get the best of both worlds from chart 1 and chart 2?


Solution

  • Move the hjust into the aes so we may vary off the value, then move it if the bar is a certain way past the max. It’s a bit hacky still, since it makes assumptions about the scaling, but looks pretty good. Divisor may need tweaking:

    library(tidyverse)
    
    dt1 <- data.frame(x=c("a","b","c","d","e"), y=c(43,52,296,102,157), y2=c(50,10,100,45,80))
    
    ggplot() +
      geom_bar(data=dt1, aes(x=x, y=y), stat="identity",fill="red") +
      coord_flip() +
      geom_text(
        data=dt1,
        aes(
          x=x, y=y,
          label=paste0("$",y," from ",y2," records"),
          hjust=ifelse(y < max(dt1$y) / 1.5, -0.1, 1.1), # <- Here lies the magic
        ),
      )
    

    Results in this plot:

    Plot generated by above code