rggplot2

Add a total (geom_text) on top of each stacked bar in facet_wrap()


How can I add text on top the stacked bar with totals? The number should be equal to the "Revenue" in col metric. I have searched everywhere but have not seen a thread for stacked col bars. Any help is appreciated

Current code:

prof %>% 
  filter(metric != "Revenue") %>% 
  pivot_longer(!1:2, names_to = "group") %>%
  mutate(total_2024 = sum(value * (year == 2024)), .by = group) %>%
  mutate(group = reorder(group, -total_2024)) %>% 
  mutate(year_group_total = sum(value), .by = c(year, group)) %>% 
  ggplot() + 
  aes(x = factor(year), y = value, fill = metric) +
  geom_bar(stat = "identity", position = "stack") +
  geom_text(aes(label = round(value, 0)), 
            position = position_stack(vjust = 0.5),
            size = 3, color = "white") + 
  facet_wrap(~ group, nrow = 1)

Data:

structure(list(metric = c("Semi-direct/Indirect OPEX", "Direct OPEX", 
"Cogs", "EBITDA", "Semi-direct/Indirect OPEX", "Direct OPEX", 
"Cogs", "EBITDA", "Revenue", "Revenue"), year = c(2024, 2024, 
2024, 2024, 2023, 2023, 2023, 2023, 2023, 2024), `B2C Mobile` = c(897.784296273134, 
1553.75734792605, 2174.93374732592, 5755.54423659222, 998.273127452503, 
1491.49788927451, 2577.32537561267, 5188.04527286322, 10255.1416652029, 
10382.0196281173), `B2B Mobile` = c(556.814329695538, 910.678821511126, 
1206.88678722175, 2974.74324198596, 551.422958887446, 886.952093722743, 
1291.87657757128, 3091.66275796777, 5821.91438814924, 5649.12318041437
), `B2C Fiber` = c(517.558827512705, 495.164268332664, 2047.61667685037, 
471.94510699589, 412.859413724063, 454.877885907187, 1936.82417093636, 
500.145306066416, 3304.70677663402, 3532.28487969163), `HFC B2C` = c(252.327289621926, 
194.055038683145, 892.79869969144, 305.281096059764, 183.864737312406, 
177.365375913805, 1020.84928874347, 403.547767116329, 1785.62716908601, 
1644.46212405627), `B2B Fiber` = c(130.704875738789, 181.150718557421, 
315.08402928116, 259.876581766672, 184.022162044549, 203.280264535604, 
290.095491871566, 288.017740790265, 965.415659241984, 886.816205344042
), Wholesale = c(335.66507766929, 81.3907132734683, 359.637328238667, 
726.393721768013, 606.085398971104, -161.245692311656, 379.926654798916, 
721.092000854812, 1545.85836231318, 1503.08684094944), Other = c(582.212942299726, 
460.192151400037, 486.308969368716, 660.596166217528, 884.601699952072, 
349.083787813294, 545.586302785773, 509.765926491606, 2289.03771704274, 
2189.31022928601)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, 
-10L))

enter image description here


Solution

  • You can achieve your desired result using a second geom_text/label where you use stat="summary" to compute the sum on the fly and use after_stat() to map the computed value on the label aes:

    library(tidyverse)
    
    prof %>%
      filter(metric != "Revenue") %>%
      pivot_longer(!1:2, names_to = "group") %>%
      mutate(total_2024 = sum(value * (year == 2024)), .by = group) %>%
      mutate(group = reorder(group, -total_2024)) %>%
      mutate(year_group_total = sum(value), .by = c(year, group)) %>%
      ggplot() +
      aes(x = factor(year), y = value, fill = metric) +
      geom_col() +
      geom_text(aes(label = round(value, 0)),
        position = position_stack(vjust = 0.5),
        size = 3, color = "white"
      ) +
      geom_label(
        aes(label = after_stat(round(y, 0)), group = 1),
        size = 3, color = "black",
        stat = "summary",
        fun = sum,
        vjust = 0,
        fill = NA,
        label.size = 0
      ) +
      facet_wrap(~group, nrow = 1)
    

    Created on 2025-03-19 with reprex v2.1.1