0

I'm trying to make my multipanel ggplot with a shared legend more flexible in a ShinyApp by allowing the user to choose how many panels to plot.

Currently my code writes out the panel objects 1 at a time like this.

grid_arrange_shared_legend(p1,p2,p3,p4, ncol = 4, nrow = 1)

I do not fully understand why I can not find a way to tell the grid_arrange_shared_legend to accept a list of plots (list object) rather than writing them out 1 after the other. It throws this error:

Error in UseMethod("ggplot_build") : no applicable method for 'ggplot_build' applied to an object of class "NULL"

library(ggplot2)
library(lemon)
plotlist <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plotlist$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plotlist$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plotlist$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plotlist$p4 <- qplot(depth, price, data = dsamp, colour = clarity)
grid_arrange_shared_legend(plotlist, ncol = 4, nrow = 1)

with the use of a list, it would not matter how many plots are in the list, and I would calculate ncol or nrow based on the length of the list...

rici
  • 234,347
  • 28
  • 237
  • 341
Mark
  • 2,789
  • 1
  • 26
  • 66

2 Answers2

1

My homebrew version of the function gets that by adding a plotlist parameter, and adding the plots <- c(list(...), plotlist) line as the first line of code. That way it can take both a list of plots or separate plot objects.

grid_arrange_shared_legend_plotlist <- function(..., 
                                                plotlist=NULL,
                                                ncol = length(list(...)),
                                                nrow = NULL,
                                                position = c("bottom", "right")) {

  plots <- c(list(...), plotlist)

  if (is.null(nrow)) nrow = ceiling(length(plots)/ncol)

  position <- match.arg(position)
  g <- ggplotGrob(plots[[1]] + theme(legend.position = position))$grobs
  legend <- g[[which(sapply(g, function(x) x$name) == "guide-box")]]
  lheight <- sum(legend$height)
  lwidth <- sum(legend$width)
  gl <- lapply(plots, function(x) x + theme(legend.position="none"))
  gl <- c(gl, ncol = ncol, nrow = nrow)

  combined <- switch(position,
                     "bottom" = arrangeGrob(do.call(arrangeGrob, gl),
                                            legend,
                                            ncol = 1,
                                            heights = unit.c(unit(1, "npc") - lheight, lheight)),
                     "right" = arrangeGrob(do.call(arrangeGrob, gl),
                                           legend,
                                           ncol = 2,
                                           widths = unit.c(unit(1, "npc") - lwidth, lwidth)))

  grid.newpage()
  grid.draw(combined)

  # return gtable invisibly
  invisible(combined)
}

Using your example:

library(gridExtra)
library(grid)
library(ggplot2)
plots <- list()
dsamp <- diamonds[sample(nrow(diamonds), 300), ]
plots$p1 <- qplot(carat, price, data = dsamp, colour = clarity)
plots$p2 <- qplot(cut, price, data = dsamp, colour = clarity)
plots$p3 <- qplot(color, price, data = dsamp, colour = clarity)
plots$p4 <- qplot(depth, price, data = dsamp, colour = clarity)

grid_arrange_shared_legend_plotlist(plotlist = plots, ncol = 4)

resulting plot

mgiormenti
  • 793
  • 6
  • 14
  • still getting the same error Error in UseMethod("ggplot_build") : no applicable method for 'ggplot_build' applied to an object of class "NULL" grid_arrange_shared_legend_custom(plotlist, ncol = 4, nrow = 1) (gave the function a new name) – Mark Apr 01 '19 at 20:19
  • Are you explicitly passing the plotlist object to the plotlist parameter? (sorry they're called the same). Like `grid_arrange_shared_legend(plotlist = plotlist, ncol = 4)`. If you don't, the list of plots is going to be taken by the ... assuming that it is a single plot object, thus causing an error. – mgiormenti Apr 01 '19 at 21:26
  • ah yes that explains it. I also already realized that "plotlist" was not a smart name for the dummy code, in my real app it has a different name. I found out that my silly eval(parse solution below works like a charm too though – Mark Apr 01 '19 at 21:46
  • You're solution reads a lot more pleasant code wise though, and indeed it works fine now. cuddos. I'll accept your answer as the solution – Mark Apr 01 '19 at 21:49
  • Thanks! Glad I was somewhat useful. In retrospect, I should have included the working example from the beginning. I'll bear that in mind from now on. – mgiormenti Apr 01 '19 at 21:56
  • certainly avoids confusion indeed. I missed the argument for the function as i've had a long day of coding and work already. Thanks – Mark Apr 01 '19 at 22:02
0

The ugly text string paste solution:

Since the answers provided do not seem to work, or are not suitable (rebuilding an entirely different set of plots than the list of plot objects I already have from extensive code, I played around a bit with eval(parse(text = ....) and paste0 to dynamically generate a text string that ends up being the fully written out code (which works) without actually writing it out

nplots = 4
nrow = 2
ncol = ceiling(nplots/nrow)
eval(parse( text = paste0("grid_arrange_shared_legend(", paste0("plotlist", "[[", c(1:nplots), "]]", sep = '', collapse = ','), ",ncol =", ncol, ",nrow =", nrow, ", position = 'right',  top=grid::textGrob('My title', gp=grid::gpar(fontsize=18)))", sep = '')))

which produces:

[1] "grid_arrange_shared_legend(plotlist[[1]],plotlist[[2]],plotlist[[3]],plotlist[[4]],ncol =2,nrow =2, position = 'right', top=grid::textGrob('My title', gp=grid::gpar(fontsize=18)))"

Mark
  • 2,789
  • 1
  • 26
  • 66