rggplot2

ggplot: how do you put two graphs side-by-side and expand the canvas to keep the default layouts?


I have some ggplots which I have tweaked to make sure the annotations fit nicely within the bounds of the plot. For example:

enter image description here

Now I want to put two of these images side-by-side. I've tried to do this with grid.arrange and patchwork, but the layout of the graph changes, presumably because it's being fit into a smaller space:

enter image description here

Everything is more cramped, points are packed together so it's harder to read and the annotation boxes go off the side of the graph.

What I want to do is to expand the canvas by exactly enough that each graph looks exactly as it did when plotted alone (i.e. when plotted using the default canvas size). It would presumably need to be a little more than twice as wide, to allow for padding. How would I achieve this?

Example code:

library(ggplot2)
library(ggtext)
library(tidyverse)
library(patchwork)

N <- 10000
rho = 0.5
  
mu <- c(0, 0)
sigma <- matrix(c(1,   rho,
                  rho, 1   ), 
                nrow = 2, ncol = 2, byrow = TRUE)
            
vs = MASS::mvrnorm(N, mu, sigma)
df = tibble(rMZall = vs[,1], rDZall = vs[,2], n_twin_pairs = runif(N, 5000, 50000))

plot <- function(min_twin_pairs) {
  ggplot(data=df |> filter(n_twin_pairs > min_twin_pairs), aes(rMZall, rDZall)) + 
    ggtitle(sprintf("Studies with > %d twin pairs", min_twin_pairs)) + 
    theme(legend.position="none") +
    # Bounds
    scale_x_continuous(name = "rMZ", limits = c(-0.05, 1.1), breaks = seq(0,1,0.25)) +
    scale_y_continuous(name = "rDZ", limits = c(-0.05, 1), breaks = seq(0,1,0.25)) +
    coord_fixed() +
    # Scatterplot
    stat_density2d(aes(fill=..level.., alpha=..level..), geom='polygon', colour='black', linewidth = 0.1) + 
    scale_fill_continuous(low="green", high="red") +
    geom_point(aes(size = sqrt(n_twin_pairs))) +
    scale_size_continuous(range = c(0, 1)) + 
    # AE and AD lines
    geom_abline(intercept = 0, slope = .5) +
    annotate(geom = "richtext", x = 1, y = .5, label = "AE model", angle = 26.6) + 
    geom_abline(intercept = 0, slope = .25) +
    annotate(geom = "richtext", x = 1, y = .25, label = "AD model", angle = 14.0) 
}

plot1000 = plot(min_twin_pairs = 1000)
plot4000 = plot(min_twin_pairs = 4000)

plot1000
plot4000

plot1000 + plot4000

Solution

  • You could instead use geomtextpath, specifically geomtextpath::geom_labelabline(). This handles the positioning automatically so you don’t need to futz with angles, canvas sizing, and the like.

    library(tidyverse)
    library(geomtextpath)
    library(patchwork)
    set.seed(13)
    
    plot <- function(min_twin_pairs) {
      ggplot(data=df |> filter(n_twin_pairs > min_twin_pairs), aes(rMZall, rDZall)) + 
        ggtitle(sprintf("Studies with > %d twin pairs", min_twin_pairs)) + 
        theme(legend.position="none") +
        # Bounds
        scale_x_continuous(name = "rMZ", limits = c(-0.05, 1.1), breaks = seq(0,1,0.25)) +
        scale_y_continuous(name = "rDZ", limits = c(-0.05, 1), breaks = seq(0,1,0.25)) +
        coord_fixed() +
        # Scatterplot
        stat_density2d(aes(fill=..level.., alpha=..level..), geom='polygon', colour='black', linewidth = 0.1) + 
        scale_fill_continuous(low="green", high="red") +
        geom_point(aes(size = sqrt(n_twin_pairs))) +
        scale_size_continuous(range = c(0, 1)) + 
        # AE and AD lines
        geom_labelabline(intercept = 0, slope = .5, label = "AE model", hjust = 0.85) +
        geom_labelabline(intercept = 0, slope = .25, label = "AD model", hjust = 0.85)
    }
    
    plot1000 <- plot(min_twin_pairs = 1000)
    plot4000 <- plot(min_twin_pairs = 4000)
    
    plot1000 + plot4000