It is common to tinker with the width and height of ggplot and/or grob objects to ensure important features of plots are legible at the expected scale of the plot. This ensures that e.g., text is not cut off within each plot. When it comes to plots with multiple nested sub-plots, an additional concern is overlap across plots: one element of the multi-element plot may spill into the next, (partially) covering it up.
Is there any general solution to avoiding this between-subplot overlap for a plot of fixed dimensions (i.e., Z by Y cm) with a package like gridExtra? I am surprised that when one specifies the relative width of two gtable objects in a call to grid.arrange() that at "too small" or "too large" of widths, elements of plots can spill into others instead of scaling proportionally. While scaling would cause the contents to potentially become illegible if too small/squished, it's much more complicated to try to jointly optimize two parameters at once compared to a hyperparameter essentially.
I am aware that one can in theory manually add a margin to the image to ensure this doesn't become a problem, but this is problematic as (a) it also affects the spacing between subplots when there is no risk of overlap and (b) is a manual and tedious process potentially.
Below I show some examples where I was wondering if there is a means of rescaling per the above.
Example Data/Objects
library(ggplot2)
library(gridExtra)
library(grid)
set.seed(123)
df_list <- replicate(4, data.frame(x = sample(1:100, 20, replace = T),
group = rep(c(
"alpha two", "beta one"
), 10)), simplify = F)
####################################################################
# Make 4 plots, two with a legend and 2 without
####################################################################
plots_leg <- lapply(df_list[1:2], function(p) {
ggplot(p, aes(x = x, group = group, fill = group)) +
geom_density()
})
plots_noleg <- lapply(df_list[3:4], function(p) {
ggplot(p, aes(x = x)) +
geom_density()
})
####################################################################
# Create two objects that will each be a column in our final grid
# which will have a common Y-axis and legend
####################################################################
# Extract an arbitrary legend so we can show only 1 shared legend
# https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
get_legend <- function(obj) {
tmp <- ggplot_gtable(ggplot_build(obj))
leg <- which(sapply(tmp$grobs, function(x)
x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend)
}
shared_leg <- get_legend(plots_leg[[1]])
# Create each column of our 2x2 plot grid, removing legends
# Column 1
col1 <- arrangeGrob(
grobs =
lapply(c(plots_leg[1], plots_noleg[1]) ,
function(p) {
p + guides(fill = "none",
color = "none",
pattern = "none") +
ylab("")
}),
nrow = 2,
top = textGrob("DV: A col1 title here"),
heights = c(1, 1)
)
# Column 2
col2 <- arrangeGrob(
grobs =
lapply(c(plots_leg[2], plots_noleg[2]) ,
function(p) {
p + guides(fill = "none",
color = "none",
pattern = "none") +
ylab("")
}),
nrow = 2,
top = textGrob("DV: A col2 title here"),
heights = c(1, 1)
)
####################################################################
# Combine each column into a single grob then plot
####################################################################
# Create combined object
combined <- arrangeGrob(
col1,
col2,
ncol = 2,
widths = c(1, 1),
left = textGrob("This is the shared Y-axis", rot = 90)
)
Issues with finding a "good" relative width
Legend overlaps plot backgrounds unless device is massive
Is there a way to ensure the legend itself scales down in size instead?
grid.arrange(combined,
shared_leg,
ncol = 2,
widths = c(9, 1))

More relative width for legend eliminates overlap, showing it's a scaling issue
grid.arrange(combined,
shared_leg,
ncol = 2,
widths = c(9, 4))

If we give the legend too much width, the titles overlap
Is there any way to ensure that the title scales down to never overlap?
grid.arrange(combined,
shared_leg,
ncol = 2,
widths = c(9, 20))
