Combining plots in a grid

Author

Charlotte Soneson, Michael Stadler

Summary

This document illustrates how to use cowplot and patchwork (two companion packages to ggplot2) to:

  • combine multiple panels into a single figure
  • include a non-ggplot2 panel and set a joint title for the figure

Prepare data

Run the following code to prepare the data used in this document:

suppressPackageStartupMessages({
    library(tibble)
    library(swissknife)
    library(ggplot2)
    library(cowplot)
    library(patchwork)
})

loadExampleData("mycars")
`mycars`: re-encoded version of `datasets::mtcars`
tibble(mycars)
# A tibble: 32 × 13
     mpg cyl    disp    hp  drat    wt  qsec    vs    am  gear  carb
   <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1  21   6      160    110  3.9   2.62  16.5     0     1     4     4
 2  21   6      160    110  3.9   2.88  17.0     0     1     4     4
 3  22.8 4      108     93  3.85  2.32  18.6     1     1     4     1
 4  21.4 6      258    110  3.08  3.22  19.4     1     0     3     1
 5  18.7 8      360    175  3.15  3.44  17.0     0     0     3     2
 6  18.1 6      225    105  2.76  3.46  20.2     1     0     3     1
 7  14.3 8      360    245  3.21  3.57  15.8     0     0     3     4
 8  24.4 4      147.    62  3.69  3.19  20       1     0     4     2
 9  22.8 4      141.    95  3.92  3.15  22.9     1     0     4     2
10  19.2 6      168.   123  3.92  3.44  18.3     1     0     4     4
# ℹ 22 more rows
# ℹ 2 more variables: engine_shape <fct>, transmission <fct>

Create individual panels

We first create the individual plots that we will later use as panels in the combined plot.

Code
p1 <- ggplot(data = mycars, mapping = aes(x = mpg, y = hp)) +
    geom_point(size = 3, alpha = 0.5, aes(color = transmission)) +
    labs(x = "Fuel efficiency (miles/gallon)", y = "Gross horsepower") +
    theme_bw(13) +
    theme(panel.grid = element_blank(),
          legend.position = "bottom")
p1

Code
p2 <- ggplot(data = mycars, mapping = aes(x = mpg)) +
    geom_histogram(bins = 10, 
                   color = "darkgrey", fill = "lightgrey") +
    labs(x = "Fuel efficiency (miles/gallon)") +
    theme_bw(13) +
    theme(panel.grid = element_blank(),
          legend.position = "bottom")
p2

Code
p3 <- ggplot(data = mycars, mapping = aes(x = transmission, y = hp)) + 
    geom_boxplot(outlier.size = -1) + 
    geom_jitter(width = 0.2, height = 0, alpha = 0.5) + 
    labs(x = "Transmission", y = "Gross horsepower") + 
    theme_bw(13) + 
    theme(panel.grid = element_blank(),
          legend.position = "bottom")
p3

Combine panels into final figure (cowplot)

Next we combine these plots into a single figure. We also want to include a jpg image of a puppy. The first thing we need to do is to convert this image into a format that can be understood by cowplot:

## Generate image object from jpg file
img <- ggdraw() + 
    draw_image("https://publicdomainvectors.org/photos/johnny_automatic_puppy.png")

Next, we combine the three plots and the puppy image into a single figure, using a grid layout.

Code
comb <- plot_grid(
    p3, p1, img, p2, 
    ncol = 2, labels = c("A", "B", "", "C"), 
    align = "hv", axis = "tblr", rel_widths = c(0.5, 1)
)
comb

We can also set a joint title for the full plot (this has to be generated separately and added as an additional figure component).

Code
## Create a joint title
jointtitle <- ggdraw() + 
    draw_label("Combined plot title",
               fontface = "bold", x = 0, hjust = 0)

plot_grid(jointtitle, 
          comb, 
          ncol = 1, rel_heights = c(0.1, 1)
)

Combine panels into final figure (patchwork)

Here we create the same combined figure as above, but now using patchwork::wrap_plots rather than cowplot.

Code
wrap_plots(p3, p1, img, p2, ncol = 2) + 
    plot_layout(widths = c(0.5, 1)) + 
    plot_annotation(tag_levels = list(c("A", "B", "", "C")), 
                    title = "Combined plot title",
                    theme = theme(plot.title = element_text(face = "bold")))

Here’s another visualization, where the different rows distribute the plot widths in different ways.

Code
(p3 + p1 + plot_layout(widths = c(1, 1))) / (img + p2 + plot_layout(widths = c(0.5, 1))) + 
    plot_annotation(tag_levels = list(c("A", "B", "", "C")),
                    title = "Combined plot title",
                    theme = theme(plot.title = element_text(face = "bold")))

Remarks

  • If panels have a shared legend and we only wish to include it once, we can extract it using cowplot::get_legend() and include it as a separate panel (note that some issues were reported with this functionality starting with ggplot2 3.5.0).
  • To include ggpairs panels, see the tips here.

Session info

Code
sessioninfo::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.5.2 (2025-10-31)
 os       macOS Sequoia 15.7.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Europe/Zurich
 date     2025-11-03
 pandoc   3.6.4 @ /usr/local/bin/ (via rmarkdown)
 quarto   1.7.28 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 package              * version date (UTC) lib source
 abind                  1.4-8   2024-09-12 [1] CRAN (R 4.5.0)
 Biobase                2.70.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 BiocGenerics           0.56.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 BiocParallel           1.44.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 cli                    3.6.5   2025-04-23 [1] CRAN (R 4.5.0)
 codetools              0.2-20  2024-03-31 [2] CRAN (R 4.5.2)
 cowplot              * 1.2.0   2025-07-07 [1] CRAN (R 4.5.0)
 curl                   7.0.0   2025-08-19 [1] CRAN (R 4.5.0)
 DelayedArray           0.36.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 dichromat              2.0-0.1 2022-05-02 [1] CRAN (R 4.5.0)
 digest                 0.6.37  2024-08-19 [1] CRAN (R 4.5.0)
 dplyr                  1.1.4   2023-11-17 [1] CRAN (R 4.5.0)
 evaluate               1.0.5   2025-08-27 [1] CRAN (R 4.5.1)
 farver                 2.1.2   2024-05-13 [1] CRAN (R 4.5.0)
 fastmap                1.2.0   2024-05-15 [1] CRAN (R 4.5.0)
 fs                     1.6.6   2025-04-12 [1] CRAN (R 4.5.0)
 generics               0.1.4   2025-05-09 [1] CRAN (R 4.5.0)
 GenomeInfoDb           1.46.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 GenomicRanges          1.62.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 ggplot2              * 4.0.0   2025-09-11 [1] CRAN (R 4.5.0)
 glue                   1.8.0   2024-09-30 [1] CRAN (R 4.5.0)
 gtable                 0.3.6   2024-10-25 [1] CRAN (R 4.5.0)
 htmltools              0.5.8.1 2024-04-04 [1] CRAN (R 4.5.0)
 htmlwidgets            1.6.4   2023-12-06 [1] CRAN (R 4.5.0)
 httr                   1.4.7   2023-08-15 [1] CRAN (R 4.5.0)
 IRanges                2.44.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 jsonlite               2.0.0   2025-03-27 [1] CRAN (R 4.5.0)
 KernSmooth             2.23-26 2025-01-01 [2] CRAN (R 4.5.2)
 knitr                  1.50    2025-03-16 [1] CRAN (R 4.5.0)
 labeling               0.4.3   2023-08-29 [1] CRAN (R 4.5.0)
 lattice                0.22-7  2025-04-02 [1] CRAN (R 4.5.0)
 lifecycle              1.0.4   2023-11-07 [1] CRAN (R 4.5.0)
 magick                 2.9.0   2025-09-08 [1] CRAN (R 4.5.0)
 magrittr               2.0.4   2025-09-12 [1] CRAN (R 4.5.1)
 Matrix                 1.7-4   2025-08-28 [2] CRAN (R 4.5.2)
 MatrixGenerics         1.22.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 matrixStats            1.5.0   2025-01-07 [1] CRAN (R 4.5.0)
 patchwork            * 1.3.2   2025-08-25 [1] CRAN (R 4.5.0)
 pillar                 1.11.1  2025-09-17 [1] CRAN (R 4.5.0)
 pkgconfig              2.0.3   2019-09-22 [1] CRAN (R 4.5.0)
 png                    0.1-8   2022-11-29 [1] CRAN (R 4.5.0)
 purrr                  1.1.0   2025-07-10 [1] CRAN (R 4.5.1)
 R6                     2.6.1   2025-02-15 [1] CRAN (R 4.5.0)
 RColorBrewer           1.1-3   2022-04-03 [1] CRAN (R 4.5.0)
 Rcpp                   1.1.0   2025-07-02 [1] CRAN (R 4.5.0)
 rlang                  1.1.6   2025-04-11 [1] CRAN (R 4.5.0)
 rmarkdown              2.30    2025-09-28 [1] CRAN (R 4.5.0)
 rstudioapi             0.17.1  2024-10-22 [1] CRAN (R 4.5.0)
 S4Arrays               1.10.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 S4Vectors              0.48.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 S7                     0.2.0   2024-11-07 [1] CRAN (R 4.5.0)
 scales                 1.4.0   2025-04-24 [1] CRAN (R 4.5.0)
 Seqinfo                1.0.0   2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 sessioninfo            1.2.3   2025-02-05 [1] CRAN (R 4.5.0)
 SparseArray            1.10.1  2025-10-31 [1] Bioconductor 3.22 (R 4.5.1)
 SummarizedExperiment   1.40.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 swissknife           * 0.44    2025-11-03 [1] Github (fmicompbio/swissknife@c69e512)
 tibble               * 3.3.0   2025-06-08 [1] CRAN (R 4.5.0)
 tidyselect             1.2.1   2024-03-11 [1] CRAN (R 4.5.0)
 UCSC.utils             1.6.0   2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 usethis                3.2.1   2025-09-06 [1] CRAN (R 4.5.0)
 utf8                   1.2.6   2025-06-08 [1] CRAN (R 4.5.0)
 vctrs                  0.6.5   2023-12-01 [1] CRAN (R 4.5.0)
 withr                  3.0.2   2024-10-28 [1] CRAN (R 4.5.0)
 xfun                   0.54    2025-10-30 [1] CRAN (R 4.5.0)
 XVector                0.50.0  2025-10-29 [1] Bioconductor 3.22 (R 4.5.1)
 yaml                   2.3.10  2024-07-26 [1] CRAN (R 4.5.0)

 [1] /Users/stadler/Library/R/arm64/4.5/library/__bioc322
 [2] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
 * ── Packages attached to the search path.

──────────────────────────────────────────────────────────────────────────────