5

I am preparing a grid of 37 ggplots using the grid.arrange function. To save space currently taken by the axis labels and to add some information like Sys.time() I would add a box to the lower right of the graphics grid.

A minimal example using the mtcars data can be found below. The real data will cover very different ranges on x axis to facetting is not an option.

Is there a way to add a "textbox" as shown in the *.pdf below within R to add further info using e.g. cat or print? Any hint would be highly appreciated.

# load needed libraries
library(ggplot2)
library(gridExtra)

# Set loop counter and create list to store objects
imax=37 
plist <- list() 

# loop to generate 37 ggplot objects
# the real example covers different ranges on x-axis so facetting
# is not an option
for(i in 1:imax){
  p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_line()  + ggtitle(i) 
  plist[[i]] <- p
}

# print to pdf in A3 format
pdf(file="out.pdf",width=16.5,height=11.7)
do.call(grid.arrange,c(plist,main="Main Title",sub="Subtitle"))
dev.off()

The output generated by above script

Update

The solution by Slowlearner using the code provided by Baptiste does exactly what I was looking for.

Another way to achieve something similar would be to use the annotate_custom() function of ggplot2 on an empty plot. Empty means that all theme() attributes are set to element_blank(). This plot could then be arranged in the grid using the following function provided by Winston Chang on his R Cookbook website. However, in this solution the textgrob would not span all the remaining empty grobs.

Community
  • 1
  • 1
Tungurahua
  • 489
  • 7
  • 21
  • I think there is `annotate` option in `ggplot` – Metrics Sep 19 '13 at 23:32
  • @Metrics Thanks a lot for the quick comment. My understanding is that `annotate` allows to add text only on the plots that are assembled in the grid. However, I would like to add the annotation in the remaining area. I suspect that this rather relates to gridExtra. But maybe I miss something out. If so, could you please be more specific how `annotate`can be used in this context? – Tungurahua Sep 19 '13 at 23:46
  • you can pass arbitrary grobs to grid.arrange – baptiste Sep 19 '13 at 23:56
  • it's probably easier with [a version of grid.arrange](http://rpubs.com/baptiste/gtable_arrange) based on gtable – baptiste Sep 20 '13 at 01:04
  • @Metrics After studying [this post]( http://stackoverflow.com/questions/14313285/ggplot2-theme-with-no-axes-or-grid) I understood that `annotate_custom()' will get me a long way if I use 'element_blank()' for a "plain vanilla" theme. – Tungurahua Sep 20 '13 at 10:26
  • Glad that it worked out. Can you post that as an answer (and not as a comment)?It should be helpful to future users. – Metrics Sep 20 '13 at 12:34
  • With reference to the image posted in the question based on my code below - I copied and pasted the code into a vanilla R session and it works and generates a png file that looks like the image in my answer and nothing like the image generated by the asker of the question. Suggest you try copy and paste again. – SlowLearner Sep 20 '13 at 12:59
  • @Metrics added the answer to the post. Thanks for the suggestion. – Tungurahua Sep 23 '13 at 07:40

1 Answers1

4

Based on mature consideration of Baptiste's comments above (i.e. I basically pinched all his code) I have put together a simple example. Obviously you will need to experiment with formatting and sizes for the plots and the textGrob will need to be defined and formatted elsewhere, but these are details. The plot generated is below and the code follows that. Most of it is taken up by the function definition with the plot code at the bottom.

enter image description here

gtable_arrange <- function(..., grobs=list(), as.table=TRUE,
                           top = NULL, bottom = NULL, 
                           left = NULL, right = NULL, draw=TRUE){
  require(gtable)
  # alias
  gtable_add_grobs <- gtable_add_grob

  dots <- list(...)
  params <- c("nrow", "ncol", "widths", "heights",
              "respect", "just", "z") # TODO currently ignored

  layout.call <- intersect(names(dots), params)
  params.layout <- dots[layout.call]

  if(is.null(names(dots)))
    not.grobnames <- FALSE else
      not.grobnames <- names(dots) %in% layout.call

  if(!length(grobs))
  grobs <- dots[! not.grobnames ]

  ## figure out the layout
  n <- length(grobs)
  nm <- n2mfrow(n)

  if(is.null(params.layout$nrow) & is.null(params.layout$ncol)) 
  {
    params.layout$nrow = nm[1]
    params.layout$ncol = nm[2]
  }
  if(is.null(params.layout$nrow))
    params.layout$nrow = ceiling(n/params.layout$ncol)
  if(is.null(params.layout$ncol))
    params.layout$ncol = ceiling(n/params.layout$nrow)

  if(is.null(params.layout$widths))
    params.layout$widths <- unit(rep(1, params.layout$ncol), "null")
  if(is.null(params.layout$heights))
    params.layout$heights <- unit(rep(1,params.layout$nrow), "null")

  positions <- expand.grid(row = seq_len(params.layout$nrow), 
                           col = seq_len(params.layout$ncol))
  if(as.table) # fill table by rows
    positions <- positions[order(positions$row),]

  positions <- positions[seq_along(grobs), ] # n might be < ncol*nrow

  ## build the gtable, similar steps to gtable_matrix

  gt <- gtable(name="table")
  gt <- gtable_add_cols(gt, params.layout$widths)
  gt <- gtable_add_rows(gt, params.layout$heights)
  gt <- gtable_add_grobs(gt, grobs, t = positions$row, 
                            l = positions$col)

  ## titles given as strings are converted to text grobs
  if (is.character(top)) 
    top <- textGrob(top)
  if (is.character(bottom)) 
    bottom <- textGrob(bottom)
  if (is.character(right)) 
    right <- textGrob(right, rot = -90)
  if (is.character(left)) 
    left <- textGrob(left, rot = 90)

  if(!is.null(top)){
    gt <- gtable_add_rows(gt, heights=grobHeight(top), 0)
    gt <- gtable_add_grobs(gt, top, t=1, l=1, r=ncol(gt))
  }
  if(!is.null(bottom)){
    gt <- gtable_add_rows(gt, heights=grobHeight(bottom), -1)
    gt <- gtable_add_grobs(gt, bottom, t=nrow(gt), l=1, r=ncol(gt))
  }
  if(!is.null(left)){
    gt <- gtable_add_cols(gt, widths=grobWidth(left), 0)
    gt <- gtable_add_grobs(gt, left, t=1, b=nrow(gt), l=1, r=1)
  }
  if(!is.null(right)){
    gt <- gtable_add_cols(gt, widths=grobWidth(right), -1)
    gt <- gtable_add_grobs(gt, right, t=1, b=nrow(gt), l=ncol(gt), r=ncol(gt))
  }

  if(draw){
   grid.newpage()
   grid.draw(gt)
  }
  gt

}

# load needed libraries
library(ggplot2)

# Set loop counter and create list to store objects
imax=37
plist <- list()
p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_line() 

for(i in 1:imax){
  plist[[i]] <- p + ggtitle(i)
}

# build list of grobs
grob.list <- lapply(plist, ggplotGrob)

# prepare titles
title.main <- textGrob("Main title")
title.sub <- textGrob("Subtitle")

# then arrange as required
g <- gtable_arrange(ncol=6, grobs=grob.list, 
                    top=title.main, bottom=title.sub, draw=FALSE)
ann <- grobTree(rectGrob(), textGrob("Annotation box here"))
g <- gtable_add_grobs(g, ann, t=nrow(g)-1, l=2, r=ncol(g))

# save it all together
png(file = "out.png",width=1000, height=710, units = "px")
grid.draw(g)
dev.off()    
SlowLearner
  • 7,907
  • 11
  • 49
  • 80
  • Thanks for this great worked example! After adding a `)` to close the `lapply`function at the end I was able to run it smoothly. However, the results differ from the graphic you posted as all legends and axes are missing. I tried to change size and formatting of the graphs by changing `par()` as well as the ggplottheme, but was left without success. I would be more than happy if you could point me to the place in the function or script where I can set the margins of the ggplot-grobs (I hope I got terminology right). – Tungurahua Sep 20 '13 at 11:50
  • Happy to try to change the code to make it work better, but the plots in my answer seem to be pretty much identical to the plots you put up. The plots in my answer have axes. They do not have legends, but then neither do the plots in your question. Is what you want different to what you put in your question? Bit more detail please. – SlowLearner Sep 20 '13 at 12:10
  • No, what you posted is exactly what I was looking for. Well, I posted my result below the original question. As you can see all legends and labels are gone. I must admit that I have been tinkering for some hours with `grid`and `gridExtra`functions. However I didn't change any confguration of setup files, so my understanding would be that no pertinent traces are left. Formatting and changing the `textgrob`is fine. So if you have some clue about what is going on here, I would be very happy to know. – Tungurahua Sep 20 '13 at 12:30
  • What kind of legends did you have in mind? In the example in your question there is no need for a legend because each plot only shows one data item (so no need to distinguish one from the other). However, if you did have two lines per plot I would expect the legend to show up. Incidentally, I have updated the code to add the titles that you had in your example. Also copy and paste all the code above and try again - it works for me. – SlowLearner Sep 20 '13 at 12:47
  • All sweet now! Thanks a lot! – Tungurahua Sep 20 '13 at 16:05