Search code examples
rggplot2pdf-generationr-grid

Add separate legend to PDF from lapply


I've created a multi-page PDF with plots generated from a few hundred unique identifiers. Basically, I would like to add a separate legend panel once per page. The PDF is basically constructed as detailed here and here

There are dozens of walk-throughs on how to add a separate legend for a few graphical objects using grid.arrange, the most promising are here and here.

Basically, the steps are:

  1. create the database
  2. use lapply to make a list of the graphical objects and
  3. create a pdf the chunks up the list of graphical objects.

I suspect the process falls apart at step 3 - adding the legend to the list of grobs.


To reproduce the problem

color.names <- setNames(c("A", "B", "C", "D", "F"), c("green3", "chocolate1", "darkgoldenrod1", "firebrick1"))    

group.colors <- c(A = "#333BFF", B = "#CC6600", C ="#9633FF", D = "#E2FF33", F = "#E3DB71")

SOexample <- data.frame(
        studentid = runif(100,min=500000, max=999999),
        grade = runif(100, min=20, max=100),
        lettergrade = sample(c("A", "B","C","D","F"),size=100,replace=TRUE),
        firstname = sample(c("Alan", "Billy","Charles","Donna","Felicia"),size=100,replace=TRUE)
        )

To generate the legend

df <- SOexample
gl <- ggplot(df, aes(x=" ", y=as.numeric(grade), ymin=50, ymax=100))+ geom_boxplot()+ guides(fill=FALSE) + geom_point(aes(colour=lettergrade)) + labs( x=" ", y=" ") + ggtitle(sprintf("%s", df$firstname), aes(cex=.05)) + scale_colour_manual(name="Number", values=group.colors) +              scale_fill_manual(name="", values="red") + theme_grey() +               theme(legend.position="none", plot.title = element_text(size = 8, face = "bold"), plot.subtitle=element_blank()) +                theme(axis.title.x=element_blank(), axis.text.x=element_blank(), axis.ticks.x=element_blank()) 

The function to grab the legend using cowplot

install.packages("cowplot")
library(cowplot)
leg <- get_legend(gs + theme(legend.position="right"))

To make all the graphical objects

plist = lapply(split(SOexample, factor(SOexample$studentid)), function(df) { ggplot(df, aes(x=" ", y=as.numeric(grade), ymin=50, ymax=100))+               geom_boxplot()+ guides(fill=FALSE) + geom_point(aes(colour=lettergrade)) +                labs( x=" ", y=" ") + ggtitle(sprintf("%s", df$firstname), aes(cex=.05)) + scale_colour_manual(name="Number", values=group.colors) +                scale_fill_manual(name="", values="red") + theme_grey() +theme(legend.position="none", plot.title = element_text(size = 8, face = "bold"), plot.subtitle=element_blank()) + theme(axis.title.x=element_blank(),axis.text.x=element_blank(), axis.ticks.x=element_blank())})

Making the PDF

   pdf("allpeople.pdf", pointsize=8)
    for (i in seq(1, length(plist), 11)) {
        grid.arrange(grobs=plist[i:(i+11)],
                     ncol=4, left="Magic Numbers", bottom=" ")
    }
    dev.off()

I suspect the process is falling apart in the create PDF stage. Ideally, I would add the legend as a graphical object in / at the grid.arrange step, e.g.,

grobs[12]<- leg

But no luck, and also the last item in the plist() process seems to have not been fully converted to a graphical object.

Using this auto-generating method, i.e., cannot list out graphical objects individually, how does one add the legend to each page of the PDF?


Solution

  • There are various options (ggsave('file.pdf',marrangeGrob(plist,ncol=4,nrow=3)), for instance), but I'd probably do something like this for finer control:

    pl <- split(plist, gl(10,10))
    pdf("allpeople.pdf", pointsize=8)
    for (i in seq_along(pl)) {
      grid.arrange(grobs=c(pl[[i]], list(leg)), 
                   ncol=4, 
                   left="Magic Numbers", 
                   bottom=" ")
    }
    dev.off()