[R] Adding text for written comments to bottom of graphs

baptiste auguie baptiste.auguie at googlemail.com
Sun Apr 1 01:46:18 CEST 2012


On 1 April 2012 03:41, Paul Miller <pjmiller_57 at yahoo.com> wrote:
> Hello Baptiste,
> What you've done is very interesting. Went through and tried to understand all the steps. Reminded me of studying languages in years gone by. Always found it easier to read and understand a sentence than to construct a sentence of one's own. This is particularly true when you're just learning.
> Think I basically get it.

Apart from one detail, yes. See below.

>You've got a function (make_title) that creates the title for each graph. You've got another function (make_graph) that actually creates each graph and gives it a title using the first function. You're then applying the make_graph function to each value of key_line in the TestData dataframe using dlply. dlply is being used instead of by() because you want to start with a data frame and then end up with a list.

plyr is very convenient indeed.

> Next, you use a function called make_annotation to add the comment lines to each graph. The make_annotation function uses arrangeGrob() from the gridExtra package. You apply the function using llply this time because you are starting out with a list and want to end up with a list.

base lapply would work as well.

> Finally, you're outputting the annotated plots to the pdf destination which can handle multiple plots if you specify "multiple_pages.pdf".

That's incorrect: "multiple_pages.pdf" is simply the filename, it
doesn't mean anything to the computer. The pdf device has the ability
to produce multipage output; this happens whenever you call a new plot
in base graphics, or call grid.newpage() for grid-based graphics. Here
this happens under the hood, because the graphical objects ("grobs")
that are stored in the list are print()ed, which in turn dispatches to
the `print.arrange` method associated with arrangeGrob(), which calls
grid.newpage() by default when no viewport is specified. Wasn't that

> This seems a lot better than outputting individual graphs and then inserting them into a Word document as I have been doing. The height and width of the page are determined by the height and width options.

Yes. May I suggest that you try some tools for reproducible research?
My preference goes toward the knitr package. If you're trying to
produce automated documents with fine control over the formatting,
this is certainly the way to go.

> At this point, was hoping you could help with refining the output. I've looked at the help page for pdf() and can't see how to modify the code in the ways I'd like. Online searches and an examination of my various R books haven't turned up much either.

Again, try knitr/Sweave/brew, etc. That being said, you can play
around a bit more with grid graphics and get close enough. See below.

> So here's what I'd like to do if possible:
> 1. Get the output to open at 100% magnification. This would be nice but isn't all that important.

I think that's something you could do with LaTeX or pdf post-processing tools.

> 2. Add a document title to the top of just the first page (e.g., "Regimen Patterns for Study X").
> 3. Add 1 inch margins around the page.
> 4. Make the comment lines roughly (or exactly) the same width as the graphs and justify the text under the graphs.
> 5. Shrink the size of each graph/comment combination so it's possible to fit two such combinations on a page.

The code below implements #2-4; I think #1 and #5 are best left for
the pdf viewer.


TestData <- structure(list(profile_key = c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3), line = c(1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2,
1, 1, 1, 1, 1), instance = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 2), drug = structure(c(1L, 2L, 1L, 2L, 3L, 1L, 2L,
3L, 4L, 1L, 2L, 2L, 1L, 3L, 4L, 3L), .Label = c("Drug A", "Drug B",
"Drug C", "Drug D"), class = "factor"), pattern = structure(c(1L,
1L, 4L, 4L, 4L, 3L, 3L, 3L, 3L, 2L, 2L, 5L, 5L, 5L, 5L, 5L), .Label =
c(" Begin (A), Begin (B), End (B), End (A)",
" Begin (A, B), End (A, B)", " Begin (A, B), End (A, B), Begin (C),
End (C), Begin (D), End (D)",
" Begin (A, B, C), End (A, B), End (C)", "Begin (A, B, C), End (A, B,
C), Begin (C), Begin (D), End (C, D)"
), class = "factor"), start_drug = c(0, 0.7143, 0, 0, 0, 0, 0,
14.5714, 25.4286, 0, 0, 0, 0, 0, 20, 18), stop_drug = c(21, 18,
20, 20, 36, 7.429, 7.429, 21.857, 231.286, 35.286, 35.286, 17,
17, 17, 32.8571, 32.8571)), .Names = c("profile_key", "line",
"instance", "drug", "pattern", "start_drug", "stop_drug"), row.names = c(NA,
-16L), class = "data.frame")

TestData <- melt(TestData, measure.vars = c("start_drug", "stop_drug"))
TestData$drug <- factor(TestData$drug, levels = c("Drug D", "Drug C",
"Drug B", "Drug A"))
TestData$key_line <- with(TestData,paste(profile_key, line, sep = ""))

make_title <- function(d){
  with(d, paste("Pattern = ", unique(pattern),
                " \n (profile_key = ", unique(profile_key),
                ", line = ", unique(line), ") \n", sep = ""))

make_plot <- function(d){
  ggplot(d, aes(value, drug, fill = factor(instance))) +
    geom_line(size = 6) + xlab("\n Time (Weeks)") + ylab("") + theme_bw()  +
      opts(title=make_title(d), legend.position="none",
           plot.margin=unit(c(1, 0, 0.5, 0.5), "lines"))

plots <- dlply(TestData, "key_line", make_plot)

annotationGrob <- function(names=c(paste("Regimen", 1:5), "Rationale",
                           width=unit(1,"npc"), ...){

  n <- length(names)
  textwidth <- max(unit(rep(1, n), "strwidth", data=as.list(names)))
  gap <- unit(0.5, "line") # gap between text and lines
  f <- 1.5 # spacing factor
  positions <- f * (seq_along(names)) * unit(1, "line")
  tg <- textGrob(rev(names), x=0, hjust=0, y=positions, vjust=0.2,

  lg <- segmentsGrob(x0 = textwidth + gap, y0 = positions,
                     x1 = unit(1, "npc"), y1 = positions,
                     gp=gpar(lwd=0.5, lty=3))

  gTree(children=gList(tg,lg), height = grobHeight(tg) + unit(f, "line") + gap,


## grid.newpage()
## grid.draw(annotationGrob())


make_annotation <- function(p, ann=annotationGrob(), first=FALSE,
                            title="Regimen Patterns for Study X", ...){

  title <- textGrob(title, gp=gpar(fontsize=14, fontface="bold"), ...)

  arrangeGrob(p, ann, ncol=1, main=if(first) title,
              heights = unit.c(unit(1,"npc") - ann$height, ann$height))


plots_annotated <- mapply(make_annotation, p=plots,
                          first=c(TRUE, rep(FALSE, length(plots)-1)),

pdf("multiple_pages.pdf", height=12, width=10)
## we need to call `print.arrange` directly to specify the viewport
l_ply(plots_annotated, `print.arrange`,
vp=viewport(width=unit(1,"npc") - unit(1,"inch"), height=unit(1,"npc")
- unit(1,"inch")), newpage=TRUE)

More information about the R-help mailing list