'How do i show values for a horizontal stacked barplot inside the bar?

I have a stacked bar plot i created with this code

p<- ggplot(data = df21[1:20,], aes(x =Initiative, y = Frequency, fill= Topic)) +
  geom_col(aes(fill = Topic), width = 0.5)+ 
  geom_text(aes(label = Frequency,  #paste0(round(100*value/sum(value),2),"%")
  ),
  vjust = -1.5,
  hjust = 6,
  color = "black",
  position ="stack",  #position_dodge(preserve = "single"),
  size = 5)+
  theme_classic()+
  coord_flip()+
  scale_y_continuous(breaks=seq(0, 100, by=10))+
  geom_bar(position="stack", stat="identity", width = .5)+
  scale_x_discrete(labels = function(x) str_wrap(x, width = 35))+
  theme(legend.position="bottom",
        axis.text.x = element_text( face = "plain", size = 15, vjust= 0.5),
        axis.text.y = element_text( face = "plain", size = 15, vjust= 0.5),
        plot.title = element_text(family = "Arial", face = "bold", size = (18),colour = "steelblue4"),
        legend.title = element_text( face = "bold", size = 15, vjust= 1, hjust =-3),
        legend.text = element_text(face = "bold",
                                   colour = "steelblue4", family = "Arial", size = 8),
        legend.key.size = unit(0.8, 'cm'), #change legend key size
        legend.key.height = unit(0.8, 'cm'), #change legend key height
        legend.key.width = unit(0.8, 'cm'),
        axis.title = element_text(face = "bold",family = "Arial", size = 15, colour = "steelblue4") )+
  labs(title = "Topics per Initiative", y = "Counts per Topic", x = "Initiative")+
  guides(fill=guide_legend(nrow=6, byrow=TRUE,
                           shape = guide_legend(override.aes = list(size = 6)),
                           ))

it creates this plot with the number of counts for each stack on top. enter image description here

i have been playing with hjust and vjust and manage to get the numbers on top, otherwise they appear behind the plot like this.

enter image description here

I've been looking for solutions and playing around with the code! i need help! i really would like to have the numbers on top and in the center of each " stacked" bar. thanks!



Solution 1:[1]

Instead of using vjust or hjust when drawing text I recommend you calculate the exact position of each label and drawing them using that as the y aesthetic. This will make the graph look more elegant and doesn't need manual adjustment when values change.

library(dplyr)
library(ggplot2)

# create a sample data with some random
groups <- c("1", "2", "3", "4", "5")
colors <- c("a", "b", "c", "e", "f")
set.seed(100) # for reproducible examples
df <- tibble(
  Initiative = sample(groups, 20, replace = TRUE),
  Topic = sample(colors, 20, replace = TRUE),
  Frequency = round(runif(20, min = 1, max = 20), digits = 0)
)

# a sample df which will need to summarized by Initiative, Topic
df
#> # A tibble: 20 x 3
#>    Initiative Topic Frequency
#>    <chr>      <chr>     <dbl>
#>  1 2          f            19
#>  2 3          c            14
#>  3 1          a            13
#>  4 2          c            17
#>  5 4          e            16
#>  6 4          b            17
#>  7 2          f             3
#>  8 3          f            10
#>  9 2          f            12
#> 10 5          a            18
#> 11 4          f            20
#> 12 3          e             2
#> 13 3          c            12
#> 14 2          a            15
#> 15 1          a             6
#> 16 2          a             7
#> 17 3          c            15
#> 18 4          a            18
#> 19 4          a             5
#> 20 4          e             8
# Change the fill color to Factor for easiser control orders of the variables
# this is useful for arrange the text position as well as order of fill
# variables when drawing plot.
df$Topic <- factor(df$Topic, levels = colors)

# summarized the df
summary_df <- df %>%
  group_by(Initiative, Topic) %>%
  summarize(Frequency = sum(Frequency), .groups = "drop") %>%
  group_by(Initiative) %>%
  # arrange the variables is needed as it related to how variables will
  # be displayed on graphs which decide where position of label would be.
  arrange(Initiative, Topic) %>%
  # here I calculate the label position using cumsum
  mutate(label_pos = cumsum(Frequency) - (Frequency / 2))

summary_df
#> # A tibble: 12 x 4
#> # Groups:   Initiative [5]
#>    Initiative Topic Frequency label_pos
#>    <chr>      <fct>     <dbl>     <dbl>
#>  1 1          a            19       9.5
#>  2 2          a            22      11  
#>  3 2          c            17      30.5
#>  4 2          f            34      56  
#>  5 3          c            41      20.5
#>  6 3          e             2      42  
#>  7 3          f            10      48  
#>  8 4          a            23      11.5
#>  9 4          b            17      31.5
#> 10 4          e            24      52  
#> 11 4          f            20      74  
#> 12 5          a            18       9
ggplot(data = summary_df, aes(x = Initiative)) +
  # I only use geom_bar here - in OP you use both geom_bar & geom_col
  # that just created duplicated layer and doesn't need to be there.
  geom_bar(aes(y = Frequency, fill = Topic), 
           # I use position_stack with reverse = TRUE here to control
           # the order of the plot matched with my label_pos calculated
           position = position_stack(reverse = T),
           stat="identity", width = .5)+
  # drawing the text label using label_pos which will be
  # consistent and auto adjust to midpoint when your values changes
  geom_text(aes(y = label_pos, label = Frequency),
            color = "black", size = 5) +
  coord_flip() + theme_classic()

Created on 2022-05-23 by the reprex package (v2.0.1)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sinh Nguyen