How to Change fig.width and fig.height Dynamically Within an R Markdown Chunk

All plots generated within an R Markdown document chunk take the width and height defined in that chunk’s options, meaning that all plots within a chunk will be the same size. There is a hacky way to get around this restriction, though.

The idea is to write code within a chunk that, when run, generates a new chunk with the desired width and height. We can construct any number of these “sub-chunks” within a regular chunk, and give each a different fig.width and fig.height, and even define them on-the-fly. When the whole notebook is knitted, the resulting output will look just as if each sub-chunk were a regular chunk with hard-coded fig.width and fig.height options.


Here’s the key function:

subchunkify <- function(g, fig_height=7, fig_width=5) {
  g_deparsed <- paste0(deparse(
    function() {g}
  ), collapse = '')
  sub_chunk <- paste0("
  `","``{r sub_chunk_", floor(runif(1) * 10000), ", fig.height=", fig_height, ", fig.width=", fig_width, ", echo=FALSE}",
    , ")()",
  cat(knitr::knit(text = knitr::knit_expand(text = sub_chunk), quiet = TRUE))

It’s based on this Stack Overflow answer by Yihui, the main author of the Knitr package that compiles R Markdown documents.

Let me break down the less intuitive parts:

  1. The g argument is whatever you want to plot – for instance, the object returned from a ggplot() call.
  2. The deparse() function turns function() {g} into a string that Knitr can interpret.
  3. The purpose of floor(runif(1) * 10000) is just to give the generated sub-chunk a unique name. (OK, OK, it’s not guaranteed to be unique, but it usually works, at least!)
  4. The knitr::knit() function compiles the generated sub-chunk.

So, overall, the function takes a plot object, a height, and a width, and turns it into a string like so:

```{r sub_chunk_9295, fig.height=5, fig.width=7, echo=FALSE}
[the deparsed function call will go here]

Then, the knitr::knit() call turns this into an actual image just like it would any other chunk, and that image is rolled up into the final document when the entire thing is knitted.


Here’s the subchunkify() function in use:

```{r echo=FALSE, results='asis'}
g <- ggplot(economics, aes(date, unemploy)) + 
subchunkify(g, 10, 3)
subchunkify(g, 7, 7)

Simple Subchunkify example

One chunk, two figure sizes! And we can change the figure sizes dynamically within the code, if we please:

```{r echo=FALSE, results='asis'}
g <- ggplot(economics, aes(date, unemploy)) + 
for (i in seq(2, 5)) {
  subchunkify(g, i / 2, i)

Dynamic R Markdown figure sizes

It’s vital to use the results='asis' chunk options or the generated images won’t get knitted into the final document. (I’m also using echo=FALSE here, but that’s just to avoid printing the underlying code to the document; it’s not specific to using subchunkify()).

Since we’re using results='asis', if we want to output text or headings or anything else from the chunk, we must use raw HTML, not Markdown, and we must use cat() to do this, not print(). For instance:

g <- ggplot(economics, aes(date, unemploy)) + 

cat('<h2>A Small Square Plot</h2>')
subchunkify(g, 3, 3)

A small square plot with HTML header

What Next?

There’s more that could be done with this idea. For example, we could make a set of subchunkify functions, like subchunkify.markdown(md = text_in_markdown_format) to knit Markdown into HTML so that we can write in Markdown in the latter example. Or, when using R Notebooks, we could use parameters to treat the sub-chunks differently depending on whether they’re being run in-line or knitted as part of the whole document, so that something gets embedded when a chunk is run in-line.

Its current state has proven to be good enough for my personal use cases, but if you do something more with it, please let me know – I’d love to see it!

Michael James Williams avatar
About Michael James Williams
Michael James Williams is a data analyst, writer, and editor.