11

I have n ggplot objects that will always have the correct number to make a lower triangle of a matrix (no diagonals). How can I arrange them in this order:

1
2 3
4 5 6
7 8 9 10

to form a grid (n = 10 here)?

Here is data to make n plots and how I'd like it to look of I had n = 6.

n <- sample(1:4, 1)
N <- sum(n:1)

library(ggplot2)
theplot <- ggplot(mtcars, aes(mpg, hp)) + geom_point()
plots <- lapply(1:N, function(i) theplot)
plots <- mapply(function(x, y) x + ggtitle(y), plots, 
    paste("PLOT", seq_along(plots)), SIMPLIFY=FALSE)

enter image description here

I suspect gridExtra may be useful here but there are blank panes. I'm open to base or add on package ideas.

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
Tyler Rinker
  • 108,132
  • 65
  • 322
  • 519

5 Answers5

12

You can pass a matrix layout to grid.arrange,

library(ggplot2)
library(gridExtra)
plots <- lapply(1:10, function(id) ggplot() + ggtitle(id))

m <- matrix(NA, 4, 4)
m[lower.tri(m, diag = T)] <- 1:10
grid.arrange(grobs = plots, layout_matrix = m)

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
9

Here's a fairly painless approach, which shouldn't be too difficult to generalize:

library(gridExtra) ## for grid.arrange()
ng <- nullGrob()
grid.arrange(plots[[1]], ng,         ng,
             plots[[2]], plots[[3]], ng,
             plots[[4]], plots[[5]], plots[[6]])

enter image description here

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • Thanks Josh. I was working through this to make it more generalizable when baptiste gave a solution that was ready to go. Interactively, this may be the preferred solution. – Tyler Rinker Apr 10 '14 at 21:19
7

I was a fan of wq::layOut for arranging ggplots when grid.arrange seems too complicated (though Josh shows that it works just fine here). If you use a new device, you don't have to worry about the holes.

layOut was removed from the wq package, so I include the code here, renamed to lay_out. It's at the bottom, after the usage examples.

lay_out(list(plots[[1]], 1, 1), # each arg is list(plot, row(s), column(s))
       list(plots[[2]], 2, 1),
       list(plots[[3]], 2, 2))

layOut1

It's main strength is when you have different sized plots.

lay_out(list(plots[[1]], 1, 1:3), 
        list(plots[[2]], 2, 1),
        list(plots[[3]], 2, 2),
        list(plots[[4]], 3, 1:2),
        list(plots[[5]], 2:3, 3))

layOut2

I think anything you could do with layOut can be done with nested grid.arrange and arrangeGrob calls, but it's often easier to think about this way.

#' Arranging ggplots
#' 
#' Provides a \code{layout}-like interface for arranging ggplots of different 
#' sizes.
#' 
#' @param ... Each argument should be of the form \code{list(plot, rows, 
#' columns)}, where \code{plot} is a ggplot (or similar), and \code{rows} and 
#' \code{columns} are consecutive sequences indicating the row and column 
#' numbers for \code{plot} to span.
#' 
#' @author Alan D. Jassby and James E. Cloern (originally from the \code{wq} 
#' package).
#' 
#' @examples
#' \dontrun{
#' gg <- ggplot(mtcars, aes(x = hp, y = mpg)) + geom_point()
#' layOut(list(gg, 1:2, 1:3),
#'        list(gg, 3, 1:2),
#'        list(gg, 3, 3))
#' }
#' 
#' @export
lay_out <- function(...) {

    x <- list(...)
    n <- max(sapply(x, function(x) max(x[[2]])))
    p <- max(sapply(x, function(x) max(x[[3]])))
    grid::pushViewport(grid::viewport(layout = grid::grid.layout(n, p)))    

    for (i in seq_len(length(x))) {
        print(x[[i]][[1]],
              vp = grid::viewport(layout.pos.row = x[[i]][[2]], 
                                  layout.pos.col = x[[i]][[3]]))
    }
}
Gregor Thomas
  • 136,190
  • 20
  • 167
  • 294
1

And this is the more general solution...

rows <- 1:3
get.row <- function(i){
  if (i==1) return(arrangeGrob(plots[[1]],ncol=length(rows)))
  start=sum(seq[1:(i-1)])+1
  end  <- start+seq[i]-1
  do.call(arrangeGrob,c(lapply(start:end,function(i)plots[[i]]),ncol=length(rows)))
}
grid.newpage()
grid.arrange(do.call(arrangeGrob,c(lapply(1:length(rows),get.row),nrow=length(rows))))
jlhoward
  • 58,004
  • 7
  • 97
  • 140
1

Another generalization building off of Josh's answer

trianglePlotGrid <- function(plots){
  #take a list of plots and returns a single plot where the elements in the list arranged in a triangular grid
  #plots should be a list of 1 or 3 or 6... plots to be arranged in a trianglular structure with 1 plot in the top row
  ncols <- (-1 + sqrt(1 + 8*length(plots)))/2
  i = 0; j = 0

  grobs <- list()

  for(p in plots){
    grobs[[length(grobs)+1]] <- p
    j = (j+1) %% ncols

    while(j > i){
      grobs[[length(grobs)+1]] <- nullGrob()
      j = (j+1) %% ncols
    }

    if(j == 0) i = i + 1
  }

  do.call("grid.arrange", c(grobs, ncol=ncols))
}

df <- data.frame(x=c(1,2), y=c(1,2))
p <- ggplot(df, aes(x=x, y=y))+geom_point()
plist <- list(p, p, p, p, p, p)
trianglePlotGrid(plist)

enter image description here

Ben
  • 20,038
  • 30
  • 112
  • 189