Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to fixed axis_nested in a facet plot?

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")

enter image description here

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:

enter image description here

But what I want is this:

enter image description here

The group labels in fixed position in x-direction and breaks in different groups in y-direction

If there are other solutions?

like image 300
NiceB Avatar asked Sep 18 '25 22:09

NiceB


1 Answers

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

like image 139
stefan Avatar answered Sep 20 '25 13:09

stefan