Export R Shiny Page to PDF

Export R Shiny Page to PDF

First of all , you should really produce a reproducible example not just a sample of your code. We should copy and paste your code and it will run.

The idea

  1. Since you are using ggplot2 which is king of grid plots, I think one easy option to save plots/tables is to use gridExtra package. Using grid.arrange or arrangeGrobs you can save your grobs to predefined device. Then, downloadhandler will do the download.

  2. To not regenerate all the plots each time, I think one solution is to save them in a global variable that you update each time you change the plot. Here reactiveValues come in rescue to store plots and tables ad dynamic variable.

Solution

ui.R

library(shiny)

shinyUI(fluidPage(

# Application title
titlePanel("Save ggplot plot/table without regenration"),

# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
downloadButton('export')
),

# Show a plot of the generated distribution
mainPanel(
plotOutput("p1"),
plotOutput("p2"),
tableOutput("t1")
)
)
))

server.R

library(shiny)
library(ggplot2)
library(gridExtra)

shinyServer(function(input, output) {
## vals will contain all plot and table grobs
vals <- reactiveValues(p1=NULL,p2=NULL,t1=NULL)

## Note that we store the plot grob before returning it
output$p1 <- renderPlot({
vals$p1 <- qplot(speed, dist, data = cars)
vals$p1
})

output$p2 <- renderPlot({
vals$p2 <- qplot(mpg, wt, data = mtcars, colour = cyl)
vals$p2
})
## same thing for th etable grob
output$t1 <- renderTable({
dx <- head(mtcars)
vals$t1 <- tableGrob(dx)
dx
})
## clicking on the export button will generate a pdf file
## containing all grobs
output$export = downloadHandler(
filename = function() {"plots.pdf"},
content = function(file) {
pdf(file, onefile = TRUE)
grid.arrange(vals$p1,vals$p2,vals$t1)
dev.off()
}
)
})

How to make pdf download in shiny app response to user inputs?

I apologize that it took me this long to get back to this. After looking at what I've done, it turns out it was a little more involved than I remembered.

Here's my example app code

library(shiny)
library(ggplot2)
library(magrittr)

ui <- shinyUI(
fluidPage(
column(
width = 2,
selectInput(
inputId = "x_var",
label = "Select the X-variable",
choices = names(mtcars)
),
selectInput(
inputId = "y_var",
label = "Select the Y-variable",
choices = names(mtcars)
),
selectInput(
inputId = "plot_type",
label = "Select the plot type",
choices = c("scatter plot", "boxplot")
),
downloadButton(
outputId = "downloader",
label = "Download PDF"
)
),
column(
width = 3,
tableOutput("table")
),
column(
width = 7,
plotOutput("plot")
)
)
)

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

#****************************************
#* Reactive Values

table <- reactive({
mtcars[, c(input[["x_var"]], input[["y_var"]])]
})

plot <- reactive({
p <- ggplot(data = mtcars,
mapping = aes_string(x = input[["x_var"]],
y = input[["y_var"]]))
if (input[["plot_type"]] == "scatter plot")
{
p + geom_point()
}
else
{
p + geom_boxplot()
}
})

#****************************************
#* Output Components

output$table <-
renderTable({
table()
})

output$plot <-
renderPlot({
plot()
})

#****************************************
#* Download Handlers

output$downloader <-
downloadHandler(
"results_from_shiny.pdf",
content =
function(file)
{
rmarkdown::render(
input = "report_file.Rmd",
output_file = "built_report.pdf",
params = list(table = table(),
plot = plot())
)
readBin(con = "built_report.pdf",
what = "raw",
n = file.info("built_report.pdf")[, "size"]) %>%
writeBin(con = file)
}
)
})

shinyApp(ui, server)

And here is my RMD (entitled report_file.Rmd)

---
title: "Parameterized Report for Shiny"
output: pdf_document
params:
table: 'NULL'
plot: 'NULL'
---

```{r}
params[["plot"]]
```

```{r}
params[["table"]]
```

Some highlights to look for

  • Notice the exists of params in the YAML front matter of the RMarkdown script. This allows us to pass in a list of values to be used in the script when we invoke rmarkdown::render(..., params = list(...))
  • I always build my PDF to a dummy file. That way it's easy to find.
  • The reason I always build to a dummy file is that to get the download handler to work, you need to read the bit-content of the PDF and push it to the file argument using writeBin. See my downloadHandler construction.
  • Using the parameterized report means you don't have to recreate your outputs in the rmarkdown script. The work was done in the Shiny app, the parameterized report just helps you send the objects correctly.
    It isn't quite the same as passing files back and forth (although if it could be that easy, I'd love to know it).

Read more about parameterized reports here: http://rmarkdown.rstudio.com/developer_parameterized_reports.html

Download DT::datatable as pdf in shiny app

You can try the pdf button of the Buttons datatables extension. In this way you don't need a downloadHandler. Otherwise, below is a solution using the good old xtable package. For more sophisticated tables, use kableExtra.

library(shiny)
library(DT)
library(xtable)
library(withr)
library(shinybusy)

ui <- fluidPage(
add_busy_spinner(spin = "cube-grid", onstart = FALSE),
column(
width = 3,
downloadButton(
outputId = "downloader",
label = "Download PDF"
)
),
column(
width = 3,
DTOutput("table")
)

)

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

table <- reactive({
mtcars
})

#****************************************
#* Output Components

output[["table"]] <- renderDT({
datatable(table())
})

#****************************************
#* Download Handlers

output[["downloader"]] <- downloadHandler(
filename = "results_from_shiny.pdf",
content = function(file){
texfile <- paste0(tools::file_path_sans_ext(file), ".tex")
latex <- print.xtable(
xtable(table()), print.results = FALSE,
floating = FALSE, scalebox = "0.7"
)
writeLines(
c(
"\\documentclass[12pt]{standalone}",
"\\usepackage{graphics}",
"\\usepackage{caption}",
"\\begin{document}",
"\\minipage{\\textwidth}",
latex,
"\\captionof{table}{My caption}",
"\\endminipage",
"\\end{document}"
),
texfile
)
with_dir(
dirname(texfile),
tools::texi2pdf(texfile, clean = TRUE)
)
}
)
}

shinyApp(ui, server)

screenshot the whole shinydashboard page as pdf by hitting a button

tada

If we can do things all in the browser, why do we need the Shiny server? pure javascript, 0 line of server code added.

# app.R ##
library(shiny)
library(shinydashboard)
library(DT)

dbHeader <- dashboardHeader(
title = "fr"
)

ui <- dashboardPage(
dbHeader,
dashboardSidebar(),
dashboardBody(
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/dom-to-image/2.6.0/dom-to-image.min.js"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.4.0/jspdf.umd.min.js"),
tags$script(HTML(
'
window.jsPDF = window.jspdf.jsPDF;
function capture(){
domtoimage.toPng(document.querySelector("body"))
.then(function(dataurl) {
var orientation = window.innerHeight >= window.innerWidth ? "portrait" : "landscape";
const doc = new jspdf.jsPDF({unit: "px", orientation: orientation, format: [window.innerHeight, window.innerWidth]});
doc.addImage(dataurl, "PNG", 0, 0, window.innerWidth, window.innerHeight);
doc.save("page.pdf");
});
}
'
)),
tags$hr(),
actionButton("generate", "Generate PDF", onclick="capture()"),
tabsetPanel(
id ="tabA",
type = "tabs",
tabPanel("Front",icon = icon("accusoft"),
plotOutput("ir")
),
tabPanel("Data", icon = icon("table"),
dataTableOutput("iris")
)
)
)
)

server <- function(input, output) {
output$ir<-renderPlot(
plot(iris)
)
output$iris<-renderDataTable(
iris
)
}

shinyApp(ui = ui, server = server)

Sample Image

Shiny to output a function that generates a pdf file itself

Thanks so much for the help from JackStat and Malanche. The following works for downloading the result!

library(shiny)
runApp(list(
#Load the exmaple from the msa package.
mySequenceFile <- system.file("examples", "exampleAA.fasta", package="msa"),
mySequences <- readAAStringSet(mySequenceFile),
myFirstAlignment <- msa(mySequences),
# A simple shiny app.
# Is it possible to see the generated pdf file on screen?
ui = fluidPage(downloadButton('downloadPDF')),
server = function(input, output) {
output$downloadPDF = downloadHandler(
filename = 'myreport.pdf',
content = function(file) {
msaPrettyPrint(
myFirstAlignment
, file = 'myreport.pdf'
, output="pdf"
, showNames="left"
, showLogo="top"
, consensusColor="BlueRed"
, logoColors="accessible area"
, askForOverwrite=FALSE)
file.rename("myreport.pdf", file) # move pdf to file for downloading
},
contentType = 'application/pdf'
)
}
))

How to download a PDF file in a Shiny app

Take a look in the downloadHandler function documentation, it has two arguments without default values: filename and content.

filename is basically the name of the file that will be downloaded. It has not to be inside a function. filename = "your-pdf-name.pdf" works as much as defining it inside the argumentless function.

content, in the other hand, creates a tempfile with the content that is going to be downloaded. In most cases you're going to create a file that is going to be fulfilled with something you have created in you app.

How that is not your case, my solution provides something we call "gambiarra" in Brasil: it copies the file you want to download to the tempfile that shiny needs to the downloadHandler works. (I've tried just define it as the path to the file but it doesn't work)

ui <- fluidPage(
downloadLink("downloadData", "Download")
)

server <- function(input, output) {

output$downloadData <- downloadHandler(
filename = "your-pdf-name.pdf",
content = function(file) {
file.copy("www/teste.pdf", file)
}
)
}

shinyApp(ui, server)


Related Topics



Leave a reply



Submit