Dan
Dan

Reputation: 2675

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?

Upvotes: 4

Views: 9697

Answers (3)

Andrew Marshall
Andrew Marshall

Reputation: 96914

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

Upvotes: 3

Michael_A
Michael_A

Reputation: 498

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.

Upvotes: 0

jazzurro
jazzurro

Reputation: 23574

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

Upvotes: 3

Related Questions