Using downloadHandler and R Studio 'Export as pdf' (cairo_pdf)

shiny
graphics
pdf
plot

#1

How would I use the same functions carried out by:

39

to export a plot produced in an shiny app via downloadHandler?

I have tried using variants of Cairo pdf output functions, including cairo_pdf in a shiny app but have not been able to replicate the desired output as produced by the above. All I have been able to produce are blank plots, error messages, and low quality plots with the text and point resizing and aliasing all wrong.

Any help with this would be much appreciated!


#2

Hi @marsnone

Could you please reply with:

  • the code that you are using to save the plot
  • the errors that are occurring

Thank you,
Barret


#3

Hi @barret, thank you for the reply.

Here are the relevant components of the latest version of the code, with the most informative error message I have yet received:

library(shiny)
library(Cairo)
library(grDevices)
library(ggbiplot)
library(vegan)

ui <- fluidPage(

mainPanel(
      tabsetPanel(
        tabPanel("NMS Ordination", plotOutput("ordplot"),
                 downloadLink("downloadPlot", "Download Plot")
   )
)



server <- function(input, output, session){

options(shiny.usecairo=T)

plotInput <- function(){
    file <- tempfile(fileext = '.pdf')
    pdf(file)
    cairo_pdf(filename = "file",
              width = 18, height = 10, pointsize = 12, family = "sans", bg = "transparent",
              antialias = "subpixel",fallback_resolution = 300)
    vegan::ordiplot(nms_graph) #Note that this is not the exact code (just for simplicity here)
    dev.off()
    dev.copy2pdf(file="file")
    list(src = "file")}

 output$ordplot <- renderPlot({
vegan::ordiplot(nms_graph) #Note again that this is not the exact code (just for simplicity here) - the plot is  successfully rendered in the UI, but the quality is too low to useable, thus requiring a high quality download
})
    
    output$downloadPlot <- downloadHandler(
      filename = function(){paste(input$dataset, '.csv', sep = '')},
      content = function(file){plotInput()},
      contentType = "application/pdf",
      outputOptions(output, "downloadPlot", suspendWhenHidden=FALSE)
    )
}

shinyApp(ui=ui, server=server)

The error returned is as follows:

Listening on http://127.0.0.1:3133
Warning: Error in .subset2(x, "impl")$outputOptions: downloadPlot is not in list of output objects
  55: stop
  54: .subset2(x, "impl")$outputOptions
  53: outputOptions
  47: server [/Users/xxx/yyy/zzz/app.R#214]
Error in .subset2(x, "impl")$outputOptions(name, ...) : 
  downloadPlot is not in list of output objects

GitHub Repository for the application:
(https://github.com/marsnone/VEGDESK)


#4
  1. I'd change your downloadHandler's filename argument to end in .pdf, not .csv
  2. In plotInput, add a file parameter to the function signature, and get rid of the line `file <- tempfile(fileext = '.pdf')
  3. In the rest of plotInput, everywhere you have "file", it should be file (no double quotes). Also, you can remove the last line, the one that returns the list.
  4. In downloadHandler's content function, call plotInput(file).

#5

Thanks very much @jcheng for fixing up those mistakes :pray:

Unfortunately, there seems to be issues beyond that. Here is the updated code as it stands and the error it returns:

library(shiny)
library(Cairo)
library(grDevices)
library(ggbiplot)
library(vegan)

ui <- fluidPage(

mainPanel(
      tabsetPanel(
        tabPanel("NMS Ordination", plotOutput("ordplot"),
                 downloadLink("downloadPlot", "Download Plot")
   )
)



server <- function(input, output, session){

options(shiny.usecairo=T)

plotInput <- function(file){
    cairo_pdf(filename = file,
              width = 18, height = 10, pointsize = 12, family = "sans", bg = "transparent",
              antialias = "subpixel",fallback_resolution = 300)
    vegan::ordiplot(nms_graph) #Note that this is not the exact code (just for simplicity here)
    dev.off()
    dev.copy2pdf(file)
    }
    
    output$downloadPlot <- downloadHandler(
      filename = function(){paste(input$dataset, '.pdf', sep = '')},
      
      content = plotInput(file),
      
      contentType = "application/pdf"
    )
}

shinyApp(ui=ui, server=server)

The error returned is as follows:

Listening on http://127.0.0.1:5809
Warning: Error in as.character: cannot coerce type 'closure' to vector of type 'character'
  80: self$downloads$set
  79: shinysession$registerDownload
  78: origRenderFunc
  77: output$downloadPlot
   1: runApp

On running the app, the error message is displayed in place of the download link (which should read: "Download Plot") for the desired plot as shown in this screenshot:

GitHub Repository for the application:
(https://github.com/marsnone/VEGDESK)

...

Any ideas?


#6

I'm not sure why this is the case, but it works for me if the content function is included inline, instead of defined separately:

library(shiny)
library(Cairo)
library(grDevices)

ui <- fluidPage(
  
  mainPanel(
    tabsetPanel(
      tabPanel(
        "NMS Ordination", 
        textInput("dataset", "Data Set", value = "my_awesome_data"),
        plotOutput("ordplot"),
        downloadLink("downloadPlot", "Download Plot")
      )
    )
  )
)


server <- function(input, output, session){
  
  options(shiny.usecairo=T)
  
  output$ordplot <- renderPlot({
    plot(faithful)
  })
  
  output$downloadPlot <- downloadHandler(
    filename = function(){paste(input$dataset, '.pdf', sep = '')},

    content = function(file){
      cairo_pdf(filename = file,
                width = 18, height = 10, pointsize = 12, family = "sans", bg = "transparent",
                antialias = "subpixel",fallback_resolution = 300)
      plot(faithful)
      dev.off()
    },
    
    contentType = "application/pdf"
  )
}

shinyApp(ui=ui, server=server)

(I removed dev.copy2pdf(file) since I wasn't sure what it was supposed to do, given that cairo_pdf() is already creating a PDF file)


#7

Got it working - now producing downloadable cairo_pdf() files

Thanks @all!