I’m using geom_label_repel to place percentages in a faceted horizontal bar chart, which mostly works great, except that I can’t seem to find a way to shift the labels to the middle (or as close as possible) of each bar. Instead, they are justified right, which visually is a bit confusing as some bars are close to one another.
I’ve tried using h_just and v_just, but these just center the text within its box rather than the box within the plot. If I drop position_identity() in favour of nudge_x the bars all drop out, so that doesn’t get me there. Assuming there is some simple way to do this, but I’m not seeing it!
Here’s the function I’ve made to do the plot:
plot_single_result_with_facets <- function(x) {
x %>%
# we need to get the data including facet info in long format, so we use pivot_longer()
pivot_longer(!response, names_to = "bin_name", values_to = "b") %>%
# add counts for plot below
count(response, bin_name, b) %>%
# remove nas
filter(!is.na(response)) %>%
# add grouping by bins
group_by(bin_name,b) %>%
# calculate percentages
mutate(perc=paste0(round(n*100/sum(n),0),"%")) %>%
# run ggplot
ggplot(aes(x = n, y = "", fill = response, label = perc)) +
# reversing order here using forcats::fct_rev() note - needs to be changed under geom_label_repel as well
geom_col(position=position_fill(), aes(fill=forcats::fct_rev(response))) +
coord_cartesian(clip = "off") +
geom_vline(xintercept = x_limits, linetype = 3) +
geom_label_repel(
# important to make sure grouping of data matches grouping of labels so they aren't backwards
# reversing order here using forcats::fct_rev() note - needs to be changed above as well
aes(group = forcats::fct_rev(response), label = perc),
# justify text using center = 0.5, left = 0 and right = 1
hjust = 0.5,
vjust = 0.5,
direction = "y",
force = 1.5,
fill = "white",
# font size in the text labels
size = 1.5,
# allow labels to overlap
max.overlaps = Inf,
# make sure that bars are included
position = position_fill(),
# hide points
segment.size = 0.2,
point.size = NA,
# reduce padding around each text label
box.padding = 0.001
) +
scale_fill_brewer(palette="YlOrBr", direction = -1) +
scale_x_continuous(labels = scales::percent_format(), expand = c(0.05, 0.05)) +
facet_grid(vars(b), vars(bin_name), labeller=as_labeller(facet_names)) +
labs(title = title, caption = caption, x = "", y = "") +
guides(fill = guide_legend(title = NULL)) +
theme_classic()
}
And the code that uses it:
caption <- NULL
df <- select(data, Q51_bin, Q52_bin, Q57_bin, Q53_bin, Q4)
df <- as_factor(df)
names(df) <- c("Q51_bin", "Q52_bin", "Q57_bin", "Q53_bin", "response")
facet_names <- c(`Q51_bin` = "Nature Relatedness", `Q52_bin` = "Spirituality", `Q57_bin` = "Religiosity", `Q53_bin` = "Politics L/R", `low`="low", `medium`="medium", `high`="high")
facet_labeller <- function(variable,value){return(facet_names[value])}
x_limits <- c(.50, NA)
facet_grid(~fct_relevel(df,'Nature Relatedness','Spirituality','Religiosity','Politics L/R'))
plot_single_result_with_facets(df)
ggsave("figures/q5_faceted.png", width = 20, height = 10, units = "cm")
Here’s the plot as it currently stands:
And a bit of data to make it reproducible:
df <- structure(list(Q51_bin = structure(c(2L, 2L, 2L, 2L), levels = c("low", "medium", "high"), class = "factor"), Q52_bin = structure(c(3L, 2L, 2L, 2L), levels = c("low", "medium", "high"), class = "factor"), Q57_bin = structure(c(2L, 2L, 2L, 2L), levels = c("low", "medium", "high"), class = "factor"), Q53_bin = structure(c(2L, 3L, 2L, 2L), levels = c("low", "medium", "high"), class = "factor"), Q4 = structure(c(2, 3, 3, 5), label = "How much have you thought about climate change before today?", format.spss = "F40.0", display_width = 5L, labels = c(`Not at all` = 1, `A little` = 2, Some = 3, `A lot` = 4, `A great deal` = 5, `Don't know` = 99), class = c("haven_labelled", "vctrs_vctr", "double"))), class = c("rowwise_df", "tbl_df", "tbl", "data.frame"), row.names = c(NA, -4L), groups = structure(list(.rows = structure(list(1L, 2L, 3L, 4L), ptype = integer(0), class = c("vctrs_list_of", "vctrs_vctr", "list"))), row.names = c(NA, -4L), class = c("tbl_df", "tbl", "data.frame")))
>Solution :
You need to put vjust = 0.5 inside position_fill:
x %>%
pivot_longer(!response, names_to = "bin_name", values_to = "b") %>%
count(response, bin_name, b) %>%
filter(!is.na(response)) %>%
group_by(bin_name,b) %>%
mutate(perc=paste0(round(n*100/sum(n),0),"%")) %>%
ggplot(aes(x = n, y = "", fill = response, label = perc)) +
geom_col(position=position_fill(), aes(fill=forcats::fct_rev(response))) +
coord_cartesian(clip = "off") +
geom_vline(xintercept = x_limits, linetype = 3) +
geom_label_repel(
aes(group = forcats::fct_rev(response), label = perc),
hjust = 0.5,
vjust = 0.5,
direction = "y",
force = 1.5,
fill = "white",
size = 1.5,
max.overlaps = Inf,
position = position_fill(vjust = 0.5),
segment.size = 0.2,
point.size = NA,
box.padding = 0.001
) +
scale_fill_brewer(palette="YlOrBr", direction = -1) +
scale_x_continuous(labels = scales::percent_format(), expand = c(0.05, 0.05)) +
facet_grid(vars(b), vars(bin_name), labeller=as_labeller(facet_names)) +
labs(title = 'title', caption = caption, x = "", y = "") +
guides(fill = guide_legend(title = NULL)) +
theme_classic()

