How can I place non-overlapping direct labels on a stacked column chart?
geom_text_repel()
is moving labels that don't need to be moved, creating readability problems.
library(tibble)
library(tidyr)
library(ggplot2)
library(ggrepel)
library(dplyr)
library(scales)
set.seed(23)
n <- 4
mu <- 4E6
sales <- tibble(
year=as.factor(seq(2019, length.out=n)),
A = rnorm(n=n, mean=mu, sd=mu/4),
B = rnorm(n=n, mean=6*mu, sd=mu),
C = rnorm(n=n, mean=mu/5, sd=mu/40),
D = rnorm(n=n, mean=mu/10, sd=mu/40),
E = rnorm(n=n, mean=4*mu, sd=mu)
) %>% pivot_longer(!year, names_to="product", values_to="sales")
p <- sales %>%
group_by(year, product) %>%
summarise(sales=sum(sales)) %>%
mutate(pct_of_year_sales = sales/sum(sales),
label=paste(
scales::label_dollar(scale=1/1E6, suffix="M", accuracy=0.1)(sales),
scales::label_percent(accuracy=0.1)(pct_of_year_sales),
sep=", ")) %>%
ggplot(aes(x=year, y=sales, fill=product, label=label))
geom_col()
scale_y_continuous(labels = scales::label_dollar(scale=1/1E6, suffix="M"),
expand = expansion(mult = c(0, .05)))
p geom_text(position=position_stack(vjust=0.5))
labs(title="geom_text()",
subtitle="overlapping labels")
p geom_text_repel(position=position_stack(vjust = 0.5),
direction="y")
labs(title="geom_text_repel()",
subtitle="text for series A moves needlessly")
p
ggrepel::geom_text_repel(
min.segment.length = 0, force = 10,
aes(label = label2),
position=position_stack(vjust = 0.5), hjust = 0.25,
direction="y", na.rm=TRUE)
I added hjust=
to shift them a little, also helping to clarify the segment lines (without hjust
, they tend to connect with the commas, which is a visually-distracting artifact). You may want to play with force=
or other segment-line aesthetics to break them out more clearly. (It would be feasible, for instance, to define hjust
within the frame itself and assign that aesthetic within aes(..)
instead, in order to control the horizontal justification per-year, for instance. Just a thought.)
CodePudding user response:
I think I like @r2evans answer better, as it utilizes the tools as they're meant to be used. I went a different route.
It's really never too early to start learning about the grid...
This uses the libraries grid
and gridExtra
.
First, I saved the plot to an object, and investigated where the labels were and what settings were applied (using geom_text
, not ...repel
).
In grid
, you can set the justification for each label. So I made the vertical justification of those values in C
to 0 and in D
to 1. This was enough in my plot pane... however, depending on the size of your graph, you may have to go to values that are further apart. Just remember that .5 is the middle, not 0.
See my code comments for a more in-depth explanation.
library(grid)
library(gridExtra)
pp <- p geom_text(position=position_stack(vjust=0.5))
labs(title="geom_text()",
subtitle="overlapping labels")
pg <- ggplotGrob(pp) # create grid (gtable) of graph
# the labels in geom_text
pg$grobs[[6]]$children$GRID.text.246$label # use this to see the label order
# lbls top to btm then left to right
# get vjust to modify
gimme <- pg$grobs[[6]]$children$GRID.text.246$vjust
# which indices need to change (C and D labels)
ttxt <- seq(from = 3, by = 5, length.out = 4) # 5 labels in column
btxt <- seq(from = 4, by = 5, length.out = 4) # 4 columns
gimme[ttxt] <- 0 # set C to top of vspace
gimme[btxt] <- 1 # set D to bottom of vspace
# replace existing vjust
pg$grobs[[6]]$children$GRID.text.246$vjust <- gimme
You can view the plot with grid or change it back to a ggplot
object. To change it back to a ggplot object you can either use ggplotify::as.ggplot
or ggpubr::as_ggplot
, they do the same thing.
plot.new()
grid.draw(pg)
# back to ggplot obj
ggpubr::as_ggplot(pg)