Search code examples
rggplot2alignmentpatchwork

Ignore x-label alignment with multiple plots in patchwork: is this possible?


I really like the patchwork package, alignment of multiple plots is often better and easier to realize than in other packages (cowplot/gridextra).

However, one thing I cannot solve: Is it possible to ignore x-axis alignment in a patchwork, and only align all other elements? Or to adjust this x-axis alignment afterwords in the patchwork manually? See attached figure: I want the x-axis title from patch B and C (Petal.Length) closer to the x-axis, if possible.

enter image description here

Code that produced the image:

library(ggplot2)
library(patchwork)

plot.1 <- ggplot(iris, aes(x = Species, y = Petal.Width)) +
  geom_boxplot() +
  scale_x_discrete(labels = c("A very\nvery\nlong\nlabel", "","")) +
  labs(x = element_blank())

plot.2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
  geom_point()

(plot.1 |plot.2)/(plot.2|plot.1) + 
  plot_annotation(tag_levels = "A")

Solution

  • OP example

    library(ggplot2)
    library(patchwork)
    
    plot.1 <- ggplot(iris, aes(x = Species, y = Petal.Width)) +
      geom_boxplot() +
      scale_x_discrete(labels = c("A very\nvery\nlong\nlabel", "","")) +
      labs(x = element_blank())
    
    plot.2 <- ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
      geom_point()
    
    (plot.1 | plot.2) / 
      (plot.2 | plot.1) + 
      plot_annotation(tag_levels = "A")
    

    reshow OP figure

    Use negative margins

    It took me a while to realize that the issue derives from the need to align a mixture of axis.texts and axis.titles. Even though they are grammatically different elements, the categorical axis text serves most or all of the practical function that the continuous axis title does. Titles will always be grid-aligned outside of the aligned text region. Justification alone can't solve that problem, but negative margins can.

    Notes:

    • The negative margin value you will want depends on other scaling factors in the plot, so this is one of those polish at the very end solutions. -50 worked in this case, but I've needed to use other values
    • It also works for y-axis
    • I messed around with parentheses but otherwise the plot spec is identical other than the extra theme() layer
    • You normally will not want to include this negative margin theme snippet in the individual plot definition, because it will appear to cause a bug there and you won't be able to iteratively refine the margin value that way, at least not easily if your script/Rmd is long and/or complex. This really is an issue only at the level of assembling the plots with patchwork, so it is probably best practice to keep a polishing tweak like this where you're defining the patchwork assembly, as I've shown here.
    (plot.1 | (
      plot.2 +
        theme(axis.title.x = element_text(margin = margin(t = -50, unit = "pt"))))
    ) / (
      (plot.2 +
         theme(axis.title.x = element_text(margin = margin(t = -50, unit = "pt")))) | 
        plot.1
    ) + 
      plot_annotation(tag_levels = "A")
    

    enter image description here