Absolute placement of geom_text() on stacked bar plot

ggplot2

#1

Is there a simple way to add text to bar plots with absolute positioning (instead of relative positioning) without calculating y values? For example, the labels on this plot are placed so they are 90% of the way from the top of each bar, which I think looks bizarre because each bar has a different height.

mtcars %>%
  group_by(am) %>%
  count(cyl) %>%
  ggplot(aes(factor(cyl), n, fill = factor(am))) +
  geom_col(width = 0.7) +
  geom_text(aes(label = n), position = position_stack(vjust = 0.9)) +    
  scale_y_continuous(expand = expand_scale(mult = c(0, 0.1))) +
  remove_ticks() +
  theme(panel.grid.major.y = element_blank(),
        axis.text.y = element_blank())

I would prefer to have all labels ~1/8" from the top of each bar.


#2

That's still relative positioning, no? For this, would you also want the bottom label equidistant from the top of its part of the bar? Unfortunately, since the adjustments apply to all of the labels, I think you'd end up with labels for the bottom factor in the sections above them.

nudge_x() and nudge_y() can be used to move text a fixed distance, but you can't use both a nudge_* and a dodging parameter for the same plot.

You could use annotate(), but I'm guessing that doesn't qualify as simple.

Also, what package is remove_ticks() from? Just curious. Ran into that while running your code.


#3

Thank you for the quick response. You're right, it's relative, but I was thinking of it as absolute because the only solutions I have discovered are to calculate the absolute Y values and then use geom_text() or annotate().

What I really want is a combination of position_stack(vjust = 1) and position_nudge(y = -0.1).

remove_ticks() is from my organization's ggplot2 themes package. It's just a simple wrapper function we use for plots with discrete axes.

function() {
  ggplot2::theme(axis.ticks = ggplot2::element_blank(),
                 axis.ticks.x = ggplot2::element_blank(),
                 axis.ticks.y = ggplot2::element_blank())
}

Sorry, next time I will make a proper repex!


#4

I think maybe the idea that you're trying to get at is a fixed offset — so "n units down" for all the labels, rather than "n percent of the bar height"? Somewhat frustrating (to me, at least!) is that you can specify a fixed offset for the label positions (as opposed to a percentage of the bar height), but because the y values are stacking on top of each other, the offsets accumulate:

library(tidyverse)

demo_theme <- function() {
  theme(
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    panel.background = element_blank(),
    panel.grid.major.y = element_line(size = 0.5, color = "gray30"),
    panel.grid.major.x = element_blank(),
    panel.grid.minor = element_blank(),
    panel.ontop = TRUE
  )
}

mtcars %>%
  group_by(am) %>%
  count(cyl) %>%
  ggplot(aes(factor(cyl), n, fill = factor(am))) +
  geom_col(width = 0.7) +
  geom_text(
    aes(label = n, y = n - 1), # << move each label down by 1 unit
    position = position_stack(), 
    color = "white", fontface = "bold", size = 8
  ) +    
  scale_y_continuous(breaks = 1:20, expand = expand_scale(add = 0:1)) +
  guides(fill = "none") + 
  demo_theme()


Created on 2018-10-09 by the reprex package (v0.2.1)

For me, conceptually, the challenge is that in this case I am continually tempted to think of the labels as attached to the things they are labelling (tied on with invisible strings! :balloon:). But the labels are actually an independent layer that just happens to be relying on the same underlying data.