rshiny

How to render reactive png image for display and download in R shiny


I'm trying to create a shiny app that both displays a png that is created by the code (i.e. reactive) and which can then be downloaded. I found a nice example on how to create and update the png (link), but have not found out how to then grab this file for download. The download button is not working as intended.

The code below shows a minimal example:

library(shiny)

ui <- pageWithSidebar(
  headerPanel("renderImage example"),
  sidebarPanel(
    sliderInput("obs", "Number of observations:",
                min = 0, max = 1000,  value = 500)
  ),
  mainPanel(
    # Use imageOutput to place the image on the page
    imageOutput("myImage"),
    # Download button
    downloadButton("download", "Download Image")
  )
)

server <- function(input, output, session) {
  
  output$myImage <- renderImage({
    # A temp file to save the output.
    # This file will be removed later by renderImage
    outfile <- tempfile(fileext = '.png')

    # Generate the PNG
    png(outfile, width = 400, height = 300)
    hist(rnorm(input$obs), main = "Generated in renderImage()")
    dev.off()

    # Return a list containing the filename
    list(src = outfile,
         contentType = 'image/png',
         width = 400,
         height = 300,
         alt = "This is alternate text")
  }, deleteFile = FALSE)
  
  # Enable image download
  output$download <- downloadHandler(
    filename = function() {
      "generated_plot.png"
    },
    content = function(file){
      # Save the plot/image to a file
      img <- system.file(output$myImage$outfile, package="png")
      file.copy(img, file)
    }
  )
}

shinyApp(ui, server)

Solution

  • When you want to display your image and want to download it, then IMHO the easiest approach would be to create your image inside a reactive which could then be used or called both in renderImage() and in downloadHandler. Also note that system.file(output$myImage$outfile, package="png") does not make sense. Instead when you store the list containing the image information as a reactive image as I do below you could use image()$src to get the file path.

    library(shiny)
    
    ui <- pageWithSidebar(
      headerPanel("renderImage example"),
      sidebarPanel(
        sliderInput("obs", "Number of observations:",
          min = 0, max = 1000, value = 500
        )
      ),
      mainPanel(
        imageOutput("myImage"),
        downloadButton("download", "Download Image")
      )
    )
    
    server <- function(input, output, session) {
      image <- reactive({
        outfile <- tempfile(fileext = ".png")
    
        png(outfile, width = 400, height = 300)
        hist(rnorm(input$obs), main = "Generated in renderImage()")
        dev.off()
    
        list(
          src = outfile,
          contentType = "image/png",
          width = 400,
          height = 300,
          alt = "This is alternate text"
        )
      })
      
      output$myImage <- renderImage(
        {
          image()
        },
        deleteFile = FALSE
      )
    
      output$download <- downloadHandler(
        filename = function() {
          "generated_plot.png"
        },
        content = function(file) {
          img <- image()$src
          file.copy(img, file)
        }
      )
    }
    
    shinyApp(ui, server)
    

    enter image description here