rggplot2axispatchwork

Fixed aspect ratio prevents patchwork to collect axis titles


When merging axis labels with the patchwork package, problems occurred (see this question). From the answer of the question, I applied the idea of the example code to my concrete example, in which I also used a fixed aspect ratio for each plot.

Here is the example code

library(ggplot2)
library(patchwork)

# Create different datasets for each plot
df1 <- expand.grid(x = seq(300, 800, length.out = 50), y = seq(300, 600, length.out = 50))
df1$z <- with(df1, dnorm(x, mean = 500, sd = 50) * dnorm(y, mean = 400, sd = 50))

df2 <- expand.grid(x = seq(300, 800, length.out = 50), y = seq(300, 600, length.out = 50))
df2$z <- with(df2, dnorm(x, mean = 600, sd = 50) * dnorm(y, mean = 450, sd = 50))

df3 <- expand.grid(x = seq(300, 800, length.out = 50), y = seq(300, 600, length.out = 50))
df3$z <- with(df3, dnorm(x, mean = 550, sd = 50) * dnorm(y, mean = 500, sd = 50))

df4 <- expand.grid(x = seq(300, 800, length.out = 50), y = seq(300, 600, length.out = 50))
df4$z <- with(df4, dnorm(x, mean = 650, sd = 50) * dnorm(y, mean = 350, sd = 50))

# Compute global min and max for z-values across all datasets
min_z <- min(c(df1$z, df2$z, df3$z, df4$z), na.rm = TRUE)
max_z <- max(c(df1$z, df2$z, df3$z, df4$z), na.rm = TRUE)

# Generate basic ggplot() objects with fixed aspect ratio
p_tmp <- lapply(list(df1, df2, df3, df4), function(df) {
  ggplot(df, aes(x, y, fill = z)) +
    geom_raster(interpolate = TRUE) +
    theme_minimal() +
    theme(
      aspect.ratio = 1,
      legend.position = "bottom",
      legend.box = "horizontal"
    )
})

plot_aspr <- wrap_plots(
    p_tmp,
    nrow = 2,
    ncol = 2,
    axis_titles = "collect",
    guides = "collect"
  ) +
  plot_annotation(
    title = "Emission-Excitation-Matrix",
    subtitle = "Rayleigh Filtered Data") &
  labs(
    x = "Emission Wavelength / nm",
    y = "Excitation Wavelength / nm"
  ) &
  scale_fill_viridis_c(limits = c(min_z, max_z)) &
  coord_cartesian(expand = FALSE)

print(plot_aspr)

and the corresponding plot. In this plot, the axis labels are not merged, but the legends are. In addition, the individual efficiencies are distributed here and have the desired square shape by definition.

with fixed aspect ratio

Without the fixed aspect ratio

# Generate basic ggplot() objects without fixed aspect ratio

p_tmp <- lapply(list(df1, df2, df3, df4), function(df) {
  ggplot(df, aes(x, y, fill = z)) +
    geom_raster(interpolate = TRUE) +
    theme_minimal() +
    theme(
      legend.position = "bottom",
      legend.box = "horizontal"
    )
})

plot_noaspr <- wrap_plots(
    p_tmp,
    nrow = 2,
    ncol = 2,
    heights = rep(1, 2),
    widths = rep(1, 2),
    axis_titles = "collect",
    guides = "collect"
  ) +
  plot_annotation(
    title = "Emission-Excitation-Matrix",
    subtitle = "Rayleigh Filtered Data") &
  labs(
    x = "Emission Wavelength / nm",
    y = "Excitation Wavelength / nm"
  ) &
  scale_fill_viridis_c(limits = c(min_z, max_z)) &
  coord_cartesian(expand = FALSE)

print(plot_noaspr)

results in the following plot for the same data set:

no fixed aspect ratio

In this case, both the axis labels and the legends are combined. One shortcoming here is that the shape of the individual graphs in the overall graph is not automatically perfectly square. Based on the results, I conclude that axis_title = "collect" from patchwork is not compatible with aspect.ratio, but is compatible with guide = "collect". Hence the question of whether this is desired or a bug.

Edit:

After posting this question, I found an interesting question and article regarding this topic. However, the problem was mainly the alignment of the graphs and not the combination of their axis titles.


Solution

  • I didn't give it much thought to be able to explain the reason for this, but using unit(rep(1,2), rep("in",2)) instead of rep(1,2) for widths/heights seems to work. I have also used design instead of nrow/ncol to have more control over overall plotting area. Moreover, I had to move theme(...) to the end of patchwork code.

    p_tmp <- lapply(list(df1, df2, df3, df4), function(df) {
      ggplot(df, aes(x, y, fill = z)) +
        geom_raster(interpolate = TRUE) +
        theme_minimal()
    })
    
    wrap_plots(p_tmp,
               design = "AAAAABBBBB
                         AAAAABBBBB
                         AAAAABBBBB
                         AAAAABBBBB
                         AAAAABBBBB
                         CCCCCDDDDD
                         CCCCCDDDDD
                         CCCCCDDDDD
                         CCCCCDDDDD
                         CCCCCDDDDD",
      widths = unit(rep(1,2), rep("cm",2)),
      heights = unit(rep(1,2), rep("cm",2)),
      axis_titles = "collect",
      guides = "collect") +
      plot_annotation(title = "Emission-Excitation-Matrix",
                      subtitle = "Rayleigh Filtered Data") &
      labs(x = "Emission Wavelength / nm",
           y = "Excitation Wavelength / nm") &
      scale_fill_viridis_c(limits = c(min_z, max_z)) &
      coord_cartesian(expand = FALSE) &
      theme(legend.position = "bottom",
            legend.box = "horizontal")
    

    This gives us the aspect ratio of 1: