How to generate plots with programmatically generated captions using knit_expand in rmarkdown

rmarkdown
knitr
knit_expand

#1

In an rmarkdown document, I'm trying to generate several plots in which the plot height is set dynamically, based on the number of categories on the y-axis of a given plot, and the caption is also dynamically generated. To do this, I'm using knit_expand to create a separate code chunk for each plot, each with the appropriate dynamically generated fig.height and fig.cap chunk options. Although the plot height setting works, and the document knits without error, no captions are generated.

Below is a complete rmarkdown document with a simplified example of what I'm trying to do. (This example is based on this answer to a question I asked on Stack Overflow a while back. The answer addresses the dynamic plot height, but doesn't produce a caption.)

In addition to learning how to resolve this particular problem, I'd also be interested in knowing if my approach here is the "right" way, or if there's a better (e.g., easier to code and maintain, easier to generalize, more idiomatic in terms of the rmarkdown/knitr-verse, etc.) way to generate multiple plots with dynamic setting of output options, like figure sizes, captions, etc.

---
output: pdf_document
---

```{r setup, include=FALSE}
knitr::opts_knit$set(progress = FALSE, verbose = FALSE)
knitr::opts_chunk$set(echo = FALSE, message=FALSE, warning=FALSE)

library(ggplot2)
theme_set(theme_bw())
library(knitr)
```

```{r data}
# Data frame for plotting
df = structure(list(dept = c("English", "English", "English", "English", 
    "English", "English", "English", "English", "English", "English", 
    "English", "English", "English", "English", "English", "Biology", 
    "Biology", "Biology", "Biology", "Biology", "Biology", "Government", 
    "Government"), tot_enrl = c(114, 349, 325, 393, 415, 401, 166, 
    117, 302, 267, 256, 224, 481, 295, 122, 410, 478, 116, 278, 279, 
    238, 142, 145), course = c("ENGL 1", "ENGL 10M", "ENGL 11M", 
    "ENGL 16", "ENGL 1X", "ENGL 20M", "ENGL 3", "ENGL 30A", "ENGL 40A", 
    "ENGL 40B", "ENGL 50A", "ENGL 50B", "ENGL 5M", "ENGL 60", "ENGL 65", 
    "BIO 15L", "BIO 2", "BIO 30", "BIO 39", "BIO 7", "BIO 9", "GOVT 10", 
    "GOVT 1H")), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, 
    -23L), .Names = c("dept", "tot_enrl", "course"))
```


```{r}
# Function to create a separate chunk to generate a plot for each department in df
kexpand <- function(plot, height, caption) {

  cat(
    knit(text=knit_expand(
      text=(
"```{r {{caption}}, fig.height={{height}}, fig.cap='Department: {{caption}}'}
plot
```"),
      plot = plot,
      caption = caption,
      height = height)))
}
```

```{r results="asis"}
height.unit <- 0.2

for (d in unique(df$dept)) {
  
  # Generat plot
  p = ggplot(df[df$dept==d, ], aes(tot_enrl, reorder(course,tot_enrl))) +
    geom_point() + 
    labs(y='Course', x='Total Enrollment')
  
  # Set plot height
  ht <- height.unit *  length(unique(df$course[df$dept==d])) + 0.5

  kexpand(plot=p, height=ht, caption=d)
}
```

```{r hardCodedCaption, fig.height=1, fig.cap="Show that captioning works when you hard-code it"}
ggplot(df[df$dept=="Biology", ], aes(tot_enrl, reorder(course,tot_enrl))) +
    geom_point() + 
    labs(y='Course', x='Total Enrollment')
```

The output document is pasted in below. The first plot has a hard-coded caption, just to show that this works. The next three plots are generated from chunks created by knit_expand. The dynamic height setting works, but no captions are included.

In addition to generating a dynamic caption, I'd also be interested in knowing how to add extra vertical space between each output graph. I tried adding \vspace{0.3cm} or \\vspace{0.3cm} in the knit_expand function like this:

kexpand <- function(plot, height, caption, dept) {

  cat(
    knit(text=knit_expand(
      text=(
"```{r {{caption}}, fig.height={{height}}, fig.cap='Department: {{caption}}'}
plot
```  \\vspace{0.3cm}"),
      plot = plot,
      caption = caption,
      height = height)))
}

but that results in an error during knitting.