[This article was first published on R – rud.is, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Subtitles aren’t always necessary for plots, but I began to use them enough that I whipped up a function for `ggplot2` that does a decent job adding a subtitle to a finished plot object. More than a few folks have tried their hand at this in the past and this is just my incremental contribution until there’s proper support in `ggplot2` (someone’s bound to add it via PR at some point).

We’ll nigh fully recreate the following plot from this NYTimes article:

Here’s a stab at that w/o the subtitle:

```library(ggplot2) library(scales)   data.frame( yrs=c("1789-90", "1849-50", "1909-10", "1965-66", "2016-16"), pct=c(0.526, 0.795, 0.713, 0.575, 0.365), xtralabs=c("", "Highest:n", "", "", "Lowest:n") ) -> hill_lawyers   gg <- ggplot(hill_lawyers, aes(yrs, pct)) gg <- gg + geom_bar(stat="identity", width=0.65) gg <- gg + geom_label(aes(label=sprintf("%s%s", xtralabs, percent(pct))), vjust=-0.4, family=c(rep("FranklinGothic-Book", 4),"FranklinGothic-Heavy"), lineheight=0.9, size=4, label.size=0) gg <- gg + scale_x_discrete() gg <- gg + scale_y_continuous(expand=c(0,0), limits=c(0.0, 1.0), labels=percent) gg <- gg + labs(x=NULL, y=NULL, title="Fewer and fewer lawyers on the Hill") gg <- gg + theme_minimal(base_family="FranklinGothic-Book") gg <- gg + theme(axis.line=element_line(color="#2b2b2b", size=0.5)) gg <- gg + theme(axis.line.y=element_blank()) gg <- gg + theme(axis.text.x=element_text(family=c(rep("FranklinGothic-Book", 4), "FranklinGothic-Heavy"))) gg <- gg + theme(panel.grid.major.x=element_blank()) gg <- gg + theme(panel.grid.major.y=element_line(color="#b2b2b2", size=0.1)) gg <- gg + theme(panel.grid.minor.y=element_blank()) gg <- gg + theme(plot.title=element_text(hjust=0, family="FranklinGothic-Heavy", margin=margin(b=10))) gg```

(There are some “tricks” in that plotting code that may be worth spending an extra minute or two to mull over if you didn’t realize some of the function parameters were vectorized, or that you could get a white background with no border for text labels so grid lines don’t get in the way.)

Ideally, a subtitle would be part of the `gtable` that gets made underneath the covers so it will “travel well” with the plot object itself. The function below makes a `textGrob` from whatever text we pass into it and does just that; it inserts the new grob into a new table row.

```#' Add a subtitle to a ggplot object and draw plot on current graphics device. #' #' @param gg ggplot2 object #' @param label subtitle label #' @param fontfamily font family to use. The function doesn't pull any font #' information from code{gg} so you should consider specifying fonts #' for the plot itself and here. Or send me code to make this smarter :-) #' @param fontsize font size #' @param hjust,vjust horizontal/vertical justification #' @param bottom_margin space between bottom of subtitle and plot (code{pts}) #' @param newpage draw new (empty) page first? #' @param vp viewport to draw plot in #' @param ... parameters passed to code{gpar} in call to code{textGrob} #' @return Invisibly returns the result of code{link{ggplot_build}}, which #' is a list with components that contain the plot itself, the data, #' information about the scales, panels etc. ggplot_with_subtitle <- function(gg, label="", fontfamily=NULL, fontsize=10, hjust=0, vjust=0, bottom_margin=5.5, newpage=is.null(vp), vp=NULL, ...) {   if (is.null(fontfamily)) { gpr <- gpar(fontsize=10, ...) } else { gpr <- gpar(fontfamily=fontfamily, fontsize=10, ...) }   subtitle <- textGrob(label, x=unit(hjust, "npc"), y=unit(hjust, "npc"), hjust=hjust, vjust=vjust, gp=gpr)   data <- ggplot_build(gg)   gt <- ggplot_gtable(data) gt <- gtable_add_rows(gt, grobHeight(subtitle), 2) gt <- gtable_add_grob(gt, subtitle, 3, 4, 3, 4, 8, "off", "subtitle") gt <- gtable_add_rows(gt, grid::unit(bottom_margin, "pt"), 3)   if (newpage) grid.newpage()   if (is.null(vp)) { grid.draw(gt) } else { if (is.character(vp)) seekViewport(vp) else pushViewport(vp) grid.draw(gt) upViewport() }   invisible(data)   }```

The roxygen comments should give you an idea of how to work with it, and here it is in action:

```subtitle <- "The percentage of Congressional members that are laywers has beenncontinuously dropping since the 1960s"   ggplot_with_subtitle(gg, subtitle, fontfamily="FranklinGothic-Book", bottom_margin=20, lineheight=0.9)```

It deals with long annotations pretty well, too (I `strwrap`ped the source text for the below at `100` characters). The text is senseless here, but it’s just for show (I had it handy…don’t judge…you’re getting free code :-):

I think this beats manually re-creating the wheel, even if you only infrequently use subtitles. It definitely beats hand-editing plots and is a bit more elegant and functional than using `grid.arrange` (et al) to mimic the functionality. It also beats futzing with panel margins and clipping to shoehorn a frankenmashup mess of `geom_text` or `annotation_custom` calls.

Kick the tyres, tell me where it breaks and if I can cover enough edge cases (or make it smarter) I’ll add it to my `ggalt` package.