I'm drawing a plot with annotation of y-axis labels using ggplot2 and ggh4x in R.
how to fixed the position of axis_nested in a facet plot with scales="free_y"
Here is the data:
ggd = data.frame(
y=c("short1","short2", "loooooooooooooooooooooong"),
x=c(1,2,4),
g=c("A", "A", "B")
)
I can get a fixed position axis_nested with:
ggplot(ggd, aes(x,interaction(y,g)))+
geom_bar(stat="identity")+
labs(y=NULL)+
guides(y="axis_nested")
If I facet the plot:
ggplot(ggd, aes(x,interaction(y,g)))+
geom_bar(stat="identity")+
labs(y=NULL)+
guides(y="axis_nested")+
facet_nested(g~., scales="free_y", space = "free")
I get this:
But what I want is this:
The group labels in fixed position in x-direction and breaks in different groups in y-direction
If there are other solutions?
I'm afraid there's no easy way to achieve this. The underlying problem is that the width of the axis text grobs is calculated per panel. Instead, one option would be to manipulate the `gtable', i.e. in the code below I set the width of the axis text grobs for all panels equal to the panel with the maximum width.
To make the example a bit more interesting, I added a third panel and used a non-standard font to show that the approach works for more general cases, whereas padding with spaces will only work for some fonts (see below). Finally, note that I switched to legendry::guide_axis_nested
as ggh4x::guide_axis_nested
is deprecated and the warnings suggest to switch to legendry
.)
ggd <- data.frame(
y = c("short1", "short2", "loooooooooooooooooooong", "shorter", "looooooooooooong"),
x = c(1, 2, 4, 5, 6),
g = c("A", "A", "B", "C", "C")
)
library(ggplot2)
library(ggh4x)
library(glue)
gg <- ggplot(ggd, aes(x, interaction(y, g))) +
geom_bar(stat = "identity") +
labs(y = NULL) +
guides(
y = legendry::guide_axis_nested(
drop_zero = FALSE
)
) +
facet_nested(g ~ ., scales = "free_y", space = "free") +
theme(axis.text.y.left = element_text(size = 12, family = "Arial Black"))
gt <- ggplotGrob(gg)
# Indices of left axes in the layout
ix_axis_l <- which(grepl("^axis-l", gt$layout$name))
# Index of the axis with the maximum axis text width
ix_max_width <- ix_axis_l[
which.max(sapply(ix_axis_l, \(x) gt$grobs[[x]]$widths[[1]]))
]
# Set the widths for the axis text grobs according to the max width
# ... and reset the viewports
ix_adjust <- setdiff(ix_axis_l, ix_max_width)
gt$grobs[ix_adjust] <- lapply(
gt$grobs[ix_adjust],
\(x) {
x$vp <- grid::viewport()
# 3 = Axis Text Grob
x$children$layout$grobs[[3]]$vp <- grid::viewport()
x$children$layout$widths <- gt$grobs[[ix_max_width]]$children$layout$widths
x$children$layout$grobs[[3]]$children$layout$widths <-
gt$grobs[[ix_max_width]]$children$layout$grobs[[3]]$children$layout$widths
x
}
)
plot(gt)
While padding with spaces works for monospaced fonts, it will not work in general, e.g. for the non-standard font the lines and the top level axis text are no longer aligned:
ggd$width <- strwidth(ggd$y, units = "inches")
ggd$pads <- round((max(ggd$width) - ggd$width) /
strwidth(" ", units = "inches"))
ggd$y <- paste0(strrep(" ", ggd$pads), ggd$y)
ggplot(ggd, aes(x, interaction(y, g))) +
geom_bar(stat = "identity") +
labs(y = NULL) +
guides(y = "axis_nested") +
facet_nested(g ~ ., scales = "free_y", space = "free") +
theme(axis.text.y.left = element_text(size = 12, family = "Arial Black"))
Created on 2025-04-01 with reprex v2.1.1