4

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?

Community
  • 1
  • 1
Dan
  • 2,625
  • 5
  • 27
  • 42

3 Answers3

3

Here is one way. It is a bit lengthy approach, but you can subset your data for geom_text. In this way, you can manually assign the position you want for each bar.

ggplot() + 
geom_bar(data = dt1, aes(x=x, y=y), stat="identity",fill="red") +
coord_flip() +
geom_text(data = filter(dt1, x == "e" | x == "c"),
     aes(x=x, y=0, label=paste0("$",y," from ",y2," records")),hjust = -0.1) +
geom_text(data = filter(dt1, x == "d"),
     aes(x=x, y=0, label=paste0("$",y," from ",y2," records")),hjust = - 1.1) +
geom_text(data = filter(dt1, x == "b"),
     aes(x=x, y=0, label=paste0("$",y," from ",y2," records")),hjust = - 0.6) +
geom_text(data = filter(dt1, x == "a"),
     aes(x=x, y=0, label=paste0("$",y," from ",y2," records")),hjust = - 0.5) 

enter image description here

jazzurro
  • 23,179
  • 35
  • 66
  • 76
  • 1
    It'd be a pain to adjust manually and the relatively positions of labels are subject to display. – KFB Nov 11 '14 at 00:56
  • @KFB Yeah I know what you mean. If a graphic is like this, I would not mind doing manual work. But if one has a large number of groups, he/she has to think something else. – jazzurro Nov 11 '14 at 01:03
  • I thought it might be somewhat difficult to do. So far I think chart 2 in my question is going to be the best approach to avoid manual intervention for the plots. – Dan Nov 11 '14 at 02:11
  • @Dan I think the choice would be either you make label position consistent with less lines or varied with more lines. I like your chart 2. If I were you, I would stick to that. – jazzurro Nov 11 '14 at 02:25
3

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

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • 1
    That's by far the best approach I've encountered yet. Completely get the tweaking of the divisor, but that is one figure across the whole chart. – Dan Jun 10 '19 at 03:47
0

I'm going to misread programmatic as 'pragmatic'. Adding "+ scale_y_continuous(limits=c(0,max(dt1$y)+100))" created sufficient room for the labels. I lack the reputation to upload the plot.

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) + scale_y_continuous(limits=c(0,max(dt1$y)+100))

Edit 2; I altered the code to retrieve the maximum value and add 100 to it. It's still not fitting the plot to include the text specifically but it'll work with fixed labels.

Michael_A
  • 488
  • 1
  • 5
  • 17
  • It assists, however it doesn't address the main question relating to having the best of both worlds. I was also after a programatic way to do things as I don't always know what the largest value of y will be and I would prefer not to hard code limits – Dan Nov 10 '14 at 23:55