patchwork icon indicating copy to clipboard operation
patchwork copied to clipboard

Arrangement of plots with absolute dimensions using `theme(panel.widths, panel.heights)` in ggplot2 v4.0.0

Open jbengler opened this issue 7 months ago • 5 comments

Since ggplot2 4.0.0 it is possible to generate plots with absolute dimensions using theme(panel.widths, panel.heights)

When feeding those plots into patchwork, one runs into some unexpected results. Here are some examples:

library(ggplot2)
library(patchwork)

p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp)) + 
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear)) + 
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))
p3 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear)) + 
  facet_wrap(vars(gear)) +
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))

p1 + p2 # looks good
p1 / p2 # looks good
p1 | p2 # looks good
(p1 | p2) / (p2 + p1) # vertical spacing dynamic instead of fixed
(p1 | p2) / p1 # bottom plot looses its absolute width

p1 + p3 # unexpected
p1 / p3 # looks good
p1 | p3 # unexpected
(p1 | p3) / (p3 + p1) # unexpected
(p1 | p3) / p1 # unexpected

Sorry for bringing up this potentially unpleasant topic 🫣

Best wishes Broder

jbengler avatar Sep 22 '25 21:09 jbengler

In the latest version of ggalign in CRAN, I’ve introduced this behavior. I’ll also explore whether I can integrate all of these features into patchwork without causing major disruptions to existing code. However, this integration is challenging since ggalign uses a different strategy for aligning plots, and it can even handle strip placements in different locations (please see https://github.com/thomasp85/patchwork/issues/446) and collect guide legends in the four sides (top, left, bottom, right) or inner position.

Could you please confirm if the following behavior aligns with your expectations?

library(ggalign)
p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp)) + 
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear)) + 
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))
p3 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear)) + 
  facet_wrap(vars(gear)) +
  theme(panel.widths = unit(30, "mm"), panel.heights = unit(30, "mm"))

ggalign::align_plots(p1, p2)
Image
ggalign::align_plots(p1, p2, ncol = 1)
Image
ggalign::align_plots(
    ggalign::align_plots(p1, p2),
    ggalign::align_plots(p2, p1),
    ncol = 1
)
Image
ggalign::align_plots(
    ggalign::align_plots(p1, p2),
    p1,
    ncol = 1
)
Image
ggalign::align_plots(p1, p3)
Image
ggalign::align_plots(p1, p3, ncol = 1)
Image
ggalign::align_plots(
    ggalign::align_plots(p1, p3),
    ggalign::align_plots(p3, p1),
    ncol = 1
)
Image
ggalign::align_plots(ggalign::align_plots(p1, p3), p1, ncol = 1)
Image

Yunuuuu avatar Oct 30 '25 03:10 Yunuuuu

Hey @Yunuuuu, for most of my use cases your solution works perfectly!

First, I was thinking the lower plot in your last example should be left aligned, instead of centered. However, the more I played around with more complex layouts, "centered" is probably the best default.

jbengler avatar Oct 30 '25 14:10 jbengler

Maybe something like this? ggalign treats NULL as an empty space, allowing you to add an empty area so that the top and bottom plots align properly.

ggalign::align_plots(
    ggalign::align_plots(p1, p3),
    ggalign::align_plots(p1, NULL),
    ncol = 1
)
Image

Yunuuuu avatar Oct 30 '25 14:10 Yunuuuu

Maybe something like this? ggalign treats NULL as an empty space, allowing you to add an empty area so that the top and bottom plots align properly.

Yes, I also came across this really nice solution.

jbengler avatar Oct 30 '25 15:10 jbengler

ggalign provides the free_vp function to control the viewport. You can use the following command to left-align the plot. Note that, by default, the viewport width and height will match the underlying gtable's dimensions

ggalign::align_plots(
    ggalign::align_plots(p1, p3),
    free_vp(p1, x = 0, just = "left"),
    ncol = 1
)
Image

Yunuuuu avatar Oct 30 '25 15:10 Yunuuuu