1

I have a simple bar chart I am using in a shiny application with some filters. I am using the following code.

ggplot(users_by_month(), aes(x = month, y = n)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label=n), nudge_y = 4) 

When no filters are being used this works and the sum of each month is above the bar. However when I use a filter and the values change the sums above the bars move to much higher above the bar. Is there a way to keep the nudge_y relative to the bar height so the text remains right above the bar as the underlying data changes?

user3249770
  • 329
  • 2
  • 7

2 Answers2

2

You can achieve it by overriding the y aes with a computed derived stat (here n + 10% of the max n):

library(ggplot2)
library(dplyr)

iris %>% 
  group_by(length= cut(Petal.Length, seq(5))) %>% 
  count() %>% 
  ggplot(aes(x = length, y = n)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label=n, y=n + .1 * after_scale(max(n))))



iris %>% 
  group_by(length= cut(Petal.Length, seq(5))) %>% 
  count() %>% 
  filter(n < 10) %>%
  ggplot(aes(x = length, y = n)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label=n, y=n + .1 * after_scale(max(n))))

Created on 2022-11-12 with reprex v2.0.2

GGamba
  • 13,140
  • 3
  • 38
  • 47
  • Not seeing any labels now using `geom_text(aes(label=n, y=n + .1 * after_scale(max(n))))`, any idea why that would occur? – user3249770 Nov 12 '22 at 00:25
2

We can see your problem very clearly by setting up an example that uses facets with very different ranges. There is a single geom_text layer with nudge_y = 4:

df <- data.frame(month = factor(c(1:3, 1:3)), 
                 n     = c(80, 21, 45, 8, 2, 1),
                 panel = rep(c("A", "B"), each = 3))

library(ggplot2)

ggplot(df, aes(month, n)) +
  geom_col(fill = "deepskyblue4") +
  geom_text(aes(label = n), size = 6, nudge_y = 4) +
  facet_wrap(.~panel, scales = "free_y") +
  scale_y_continuous(expand = expansion(c(0, 0.1))) +
  theme_bw(base_size = 16) 

Obviously, the y axis scale is smaller on the right, so the 4-unit nudge is proportionately much greater and now doesn't sit nicely on the bars.

One way to solve this is to fix the y axis limits:

ggplot(df, aes(month, n)) +
  geom_col(fill = "deepskyblue4") +
  geom_text(aes(label = n), size = 6, nudge_y = 4) +
  facet_wrap(.~panel, scales = "free_y") +
  scale_y_continuous(limits = c(0, 90), expand = expansion(c(0, 0.1))) +
  ylim(c(0, 90)) +
  theme_bw(base_size = 16)

However, if you want the bars to fill the plotting area, the easiest way is probably to just use vjust instead of nudge_y. Remember, you need a negative vjust to shift the text upwards.

ggplot(df, aes(month, n)) +
  geom_col(fill = "deepskyblue4") +
  geom_text(aes(label = n), size = 6, vjust = -0.5) +
  facet_wrap(.~panel, scales = "free_y") +
  scale_y_continuous(expand = expansion(c(0, 0.1))) +
  theme_bw(base_size = 16)

Created on 2022-11-12 with reprex v2.0.2

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • For some reason `vjust = -0.5` doesnt seem to be working? – user3249770 Nov 12 '22 at 00:18
  • Looks like it was `ggplotly` causing my issue, `style(textposition = "top")` resolved it. https://stackoverflow.com/questions/56860019/geom-text-not-positioning-vjust-label-over-geom-bar – user3249770 Nov 12 '22 at 01:39