rggplot2gridextra

Keeping two plots vertically aligned with ggplot2/gridExtra


I am trying to merge multiple ggplot2 plots into a single picture, while retaining control over the respective height of each element. The following code works well and it generates a barplot and a curve plot that are perfectly aligned to each other (see here):



library(ggplot2)
library(grid)
library(gridExtra)

# Save plot as PDF (set to NULL initially)
pdf(file = NULL)

# Data frame for the first plot
df_1 <- data.frame(
    data = c(0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.2, 0.2, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.4, 0.2,
             0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.5, 0.1, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.2, 0.2,
             0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.4, 0.2, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.5, 0.1,
             0.5, 0.1, 0.15, 0.5, 0.5, 0.4),
    base = 1:54,
    value = c(0.9557, 0.4407, 0.141, 3.4229, 3.5197, 4.7386, 6.9897, 0.3958, 4.7268, 1.8085,
              10.5312, 5.9011, 7.9393, 5.7363, 13.8539, 11.2096, 14.8407, 11.4008, 0.5412, 10.3042,
              11.3208, 15.6669, 8.2177, 12.6917, 20.7124, 10.9896, 24.7246, 7.0544, 21.0489, 16.4536,
              7.4753, 28.2106, 32.7284, 29.7557, 23.3567, 29.9186, 30.3813, 15.1307, 20.2323, 34.4262,
              20.8036, 18.7994, 41.7036, 26.828, 10.7628, 28.2335, 4.4982, 3.6528, 33.8571, 11.6208,
              43.54, 26.7859, 22.2176, 11.0588)
)

# First plot with ggplot
plot_1 <- ggplot(df_1, aes(x = base, y = data, fill = value)) +
    geom_bar(stat = 'identity', position = position_dodge(width = 0.9)) +
    scale_fill_distiller(palette = 'YlGnBu') +
    theme(
        axis.text.x = element_text(size = 9, angle = 0, vjust = 0.5),
        axis.text.y = element_text(size = 9),
        legend.position = 'right',
        legend.spacing.x = unit(5, 'pt'),
        legend.text = element_text(margin = margin(0, 0, 0, 0, 'pt'), size = 10),
        legend.key = element_rect(fill = NA),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),plot.margin = margin(0,0,0,0)
    ) +
    guides(
        colour = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        shape = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        fill = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5)
    )+
    xlim(0, 55)


# Data frame for the second plot
df_2 <- data.frame(
    x1 = c(4, 12, 1, 2),
    x2 = c(9, 15, 24, 54),
    y1 = c(0, 0, 0, 0),
    y2 = c(0, 0, 0, 0),
    priority = c('high', 'low', 'mid', 'high')
)

# Second plot with ggplot
plot_2 <- ggplot(df_2) +
    geom_curve(aes(x = x1, xend = x2, y = y1, yend = y2, color = priority),
               curvature = 1, linewidth = 0.5, ncp = 1000) +
    expand_limits(y = -1) +
    coord_fixed(ratio = 22, ylim = c(-1, 0)) +
    scale_colour_brewer(palette = 'YlGnBu') +
    theme(

        legend.position = 'right',
        legend.spacing.x = unit(5, 'pt'),
        legend.text = element_text(margin = margin(0, 0, 0, 0, 'pt'), size = 10),
        legend.key = element_rect(fill = NA),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),plot.margin = margin(0,0,0,0)
    ) +
    guides(
        colour = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        shape = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        fill = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5)
    ) +
    xlim(0, 55)


# List of ggplot objects
plots <- list(plot_1, plot_2)  # Add as many plots as you need

# Apply a small margin to each plot to reduce extra white space
plots_adjusted <- lapply(plots, "+", theme(plot.margin = margin(0, 0, 0, 0)))

# Convert adjusted plots to gtables
grob_plots <- lapply(plots_adjusted, ggplotGrob)

# Calculate the maximum widths across all plots
max_widths <- do.call(unit.pmax, lapply(grob_plots, function(g) g$widths))

# Set each plot's widths to the calculated maximum widths
grob_plots <- lapply(grob_plots, function(g) { g$widths <- max_widths; g })

# Arrange the grobs with specific heights
combined_plot <- arrangeGrob(grobs = grob_plots, heights = unit(c(1,1), "null"), padding = unit(0, "pt"))


dev.off()

# Save the combined plot to a file
ggsave("~/Downloads/mytest.pdf", combined_plot,dpi = 300)

However, when I try to modify the width on the output PDF file in ggsave() (for example by setting width=20), the relative proportions of the plots are lost, and only the first one is extended to fill the print area (see here).

Can you advise me on how to fix this?


Solution

  • As pointed out in the comments, combining the plots using patchwork will keep them aligned. To control the height of each plot, you can use the height argument in the patchwork::plot_layout function. However, this only works if you remove the coord_fixed argument from plot_2.

    library(ggplot2)
    library(patchwork)
    
    # Data frame for the first plot
    df_1 <- data.frame(
      data = c(0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.2, 0.2, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.4, 0.2,
               0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.5, 0.1, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.2, 0.2,
               0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.4, 0.2, 0.25, 0.5, 0.1, 0.15, 0.5, 0.5, 0.5, 0.1,
               0.5, 0.1, 0.15, 0.5, 0.5, 0.4),
      base = 1:54,
      value = c(0.9557, 0.4407, 0.141, 3.4229, 3.5197, 4.7386, 6.9897, 0.3958, 4.7268, 1.8085,
                10.5312, 5.9011, 7.9393, 5.7363, 13.8539, 11.2096, 14.8407, 11.4008, 0.5412, 10.3042,
                11.3208, 15.6669, 8.2177, 12.6917, 20.7124, 10.9896, 24.7246, 7.0544, 21.0489, 16.4536,
                7.4753, 28.2106, 32.7284, 29.7557, 23.3567, 29.9186, 30.3813, 15.1307, 20.2323, 34.4262,
                20.8036, 18.7994, 41.7036, 26.828, 10.7628, 28.2335, 4.4982, 3.6528, 33.8571, 11.6208,
                43.54, 26.7859, 22.2176, 11.0588)
    )
    
    # First plot with ggplot
    plot_1 <- ggplot(df_1, aes(x = base, y = data, fill = value)) +
      geom_bar(stat = 'identity', position = position_dodge(width = 0.9)) +
      scale_fill_distiller(palette = 'YlGnBu') +
      theme(
        axis.text.x = element_text(size = 9, angle = 0, vjust = 0.5),
        axis.text.y = element_text(size = 9),
        legend.position = 'right',
        legend.spacing.x = unit(5, 'pt'),
        legend.text = element_text(margin = margin(0, 0, 0, 0, 'pt'), size = 10),
        legend.key = element_rect(fill = NA),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),plot.margin = margin(0,0,0,0)
      ) +
      guides(
        colour = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        shape = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        fill = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5)
      )+
      xlim(0, 55)
    
    
    # Data frame for the second plot
    df_2 <- data.frame(
      x1 = c(4, 12, 1, 2),
      x2 = c(9, 15, 24, 54),
      y1 = c(0, 0, 0, 0),
      y2 = c(0, 0, 0, 0),
      priority = c('high', 'low', 'mid', 'high')
    )
    
    # Second plot with ggplot
    plot_2 <- ggplot(df_2) +
      geom_curve(aes(x = x1, xend = x2, y = y1, yend = y2, color = priority),
                 curvature = 1, linewidth = 0.5, ncp = 1000) +
      expand_limits(y = -1) +
      coord_fixed(ratio = 22, ylim = c(-1, 0)) +
      scale_colour_brewer(palette = 'YlGnBu') +
      theme(
        
        legend.position = 'right',
        legend.spacing.x = unit(5, 'pt'),
        legend.text = element_text(margin = margin(0, 0, 0, 0, 'pt'), size = 10),
        legend.key = element_rect(fill = NA),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),plot.margin = margin(0,0,0,0)
      ) +
      guides(
        colour = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        shape = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5),
        fill = guide_legend(label.position = 'right', title.position = 'top', title.hjust = 0.5)
      ) +
      xlim(0, 55)
    
    
    p <- plot_1 / plot_2 
    
    
    # Save the combined plot to a file
    ggsave("~/Downloads/mytest.pdf", 
           p,
           width = 20, height = 10)
    

    enter image description here

    If you want to adjust the heights, you can do:

    p <- plot_1 / plot_2 + plot_layout(heights = c(2,1))
    p
    

    enter image description here

    To use a large width in ggsave, one option is to change the aspect ratio of the plot:

    p <- plot_1 / plot_2 & 
      theme(aspect.ratio = 1/10)
    
    ggsave("~/Downloads/mytest.pdf", 
           p,
           width = 40, height = 10)
    

    enter image description here