Unai Vicente
Unai Vicente

Reputation: 379

List of plots arranged in a particular layout in a multipage PDF not working

Suppose that out of a loop I am getting a particular set of plots that I am saving in a list of plots like in my reproducible example.

plot_list = list()

for (i in 1:5){

dummy1 = ggplot(iris, aes(x = Petal.Length)) +
  geom_histogram()

dummy2 = ggplot(iris, aes(x = Petal.Length, color=Species)) +
  geom_boxplot()

dummy3 = ggplot(iris, aes(x = Petal.Length, y=Petal.Width, color=Species)) +
  geom_point()

plot_list[[length(plot_list)+1]] = list(dummy1,dummy2,dummy3) 

}

After the iteration, once I have the plot list ready, I want to create a single pdf page with a particular layout matrix passed into every page. Following different posts I´ve tried this code:

lay = rbind (c(1,1,2,2),
             c(1,1,3,3),
             c(1,1,3,3))


grDevices::cairo_pdf("plots.pdf", onefile = TRUE)
for (i in seq(length(plot_list))) {
  do.call('marrangeGrob',list(plot_list[[i]], layout_matrix=lay))  
}
dev.off()

Unfortunately it only returns a blank pdf with a single page. Any help is appreciated.

UPDATE: Very importantly, my example serves as a reproducible example but its structure cannot be modified. The reason it comes out of a for is because in my original code comes from a foreach so the answer requires to be compatible with the structure I am proposing.

Upvotes: 2

Views: 240

Answers (2)

user20650
user20650

Reputation: 25904

To plot ggplot's or other grid graphics then an explicit print is required: use grid::grid.draw (see FAQ 7.22).

Additionally, using marrangeGrob can add an extra newpage; this fix unfortunately has an empty page at the start but this still works. Or you could move the marrangeGrob outside of the pdf call ... if this fits with our workflow.

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

# Baptiste's fix
# if your example in your question represents your real example then you 
# could use `arrangeGrob` and avoid some of the newpage tweaks
grid.draw.arrangelist <- function(x, ...) {
  for(ii in seq_along(x)){
    if(ii>1) grid.newpage() 
     grid.draw(x[[ii]])
}
}

# Output plot
grDevices::cairo_pdf("plots.pdf", onefile = TRUE)

for (i in seq_along(plot_list)) {
  
  p <- marrangeGrob(grobs=plot_list[[i]], 
                    layout_matrix=lay, 
                    top = quote(paste("page", i, "of", length(plot_list))))
  grid::grid.draw(p) # need to be explicitly drawn 
  
  if(i < length(plot_list)) grid.newpage() # so plots are not drawn over each other
}

dev.off()

Upvotes: 1

stefan
stefan

Reputation: 125438

Here is an approach using lapply instead of a for loop and patchwork to combine your plots:

library(patchwork)
library(ggplot2)

plot_list <- lapply(
  1:5,
  \(x) {
    list(
      ggplot(iris, aes(x = Petal.Length)) +
        geom_histogram(),
      ggplot(iris, aes(x = Petal.Length, color = Species)) +
        geom_boxplot(),
      ggplot(iris, aes(x = Petal.Length, y = Petal.Width, color = Species)) +
        geom_point()
    )
  }
)

lay <-
"
1122
1133
1133
"

grDevices::cairo_pdf("plots.pdf", onefile = TRUE)
lapply(
  plot_list, patchwork::wrap_plots,
  design = lay
)
#> [[1]]
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> 
#> [[2]]
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> 
#> [[3]]
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> 
#> [[4]]
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> 
#> [[5]]
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
dev.off()
#> quartz_off_screen 
#>                 2

enter image description here

And a second option would be to use grid.arrange:

grDevices::cairo_pdf("plots.pdf", onefile = TRUE)
for (i in seq(length(plot_list))) {
  grid.arrange(plot_list[[i]], layout_matrix = lay)
}
dev.off()

Upvotes: 1

Related Questions