Skip to contents

This is a small utility function, intended to calculate the position of labels for stacked bar charts. Usually with ggplot, position_stack or position_fill will suffice, but sometimes you need to do these calculations manually for more specific label placement. Most common is for labels to be placed in the middle of each segment, but the just option allows labels to be aligned at some ratio along the length of each segment.

Note that this function is only concerned with numeric values. You likely want to arrange and / or group data before passing values to stack_lbls.

Usage

stack_lbls(x, just = 0.5, fill = FALSE)

Arguments

x

Numeric vector to stack

just

Numeric value representing the ratio at which to place labels, comparable to hjust and vjust arguments in plotting functions. You'll often want a single value, though setting each segment's justification can also be useful. Logically values should be between 0 and 1, but nothing's stopping you from using values outside that range. Defaults 0.5 for centered values.

fill

Logical: should stacked values be rescaled to the range 0 to 1? Defaults FALSE, returning values on the same scale as those supplied.

Value

A vector of stacked values the same length as x. If the length of x isn't a multiple of the length of just, you'll get warnings.

Examples

library(dplyr)
#> 
#> Attaching package: ‘dplyr’
#> The following objects are masked from ‘package:stats’:
#> 
#>     filter, lag
#> The following objects are masked from ‘package:base’:
#> 
#>     intersect, setdiff, setequal, union
library(ggplot2)
df <- self_rated_health |>
  filter(category %in% c("Greater New Haven", "Gender")) |>
  group_by(group)

# place labels at centers of bars--should be the same as just using position_stack
df_center <- df |>
  mutate(centered = stack_lbls(value))

ggplot(df_center, aes(x = group, y = value, fill = response)) +
  geom_col(position = position_stack(reverse = TRUE)) +
  geom_text(aes(label = percent100(value), y = centered))


# offset very small values for the top bar
df_offset <- df_center |>
  mutate(offset = ifelse(response == last(response) & value < 0.03, 1.01, centered))

ggplot(df_offset, aes(x = group, y = value, fill = response)) +
  geom_col(position = position_stack(reverse = TRUE)) +
  geom_text(aes(label = percent100(value), y = offset))


# replace the legend with direct labels along the last stack of bars
df_top <- df |>
  mutate(top = stack_lbls(value, just = 1)) |>
  ungroup()

ggplot(df_top, aes(x = group, y = value, fill = response)) +
  geom_col(position = position_stack(reverse = TRUE)) +
  geom_text(aes(label = response, color = response, y = top),
            data = ~slice_max(., group),
            hjust = 0, vjust = 1, nudge_x = 0.5) +
  scale_x_discrete(expand = expansion(add = c(0.8, 1.5))) +
  theme(legend.position = "none")