Search code examples
rplotlyr-markdownknitrhtmlwidgets

Sizing Plotly Plots Individually in a Loop in RMarkdown (without knitr chunk options)


I'm working on an RMarkdown document where I include multiple Plotly plots, and I need to control their sizes. When rendering a single plot, I can easily use chunk options like out.width and out.height:

```{r, include=TRUE, echo=FALSE, out.width='100%', out.height='200px'}
    p = plot_ly(data.frame(x = 1:10, y = rnorm(10)), x = ~x, y = ~y, type = 'scatter', mode = 'markers')
    p
```

However, I'm facing issues when trying to render multiple plots inside a loop. Here's a simplified example:

```{r, include=TRUE, echo=FALSE, results = 'asis'}
  for (i in 1:3) {
    cat("\n# Headline 1 \n\n")
    p = plot_ly(data.frame(x = 1:10, y = rnorm(10)), x = ~x, y = ~y, type = 'scatter', mode = 'markers')
    print(htmltools::tagList(p))
    cat("\n# Headline 2 \n\n")
    p = plot_ly(data.frame(x = c('A', 'B', 'C'), y = runif(3)), x = ~x, y = ~y, type = 'bar')
    print(htmltools::tagList(p))
  }
```

In this loop, I'd like each plot to have a different size (e.g., one plot should be 100% width and 200px height, while the next should be 50% width and 100px height).

Since all plots are rendered within the same chunk, setting chunk options won't work. Is there a way to specify individual plot sizes within a loop in RMarkdown?

Any guidance would be greatly appreciated!


Solution

  • While I used a for loop myself in the past I now think that an easier approach to achieve the desired result is to use a child document and knitr::knit_child, which in particular for your use case, allows to render each plotly chart in its own chunk and hence allows to set the chunk options indivdually, e.g. in the code below I use switch to conditionally set the out.width. Also note that for the sake of the reprex I dropped the second plotly chart.

    ---
    output: html_document
    date: "2024-08-14"
    ---
    
    ```{r setup, include=FALSE}
    knitr::opts_chunk$set(echo = FALSE, message=FALSE)
    ```
    
    ```{r, include=TRUE, echo=FALSE, results = 'asis'}
    library(plotly)
    
    res <- lapply(1:3, function(x) {
      knitr::knit_child(
        'plotly-child.Rmd', envir = environment(), quiet = TRUE
      )
    })
    cat(unlist(res), sep = '\n')
    ```
    

    plotly-child.Rmd

    # Headline 1
    
    ```{r}
    out_width <- switch(x,
      "1" = "50%",
      "2" = "200px",
      "3" = "100%"
    )
    ```
    
    ```{r, out.width=out_width}
    plot_ly(
      data.frame(x = 1:10, y = rnorm(10)),
      x = ~x, y = ~y, type = "scatter", mode = "markers"
    )
    ```
    

    enter image description here

    UPDATE Personally I prefer the separate child document but you can add it to the main Rmd by combining knit_child with knit_expand:

    One small difference though: To refer to variables from outside of the "child document" (aka the environment of the main Rmd) you have to wrap them in double braces {{.

    ---
    output: html_document
    date: "2024-08-14"
    ---
    
    ```{r setup, include=FALSE}
    knitr::opts_chunk$set(echo = FALSE, message=FALSE)
    ```
    
    ```{r}
    plotly_child <- paste(
       '# Headline 1\n',
       '```{r}',
       'out_width <- switch( {{i}} , "1" = "50%", "2" = "200px", "3" = "100%")',
       '```\n',
       '```{r, out.width=out_width}',
       'plot_ly(',
       'data.frame(x = 1:10, y = rnorm(10)),',
       'x = ~x, y = ~y, type = "scatter", mode = "markers"',
       ')',
       '```',
       sep = "\n"
    )
    ```
    
    
    ```{r, echo=FALSE, results='asis'}
    library(plotly)
    
    src = res <- lapply(1:3, function(i) {
      knitr::knit_expand(text = plotly_child)
    })
    res = knitr::knit_child(text = unlist(src), quiet = TRUE)
    cat(res, sep = '\n')
    ```