1

My intention is to explore some data visually using a shiny app and then to pass the resulting plot to a Markdown document. My question is similar to these two I found on stackoverflow.

How to pass reactive data as a R markdown parameter?

How to pass table and plot in Shiny app as parameters to R Markdown?

Unfortunately I am not able to figure out how to use the answers provided to solve my problem. I assume I do not understand the functionality of reactive values enough.

In the MWE I use an input slider in order to create some random numbers that should be displayed in the shiny app. Once the plot has been created in the shiny app I need it to be embedded into the Markdown document. Passing the plot as a parameter to the Markdown does not create an error, however the parameter can not be accessed (seems not to exist) in the Markdown document. But if I cancel out the code in the shiny app to display the plot in the app directly, the plot can be passed to the Markdown document as a parameter and be displayed there.

I understand that I could recreate the plot in the Markdown document, as explained in How to pass a plot (renderPlot) from shiny app as parameter to R Markdown?. Still I would like to understand if there is a solution to pass the plot without recreating it.

In order to do so I must understand why displaying the plot in the shiny app prevents it to be passed to the Markdown document. What is it that I do not understand? Please help! Thank you!

MWE: server.R

library(shiny)

shinyServer(function(input, output) {

    # create a scatter plot of random numbers
    # based on value of input slicer
    chart <- reactive({
        plot(x = 1:input$dots, y =  rnorm(n = input$dots),
             xlab = "Numbers", ylab = "Random")
    })
    

    # Display the scatter plot in shiny app
    # in reaction to actionButton display
    observeEvent(eventExpr = input$display,
                 output$dotPlot <- renderPlot({
                 chart()
                  })
    )
    
    # Create R-Markdown Report with plot and
    # value of input slider as paramters
    output$report <- downloadHandler(
        filename = "Dot_Report.html",
        
        content = function(file) {
        tempReport <- file.path(tempdir(), "Dot_Report.Rmd")
        file.copy(from = "Dot_Report.Rmd", 
                  to = tempReport, 
                  overwrite = TRUE)
        
        pars <- list(n = input$dots,
                     random_plot = reactive(chart()))

        rmarkdown::render(input = tempReport, 
                          output_file = file,
                          params = pars,
                          envir = new.env(parent = globalenv()))
    })

})

MWE: ui.R

library(shiny)

# Define UI for application that displays a Scatter Plot
shinyUI(fluidPage(

    # Title
    titlePanel("Simple Plot Export"),

    # Sidebar with a slider input for number of random numbers
    sidebarLayout(
        sidebarPanel(
            
            sliderInput("dots",
                        "Number of Random dots:",
                        min = 1,
                        max = 50,
                        value = 30)
        ,
        actionButton("display", "Display Plot"),
        
        downloadButton("report", "Generate Report")
    ),
        mainPanel(
            plotOutput("dotPlot")
        )
    )
))

MWE: Dot_Report.Rmd

---
title: "Simple Scatter Plot"
output: html_document
params: 
  n: NA
  random_plot: NA
---


```{r, echo = F}

params$random_plot()

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
n. small
  • 45
  • 6

1 Answers1

1

The problem here is, that base plots directly draw on a device.

Please see this related answer for workarounds.

Your code is working fine once we switch to e.g. ggplot (only server.R needs to be modified):

library(shiny)
library(ggplot2)

shinyServer(function(input, output) {
  
  # create a scatter plot of random numbers
  # based on value of input slicer
  chart <- reactive({
    DF <- data.frame(x = 1:input$dots, y = rnorm(n = input$dots))
    ggplot(data = DF, aes(x = x, y = y)) + geom_point() + xlab("Numbers") + ylab("Random")
  })
  
  
  # Display the scatter plot in shiny app
  # in reaction to actionButton display
  observeEvent(eventExpr = input$display,
               output$dotPlot <- renderPlot({
                 chart()
               })
  )
  
  # Create R-Markdown Report with plot and
  # value of input slider as paramters
  output$report <- downloadHandler(
    filename = "Dot_Report.html",
    
    content = function(file) {
      tempReport <- file.path(tempdir(), "Dot_Report.Rmd")
      file.copy(from = "Dot_Report.Rmd", 
                to = tempReport, 
                overwrite = TRUE)
      
      pars <- list(n = input$dots,
                   random_plot = reactive(chart()))
      
      rmarkdown::render(input = tempReport, 
                        output_file = file,
                        params = pars,
                        envir = new.env(parent = globalenv()))
    })
  
})

Edit: Using base plot() along with recordPlot()

server.R:

 # create a scatter plot of random numbers
  # based on value of input slicer
  chart <- reactive({
    plot(x = 1:input$dots, y =  rnorm(n = input$dots),
    xlab = "Numbers", ylab = "Random")
    recordPlot()
  })

Some related information - see ?dev.new and ?dev.copy:

Only one device is the ‘active’ device: this is the device in which all graphics operations occur. There is a "null device" which is always open but is really a placeholder [...]

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • 1
    Dear ismirsehregal, thank you for your answer! I thought that in all my different approaches I was able to exclude that this was the source of the problem. I also tried the recordPlot() function (in a slightly different setting though). For certain reasons I wanted to avoid ggplot. I will look at your suggestion and come back later. Thanks a lot in the meantime! – n. small Jan 20 '22 at 09:34
  • 1
    @n.small Please check my edit using `recordPlot()` and `replayPlot()`. – ismirsehregal Jan 20 '22 at 09:56
  • 1
    As @ismirsehregal pointed out, the source of the problem was in generating the plot not in the shiny app itself. In order to use a base plot, it is necessary to record the plot, as @ismirsehregal has referred to . In my MWE it is enough to change on the server side, by adding to the reactive chart: `p <- recordPlot(); p` Thanks again to @ismirsehregal ! – n. small Jan 20 '22 at 10:45
  • 1
    @n.small you are right - `replayPlot` isn't needed just as assigning `recordPlot`'s output to a variable (e.g. `p`) isn't required. I updated the answer accordingly. Thanks for the well prepared and reproducible question! – ismirsehregal Jan 20 '22 at 10:57
  • @ismiregal please help me to understand the issue better: In my previous example, without recording the plot, I was able to display the plot in the Markdown document when I deleted the commands to display it in the shiny app. Does ist mean, that the plot showed up in the Markdown file because it was the first device? So the chart() was not called in the shiny app, but passed as a parameter to the Markdown file where I called it for the first time. And hence the graphic showed up. Maybe my question is pointless, but I try to understand reactivity better. Thank you for any comment! Best! – n. small Jan 20 '22 at 10:58
  • 1
    @n.small Your understanding is wholly accurate. `plot.default` simply uses the currently active graphic device. Also see this [related post](https://stackoverflow.com/a/7942670/9841389). Maybe `dev.copy` would be another possible workaround for the issue. – ismirsehregal Jan 20 '22 at 11:10
  • thanks again for confirming @ismirsehregal! I appreciate a lot! I will take a look at the `dev.copy` command. Thanks! – n. small Jan 20 '22 at 11:16