motivation

library(tidyverse, quietly=TRUE)
library(grid) 

define my_subplot

my_subplot <- function(nrow=3, ncol=1, 
                       height=7, width=7,
                       ggs) {
  # height and width are in unit inch
  # ggs: ggplot objects wrapped as list
  # order of ggs is like matrix(1:length(ggs), nrow, ncol)
  # todo: spanning
  my_layout <- grid.layout(nrow, ncol,
                           heights=unit(rep(height, nrow)/nrow, 
                                        rep("inch", nrow)),
                           widths=unit(rep(width, ncol)/ncol, 
                                       rep("inch", ncol)))
  
  fg <- frameGrob(my_layout)
  n <- length(ggs)
  for (i in 1:n) {
    my_row <- ifelse(i %% nrow == 0, nrow, i %% nrow)
    my_col <- ifelse(i %% nrow == 0, i %/% nrow, (i %/% nrow) + 1)
    fg <- placeGrob(fg, 
                    ggplotGrob(ggs[[i]]),
                    row=my_row, col=my_col)
  }
  fg
}

prepare three ggplots

d <- tibble(t=1:100, y=abs(rnorm(100)))
g1 <- ggplot(d) + geom_point(aes(t, y))
g2 <- ggplot(d) + geom_line(aes(t, y))
g3 <- g1 + geom_line(aes(t, y))

g1

g2

g3

three plots in one page using my_subplot

grid.newpage()
grid.draw(
  my_subplot(
    3, 1,
    3.5, 6.5,
    ggs=list(g1, g2, g3)
  ))

another layout setting

grid.newpage()
grid.draw(
  my_subplot(
    3, 2,
    3.5, 6.5,
    ggs=list(g1, g2, g3, g1, g2, g3)
  ))

references

LS0tCnRpdGxlOiAibXVsdGlwbGUgZ2dwbG90cyBpbiBvbmUgcGFnZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCiMgbW90aXZhdGlvbgotIGxheW91dCBtdWx0aXBsZSBnZ3Bsb3RzIGluIG9uZSBwYWdlLCBsaWtlIHN1YnBsb3QgZnVuY3Rpb24gaW4gbWF0bGFiIHNpbXBseSB1c2luZyB7Z3JpZH0gcGFja2FnZS4KCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShncmlkKSAKYGBgCgojIGRlZmluZSBteV9zdWJwbG90CgpgYGB7cn0KbXlfc3VicGxvdCA8LSBmdW5jdGlvbihucm93PTMsIG5jb2w9MSwgCiAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PTcsIHdpZHRoPTcsCiAgICAgICAgICAgICAgICAgICAgICAgZ2dzKSB7CiAgIyBoZWlnaHQgYW5kIHdpZHRoIGFyZSBpbiB1bml0IGluY2gKICAjIGdnczogZ2dwbG90IG9iamVjdHMgd3JhcHBlZCBhcyBsaXN0CiAgIyBvcmRlciBvZiBnZ3MgaXMgbGlrZSBtYXRyaXgoMTpsZW5ndGgoZ2dzKSwgbnJvdywgbmNvbCkKICAjIHRvZG86IHNwYW5uaW5nCiAgbXlfbGF5b3V0IDwtIGdyaWQubGF5b3V0KG5yb3csIG5jb2wsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodHM9dW5pdChyZXAoaGVpZ2h0LCBucm93KS9ucm93LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiaW5jaCIsIG5yb3cpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGhzPXVuaXQocmVwKHdpZHRoLCBuY29sKS9uY29sLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCJpbmNoIiwgbmNvbCkpKQogIAogIGZnIDwtIGZyYW1lR3JvYihteV9sYXlvdXQpCiAgbiA8LSBsZW5ndGgoZ2dzKQogIGZvciAoaSBpbiAxOm4pIHsKICAgIG15X3JvdyA8LSBpZmVsc2UoaSAlJSBucm93ID09IDAsIG5yb3csIGkgJSUgbnJvdykKICAgIG15X2NvbCA8LSBpZmVsc2UoaSAlJSBucm93ID09IDAsIGkgJS8lIG5yb3csIChpICUvJSBucm93KSArIDEpCiAgICBmZyA8LSBwbGFjZUdyb2IoZmcsIAogICAgICAgICAgICAgICAgICAgIGdncGxvdEdyb2IoZ2dzW1tpXV0pLAogICAgICAgICAgICAgICAgICAgIHJvdz1teV9yb3csIGNvbD1teV9jb2wpCiAgfQogIGZnCn0KYGBgCgojIHByZXBhcmUgdGhyZWUgZ2dwbG90cwpgYGB7cn0KZCA8LSB0aWJibGUodD0xOjEwMCwgeT1hYnMocm5vcm0oMTAwKSkpCmcxIDwtIGdncGxvdChkKSArIGdlb21fcG9pbnQoYWVzKHQsIHkpKQpnMiA8LSBnZ3Bsb3QoZCkgKyBnZW9tX2xpbmUoYWVzKHQsIHkpKQpnMyA8LSBnMSArIGdlb21fbGluZShhZXModCwgeSkpCgpnMQpnMgpnMwpgYGAKCiMgdGhyZWUgcGxvdHMgaW4gb25lIHBhZ2UgdXNpbmcgbXlfc3VicGxvdApgYGB7cn0KZ3JpZC5uZXdwYWdlKCkKZ3JpZC5kcmF3KAogIG15X3N1YnBsb3QoCiAgICAzLCAxLAogICAgMy41LCA2LjUsCiAgICBnZ3M9bGlzdChnMSwgZzIsIGczKQogICkpCmBgYAoKIyBhbm90aGVyIGxheW91dCBzZXR0aW5nCmBgYHtyfQpncmlkLm5ld3BhZ2UoKQpncmlkLmRyYXcoCiAgbXlfc3VicGxvdCgKICAgIDMsIDIsCiAgICAzLjUsIDYuNSwKICAgIGdncz1saXN0KGcxLCBnMiwgZzMsIGcxLCBnMiwgZzMpCiAgKSkKYGBgCgoKCiMgcmVmZXJlbmNlcwotIE11cnJlbGwsIFAuICgyMDA1KS4gUiBncmFwaGljcy4gQ2hhcG1hbiBhbmQgSGFsbC9DUkMuCi0gTXVycmVsbCwgUC4gKDIwMDkpLiBSIGdyYXBoaWNzLiBXaWxleSBJbnRlcmRpc2NpcGxpbmFyeSBSZXZpZXdzOiBDb21wdXRhdGlvbmFsIFN0YXRpc3RpY3MsIDEoMiksIDIxNi0yMjAuCi0gV2lja2hhbSwgSC4sICYgR3JvbGVtdW5kLCBHLiAoMjAxNikuIFIgZm9yIGRhdGEgc2NpZW5jZTogaW1wb3J0LCB0aWR5LCB0cmFuc2Zvcm0sIHZpc3VhbGl6ZSwgYW5kIG1vZGVsIGRhdGEuICIgTydSZWlsbHkgTWVkaWEsIEluYy4iLgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo=