Hi all
I am trying to get my shiny app to download pdf reports for the reactive outputs that appear on screen. The problem I am having is that reactives are not being invalidated within the loop through different data. Similar to this issue https://github.com/rstudio/shiny/issues/532. The app has a file import where you choose what input data to bring in, then you can swap between radio buttons to choose which you view.
Downloading the selected radio button data works but I cant figure out how to iterate through all the listed files to create the plot and then download with the download handler to pdf.
I have tried invalidateLater, lapply and local environments inside a loop as suggested here https://gist.github.com/bborgesr/e1ce7305f914f9ca762c69509dda632e but I cant seem to get the code quite right.
Here is a reprex where single download works but multiple cannot find the contents. (Sorry its a bit big, I tried to keep to how my actual app is working as much as possible). Input data can be found here http://www.filedropper.com/reprexfiles
Thanks in advance
library(shiny)
library(shinydashboard)
library(readxl)
library(ggplot2)
library(plotly)
library(zip)
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(sidebarMenu(
# upload file box
fileInput(
inputId = "file",
label = "Select one or more .xlsx spreadsheet(s)",
multiple = TRUE,
accept = c(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlsx")), # only allows .xlsx format files to be uploaded
# radio buttons (default value of not visible)
radioButtons("yaxisGrp2", "", c(" " = "1")),
#download button
downloadButton("report", "Create report(s)"),
radioButtons("report_type", "Number of Files:",
c("single" = "single", "multiple" = "multiple"),
selected = "single"
)
)
),
dashboardBody(
plotlyOutput(
"myplot", inline = TRUE)
)
)
server <- function(input, output, session) {
cb_list <- reactiveValues(samples = NULL)
#### Collect data details ####
# List cases which can be ticked to be replaced with files (.xlsx)
# selected by the user
observe({
data <- input$file
dsnames2 <- data[, 1]
cb_options <- list()
cb_options[dsnames2] <- dsnames2
updateRadioButtons(session,
"yaxisGrp2", # update radio buttons with values
label = " ",
choices = cb_options,
selected = cb_options[1]
)
cb_list$samples <- cb_options
})
# record file name into a variable
input_data <- reactive({
validate(
need(input$file != "", "Choose one or more files (.xlsx)")
)
infile <- input$file
if (is.null(infile)) {
return(NULL)
}
infile
})
# monitor for a change in radio button selection
chosen_file <- eventReactive(input$yaxisGrp2, {
input_file <- input_data()
reactiveValues(
path = input_file$datapath[input_file$name == input$yaxisGrp2],
name = input_file$name
)
})
# load the selected files (.xlsx) based on selection
mydata <- reactive({
# check that the uploaded data is in the expected format and stop if it isnt
temp <- read_excel(chosen_file()$path, sheet = 1)
validate(
need(
names(temp)[1] == "Name",
"I am an error"
)
)
# Read-in each datatable as a different element of a list
data_list <- list(
read_excel(chosen_file()$path)
)
return(data_list)
})
#make the base plot
do_the_plot <- reactive({
ggplot(mydata()[[1]], aes(x = Name, y = Size)) +
geom_col(fill = "blue",
colour = "black")
})
#change plot to plotly for interactive env
plot_for_shiny <- reactive({
ggplotly(do_the_plot(),
tooltip = c("Name", "Size"),
dynamicTicks = "y")
})
#change theme elements for pdf only
plot_for_report <- reactive({
do_the_plot() +
theme_bw()
})
output$myplot <- renderPlotly({
plot_for_shiny()
})
#output report
output$report <-
downloadHandler(
filename = function() { # choose filename based on combination of radio buttons chosen
#select substring of name for file naming
sample_name <- substr(input$yaxisGrp2, 1, regexpr("([ ])", input$yaxisGrp2) - 1)
if (input$report_type == "single") {
filename <- paste(sample_name, "Report.pdf", sep = "_")
} else {
filename <- "output.zip"
}
return(filename)
},
content = function(file) { # choose content based on combination of radio buttons chosen
filedir <- dirname(chosen_file()$path[1])
sample_name <- substr(input$yaxisGrp2, 1, regexpr("([ ])", input$yaxisGrp2) - 1)
# Copy the report file to a known directory before processing it, in
# case we don't have write permissions to the current working dir (which
# can happen when deployed).
tempReport <- file.path(filedir, "example_report.Rmd")
file.copy("example_report.Rmd", tempReport, overwrite = TRUE)
# render a single report
if (input$report_type == "single") {
parameters <- list(
filename = sample_name,
myplot = plot_for_report()
)
# Knit the document, passing in the `params` list, and eval it in a
# child of the global environment (this isolates the code in the document
# from the code in this app).
rmarkdown::render(tempReport,
output_format = "pdf_document",
output_file = file,
params = parameters,
envir = new.env(parent = globalenv())
)
} else { # if downloading multiple reports
fs <- c()
observeEvent(input$report,{
# invalidate every 1 second
invalidateLater(1000)
isolate({
# Find index of currently selected choice, and then get index of the next one.
# index %% mod n + 1, so goes back to 1 when index = n
index = which(cb_list$samples == input$file)
index = index %% length(cb_list$samples) + 1
# Update the radioButtons
updateRadioButtons(session, "yaxisGrp2",selected = cb_list$samples[index])
})
#select substring of name for file naming
sample_name <- substr(input$yaxisGrp2, 1, regexpr("([ ])", input$yaxisGrp2) - 1)
# Set up parameters to pass to Rmd document for colour report
parameters <- list(
filename = sample_name,
myplot = plot_for_report()
)
# set output file name based on colour choice
fname <- paste0(filedir, "/", sample_name, "_Report.pdf")
# render report
rmarkdown::render(tempReport,
output_format = "pdf_document",
output_file = fname,
params = parameters,
envir = new.env(parent = globalenv())
)
fs <- c(fs, fname)
})
# change to temp directory on exit so zip function can find files
owd <- setwd(filedir)
on.exit(setwd(owd))
# zip all created files into one zip file
zipr(file, fs)
}
})