1

I want to generate a R Markdown report using plots from various tabs in my Shiny app.
The plots in each tab get generated only once the tab is activated by the user. If a tab was not activated before, the plot will not exist and thus will not be available for the report.
Therefore I tried to successively open all tabs using updateTabsetPanel before calling downloadHandler. However as opposed to manually clicking the tabs, the empty tabs open and the file download dialog appears immediately before the plots start to render. Only after saving the report file, Shiny will start to render the plots and the report is empty.
Is there a way to force the downloadHandler to wait until all plots in all tabs finished rendering?

Here's an example:

app.R

library(shiny)
library(ggplot2)

ui <- fluidPage(
  mainPanel(
    tabsetPanel(id = "tabs",
                tabPanel("Plot1",
                         downloadButton("report", "create report"),
                         plotOutput("plot1")
                ),
                tabPanel("Plot2", plotOutput("plot2")),
                tabPanel("Plot3", plotOutput("plot3"))     
    )
  )
)
server <- function(input, output, session) {
  plots <<- list()
  
  output$plot1 <- renderPlot({
    plots[[1]] <<- ggplot(mtcars, aes(wt, mpg)) + geom_point()
    plot(plots[[1]])
  })  
  
  output$plot2 <- renderPlot({
    plots[[2]] <<- ggplot(mtcars, aes(wt, cyl)) + geom_point()
    plot(plots[[2]])
  })  
  
  output$plot3 <- renderPlot({
    plots[[3]] <<- ggplot(mtcars, aes(wt, disp)) + geom_point()
    plot(plots[[3]])
  })  
  
  
  output$report <- downloadHandler(
    filename = ("report.html"),
    content = function(file) {
      updateTabsetPanel(session, "tabs", selected = "Plot1")
      updateTabsetPanel(session, "tabs", selected = "Plot2")
      updateTabsetPanel(session, "tabs", selected = "Plot3")
      tempReport <- file.path(tempdir(), "report.rmd")
      file.copy("report.rmd", tempReport, overwrite = TRUE)
      params <- list(plots = plots)
      rmarkdown::render(tempReport, output_file = file, params = params, envir = new.env(parent = globalenv()))
    }
  )  
}

shinyApp(ui, server)

report.rmd

---
title: "report"
output: html_document
params:
  plots: NA
---

```{r}
  for (i in params$plots)
    plot(i)
```

In this case, I put the tabPanel calls in the downloadHandler for simplicity. They are supposed to open all tabs so the plots get rendered and after that, rmarkdown can plot them.
If you click the "report" button, the 3rd tab will open and the file dialog appears before the plots from tabs 2 and 3 are rendered, so the report only contains plot 1. But if you manually click all 3 tabs and then click "report", all plots will be rendered and included in the report.

Houndmux
  • 13
  • 3
  • Welcome to SO, Houndmux! Most questions on SO (and the [tag:r] tag specifically) benefit from or really need to be *reproducible*, to include sample data, minimum code, and expected output. Please see https://stackoverflow.com/q/5963269, [mcve], and https://stackoverflow.com/tags/r/info for good discussions on how to frame questions well in this context. Thanks! – r2evans Oct 04 '21 at 20:44
  • One way forward might be to use the `shinyjs` package to disable the download button, enabling it only under conditions you require. – r2evans Oct 04 '21 at 20:45
  • This would still require the user to manually open all tabs one by one in order to render all the plots first. I would prefer if the tabs were opened automatically. But using updateTabsetPanel on all tabs successively does not even actually render all tabs. It only renders the last one, and that even after downloadHandler, which is called after all updateTabsetPanel calls. So the order of execution is not given. – Houndmux Oct 04 '21 at 20:56
  • Okay, I understand this is not just about disabling `downloadHandler` until all plots are complete. Please make a small, reproducible example that clearly demonstrates this. Please don't include widgets, controls, or code that have nothing to do with the basic flow; we don't need fancy plots, simply `plot(1);plot(2);plot(3);` might suffice. Good luck! – r2evans Oct 04 '21 at 20:59
  • Added an example. The reason why I save the ggplots in the "plots"-list instead of re-defining the plots in the rmd file is that in my actual application, the plots require a lot of different data which I do not want to shift into the rmd file. Also these are interactive ggplotly-objects where the user can select data inside the plots and I want these selections to be included in the report. – Houndmux Oct 04 '21 at 22:10

1 Answers1

0

Try:

  output$plot1 <- renderPlot({
    plots[[1]] <<- ggplot(mtcars, aes(wt, mpg)) + geom_point()
    plot(plots[[1]])
  })  
  outputOptions(output, "plot1", suspendWhenHidden = FALSE)

  output$plot2 <- renderPlot({
    plots[[2]] <<- ggplot(mtcars, aes(wt, cyl)) + geom_point()
    plot(plots[[2]])
  })  
  outputOptions(output, "plot2", suspendWhenHidden = FALSE)
  
  output$plot3 <- renderPlot({
    plots[[3]] <<- ggplot(mtcars, aes(wt, disp)) + geom_point()
    plot(plots[[3]])
  })  
  outputOptions(output, "plot3", suspendWhenHidden = FALSE)

EDIT

The above does not work. The following works.

library(shiny)
library(ggplot2)

ui <- fluidPage(
  mainPanel(
    tabsetPanel(id = "tabs",
                tabPanel("Plot1",
                         downloadButton("report", "create report"),
                         plotOutput("plot1")
                ),
                tabPanel("Plot2", plotOutput("plot2")),
                tabPanel("Plot3", plotOutput("plot3"))     
    )
  )
)
server <- function(input, output, session) {
  plots <- vector("list", length = 3L)
  
  plots[[1L]] <- reactive({
    ggplot(mtcars, aes(wt, mpg)) + geom_point()
  })
  plots[[2L]] <- reactive({
    ggplot(mtcars, aes(wt, cyl)) + geom_point()
  })
  plots[[3L]] <- reactive({
    ggplot(mtcars, aes(wt, disp)) + geom_point()
  })
  
  
  output$plot1 <- renderPlot({
    plots[[1L]]()
  })  
  
  output$plot2 <- renderPlot({
    plots[[2L]]()
  })  
  
  output$plot3 <- renderPlot({
    plots[[3L]]() 
  })  
  
  
  output$report <- downloadHandler(
    filename = "report.html",
    content = function(file) {
      tempReport <- file.path(tempdir(), "report.rmd")
      file.copy("report.rmd", tempReport, overwrite = TRUE)
      params <- list(plots = plots)
      rmarkdown::render(
        tempReport, output_file = file, params = params, 
        envir = new.env(parent = globalenv())
      )
    }
  )  
}

shinyApp(ui, server)

report.Rmd:

---
title: "report"
output: html_document
params:
  plots: NA
---

```{r}
for(plot in params$plots)
  print(plot())
```
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • Unfortunately this does not work either. The file download dialog still opens immediately before any of the plots gets processed and the plots do not get processed in the background while the dialog is open. – Houndmux Oct 05 '21 at 07:08
  • @Houndmux Ok. Do you want something which work with 3 plots, or do you intend to do an app with an arbitrary number of plots? – Stéphane Laurent Oct 05 '21 at 07:15
  • I need an arbitrary number of plots, so in my app, they get created on the fly using a tagList. What I need is a way to have all the plots from all tabs available in the R markdown report, even if the user did not open the tabs before. If there is a better way than trying to force Shiny to open all tabs before the download dialog appears, I would try that as well. – Houndmux Oct 05 '21 at 07:27
  • @Houndmux Please see my edit. – Stéphane Laurent Oct 05 '21 at 07:34
  • Thank you very much, that worked! Is there a reason why you explicitly designate the array index as an integer? It works without it too. – Houndmux Oct 05 '21 at 07:46
  • @Houndmux No important reason. I just prefer. – Stéphane Laurent Oct 05 '21 at 08:04