Introduction

When visualizing data using a Bar Plot, there are situations where displaying percentage labels for all categories becomes unfeasible due to overlapping text. This often occurs when the proportion of one or more categories is significantly small and disproportionate compared to others. To handle this issue, there are two possible solutions:

  1. Display labels for only one category, focusing on the category of interest (e.g., “Very Satisfied”).
  2. Display labels for a few selected categories (e.g., “Very Satisfied” and “Satisfied”).

R Codes

Below are the R codes for implementing these two solutions.

#================================
#  Draft Report for Survey Data
#================================

# Clear R environment: 

rm(list = ls())

# Load some R packages: 

library(readr)
library(dplyr)
library(stringi)
library(stringr)
library(showtext)
library(ggplot2)
library(tidyr)

questions <- c("Q1: Encouraging and motivating me to participate in learning activities", 
               "Q2: Having an effective teaching style", 
               "Q3: Being well prepared", 
               "Q4: Explaining clearly the lectures and tutorials", 
               "Q5: Being approachable and responsive?", 
               "Q6: Providing a variety of perspectives and evidence", 
               "Q7: Providing feedback that supported my learning", 
               "Q8: I was satisfied with the quality of this lecturer's teaching")

responses <- c("Very Satisfied", "Satisfied", "Neutral", "Dissatisfied", "Very Dissatisfied")

# Function recodes Likert scales:

convert_to_meaning <- function(x) {case_when(x == 1 ~ "Very Dissatisfied", 
                                             x == 2 ~ "Dissatisfied", 
                                             x == 3 ~ "Neutral", 
                                             x == 4 ~ "Satisfied", 
                                             x == 5 ~ "Very Satisfied")}

# Prepare colors for ploting: 

col_Very_Dissatisfied <- "#e36c33"

col_Dissatisfied <- "#edad88"

col_neutral <- "#c7cdd1"

col_Satisfied <- "#829cb2"

col_Very_Satisfied <- "#3e6487"

# Select Font for the graph: 

my_font <- "Lato"

font_add_google(name = my_font, family = my_font)

showtext_auto()

# Load data: 
ausData <- read_csv("tqs_for Aus team.csv")

# Prepare data for ploting: 

ausData %>% 
  select(Question1:Question8) %>% 
  mutate_all(convert_to_meaning) -> df1

names(df1) <- questions

df1 %>% 
  bind_cols(ausData %>% select(CourseName)) -> dataCourseNamePloting

nrow(dataCourseNamePloting) # Number of responses

dataCourseNamePloting %>% 
  pivot_longer(cols = questions) %>% 
  group_by(name, value) %>% 
  count() %>% 
  mutate(value = factor(value, levels = responses[5:1])) %>% 
  mutate(name = factor(name, levels = questions[8:1])) -> dataForPlotingBar 

#------------------------------------------
#  Design a Graph Template (Version 1)
#------------------------------------------

# Prepare data for ploting percent text on bar plot: 

dataForPlotingBar %>% 
  pivot_wider(names_from = "value", values_from = "n") %>% 
  mutate(rate = `Very Satisfied` / (`Very Satisfied` + `Very Dissatisfied` + Dissatisfied + Neutral + Satisfied)) %>% 
  mutate(rate = round(100*rate, 1)) %>% 
  mutate(rate = as.character(rate)) %>% 
  mutate(ratetext = case_when(str_count(rate) != 4 ~ str_c(rate, ".0"), TRUE ~ rate)) %>% 
  mutate(ratetext = str_c(ratetext, "%")) -> dfText

dataForPlotingBar %>% 
  pivot_wider(names_from = "value", values_from = "n") %>% 
  mutate(rateVerySat = `Very Satisfied` / (`Very Satisfied` + `Very Dissatisfied` + Dissatisfied + Neutral + Satisfied)) %>% 
  mutate(rateSat = Satisfied / (`Very Satisfied` + `Very Dissatisfied` + Dissatisfied + Neutral + Satisfied)) -> dfText2

dfText2 %>% pull(rateVerySat) -> percent1

round(100*percent1, 1) %>% as.character() -> percent1Text

case_when(str_count(percent1Text) != 4 ~ str_c(percent1Text, ".0"), TRUE ~ percent1Text) -> percent1Text

str_c(percent1Text, "%") -> percent1Text

percent1 / 2 -> position1

dfText2 %>% mutate(posi2 = rateVerySat + 0.5*rateSat) -> dfText2

dfText2 %>% pull(rateSat) -> percent2

round(100*percent2, 1) %>% as.character() -> percent2Text

case_when(str_count(percent2Text) != 4 ~ str_c(percent2Text, ".0"), TRUE ~ percent2Text) -> percent2Text 

str_c(percent2Text, "%") -> percent2Text

dfText2 %>% pull(posi2) -> position2

ggplot() + 
  geom_col(data =  dataForPlotingBar, aes(y = name, x = n, fill = value), position = "fill") + 
  theme(axis.title = element_blank()) + 
  labs(title = "Students' Satisfaction Level with The Quality of Teaching", 
       subtitle = "The total number of responses for the 20 courses is 932. The positive feedback rate from students\nacross 8 metrics is high. Teaching style (Q2) is the aspect with the highest dissatisfaction rate.", 
       caption = "Source: Student Feedback on Teaching Survey | Swinburne University of Technology, Vietnam Campus") -> draftBarPlot

draftBarPlot + 
  scale_x_continuous(breaks = seq(0, 1, 0.25), expand = c(0, 0), labels = scales::percent) + 
  scale_y_discrete(expand = c(0, 0)) + 
  scale_fill_manual(values = c(`Very Satisfied` = col_Very_Satisfied, 
                               `Satisfied` = col_Satisfied, 
                               `Neutral` = col_neutral, 
                               `Dissatisfied` = col_Dissatisfied, 
                               `Very Dissatisfied` = col_Very_Dissatisfied)) + 
  theme(strip.text = element_text(color = "grey20", family = my_font, hjust = 0, size = 10)) + 
  theme(legend.position = "top") + 
  theme(axis.title = element_blank()) + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.7, 4), "cm")) + 
  theme(legend.text = element_text(size = 9, family = my_font, color = "grey30")) +
  theme(legend.key.height = unit(0.4, "cm")) +  
  theme(legend.key.width = unit(0.27*1, "cm")) + 
  theme(legend.title = element_blank()) + 
  theme(panel.grid.minor = element_blank()) + 
  theme(panel.grid.major.x = element_line(size = 0.5, color = "grey40")) + 
  theme(axis.text.x = element_text(color = "grey30", size = 11, family = my_font)) +
  theme(axis.text.y = element_text(color = "grey30", size = 10.1, family = my_font)) + 
  theme(plot.title = element_text(size = 14, face = "plain", color = "grey20")) + 
  theme(plot.subtitle = element_text(size = 10, color = "grey30")) + 
  theme(plot.caption = element_text(size = 8, color = "grey40", vjust = -1.5, hjust = 1)) + 
  theme(plot.title.position = "plot") +
  theme(plot.caption.position = "plot") +
  guides(fill = guide_legend(reverse = TRUE)) + 
  theme(axis.ticks.y = element_blank()) -> draftBarPlot

draftBarPlot + 
  geom_text(data = dfText2, 
            aes(y = name, x = 0.06, label = percent1Text), 
            size = 3.5, color = "white", family = my_font) -> barVersion1 

barVersion1

#------------------------------------------
#  Design a Graph Template (Version 2)
#------------------------------------------

draftBarPlot + 
  theme(panel.grid.major.x = element_blank()) + 
  geom_text(data = dfText2, 
            aes(y = name, x = position1, label = percent1Text), 
            size = 3.5, color = "white", family = my_font) + 
  geom_text(data = dfText2, 
            aes(y = name, x = position2, label = percent2Text), 
            size = 3.5, color = "white", family = my_font) -> barVersion2

barVersion2  

For an alternative approach to this problem, you can read this post on Stack Overflow.