I'm creating an application to track soccer statistics for a high school team. The coach wants it to be like an excel sheet, where player data can be inputted and saved to be looked at later. I've figured out how to do that with rhandsontable, but I can't get it to download to my shadow document from my r shiny. I was able to get the shadow doc to work before adding the rhandsontable, but it is no longer working. I'm attaching the code from the server that I'm using to create the table. The player_names is a csv with columns for player name, team level, goals scored,.... other stats. I'm first looking at only one team level (ie varsity), then making the table. It works great in the shiny, but I don't how to save it from there.
output$hot <- renderRHandsontable({
player_names %>%
filter(Team == input$team_level) %>%
rhandsontable()
})
values <- reactiveValues(data = NULL)
observe({
values$data <- hot_to_r(input$hot)
})
# 3. I assign this df to a variable call df1
df1 <- reactive({
values$data
})
Code to make shadow doc:
output$downloadReport <- downloadHandler(
filename = function() {
paste("ophs_soccer", sep = ".", switch(
input$format, PDF = "pdf", HTML = "html", Word = "docx"
))
},
content = function(file) {
src <- normalizePath("shadow_page.Rmd")
# temporarily switch to the temp dir, in case you do not have write
# permission to the current working directory
owd <- setwd(tempdir())
on.exit(setwd(owd))
file.copy(src, "shadow_page.Rmd", overwrite = TRUE)
library(rmarkdown)
out <- render("shadow_page.Rmd", switch(
input$format,
PDF = pdf_document(), HTML = html_document(), Word = word_document()
))
file.rename(out, file)
I tried to create output in the shadow doc like I did with the other reactive inputs, and it did not work. I get an error when trying to download the document.
Related
I have hundreds of images stored locally which I want to include in my Shiny App table. I start by creating a path to each image as a variable
path = "/Users/author/folder/subfolder/"
df%<>% mutate(Image = paste(path, ID, ".png", sep = ""))
I subsequently process them as advised on Adding an image to a datatable in R
for (i in df$Image) {
df$ProcessedIcon <- knitr::image_uri(i)
}
I then create the Icon variable with the processed information
df%<>% mutate(Icon = paste("<img src=", ProcessedIcon ,"></img>", sep = ""))
my server looks like
server <- function(input, output) {
table2 <- reactive ({
df %>%
select(Icon, Category, SubCategory, Item)
})
output$foodtable <- DT::renderDataTable({
DT::datatable(table2(), escape = FALSE)
})
}
My icons still looks like this
Why are the icons not loading properly?
What am I missing?
There are a number of different Q/A's regarding this topic on SO, but none that I have been able to find that fit my use-case. I am also very surprised that RStudio / the Shiny developers themselves have not come out with some documentation on how to do this. Regardless, take this example application:
library(shiny)
library(glue)
library(tidyverse)
# Define UI for application
ui <- fluidPage(
# Application title
titlePanel("Test Multi-File Download"),
p("I hope this works!"),
downloadButton(
outputId = "download_btn",
label = "Download",
icon = icon("file-download")
)
)
# Define server logic
server <- function(input, output) {
#datasets stored in reactiveValues list
to_download <- reactiveValues(dataset1 = iris, dataset2 = airquality, dataset3 = mtcars, dataset4 = NULL)
blahblah <- iris
output$download_btn <- downloadHandler(
filename = function(){
paste("my_data_", Sys.Date(), ".csv", sep = "")
},
content = function(file){
#works
#readr::write_csv(blahblah, file)
#Attempt 1
# #create some temp directory
# temp_directory <- tempdir()
# browser()
# reactiveValuesToList(to_download) %>%
# #x is data, y is name
# imap(function(x,y){
# #browser()
# #check if data is not null
# if(!is.null(x)){
# #create file name based on name of dataset
# file_name <- glue("{y}_data.csv")
# #write file to temp directory
# readr::write_csv(x, file_name)
# }
# })
# zip::zip(
# zipfile = file,
# files = ls(temp_directory),
# root = temp_directory
# )
}
)
}
# Run the application
shinyApp(ui = ui, server = server)
I have some datasets that are stored in a reactiveValues list, and I would like the user to be able to download them all. Ideally, I'd like for them just to be able to download multiple files all at once, rather than having to zip them up, and then download a .zip file. Another option I would be okay with is to add each dataset to an Excel sheet, then download the multi-sheet Excel file.
My general thought process (on the former) is as follows:
Download button gets pressed
create some temporary directory
write (the not NULL) datasets contained in to_download reactiveValues list to this directory
zip the temp directory and download
I feel like I am very close, however I have not been able to successfully get this work yet. Any ideas?
Edit 1: I am aware of the proposed answer here, but would like to avoid using setwd() because I believe it is bad practice to mess with working directories from within a Shiny application.
A few things edited and it's working:
using dir instead of ls inside the zip::zip call to show the contents of the temp directory (ls lists R environment rather than directory contents)
as a further suggestion: making a new, unique folder inside tempdir() to ensure only relevant files are added.
library(shiny)
library(glue)
library(tidyverse)
# Define UI for application
ui <- fluidPage(
# Application title
titlePanel("Test Multi-File Download"),
p("I hope this works!"),
downloadButton(
outputId = "download_btn",
label = "Download",
icon = icon("file-download")
)
)
# Define server logic
server <- function(input, output) {
#datasets stored in reactiveValues list
to_download <- reactiveValues(dataset1 = iris, dataset2 = airquality, dataset3 = mtcars, dataset4 = NULL)
blahblah <- iris
output$download_btn <- downloadHandler(
filename = function(){
paste("my_data_", Sys.Date(), ".zip", sep = "")
},
content = function(file){
temp_directory <- file.path(tempdir(), as.integer(Sys.time()))
dir.create(temp_directory)
reactiveValuesToList(to_download) %>%
imap(function(x,y){
if(!is.null(x)){
file_name <- glue("{y}_data.csv")
readr::write_csv(x, file.path(temp_directory, file_name))
}
})
zip::zip(
zipfile = file,
files = dir(temp_directory),
root = temp_directory
)
},
contentType = "application/zip"
)
}
shinyApp(ui = ui, server = server)
In my own Shiny app I had used a multi-worksheet approach as you suggested above. An alternative setup which works to produce a multi-sheet xlsx workbook using openxlsx could be:
...
output$download_btn <- downloadHandler(
filename = function(){
paste("my_data_", Sys.Date(), ".xlsx", sep = "")
},
content = function(file){
wb <- createWorkbook()
reactiveValuesToList(to_download) %>%
imap(function(x,y){
if(!is.null(x)){
addWorksheet(wb, sheetName = y)
writeData(wb, x, sheet = y)
}
})
saveWorkbook(wb, file = file)
},
contentType = "file/xlsx"
)
...
Created on 2021-12-16 by the reprex package (v2.0.1)
I am working on an R Shiny app where a user supplies information that modifies an existing Word document for the user to download. I've had trouble getting R Shiny to download the resulting new Word document. I've tried regular hyperlinks, and that doesn't seem to work.
(Edit: After typing up this post, I came across how to download files with hyperlinks. I forgot files need to be placed inside a www folder, as specified here: Shiny hyperlink relative path to a file. So although I can get my Shiny App to work using this approach, I'd still like to know why my example below is not working).
I came across Github Issue #145 (https://github.com/davidgohel/officer/issues/145) which almost has the solution. But the pptx being downloaded is created from scratch whereas I want to start from an EXISTING Word docx.
In my code example, there are 3 downloadHandler buttons:
Uses the original pptx download code example from Github Issue #145
The second modifies the above to download a docx
The third button is my attempt to download an existing and modified docx file
The third button is not working as I had hoped. If I had to guess, I think it has to do with my template being from from read_docx. It looks like it creates some temporary file behind the scenes. But I don't know where to go from here.
For completeness, here are some related links:
Reporters package to download docx report from shiny (uses ReporeRs which is older than officer R package)
Writing word documents with the officer package: How to combine several rdocx objects? (Helpful if merging existing docx to the tempfile in my example)
downloadHandler reference: https://shiny.rstudio.com/reference/shiny/latest/downloadHandler.html
# -------- Example code ------------
library(shiny)
library(officer)
library(mschart)
library(dplyr)
# Create template folder and file. (Irrelevant if already exists.)
dir.create("www")
read_docx() %>%
body_add_par("My template file") %>%
print(., target = "www/template.docx")
# Existing file as Template
mytemplate <- read_docx(path = "www/template.docx")
# For Button 1
gen_pptx <- function(chart, file) {
read_pptx() %>%
add_slide(layout = "Title and Content", master = "Office Theme") %>%
ph_with_chart(chart = chart) %>%
print(target = file)
}
chart <- data.frame(x = letters[1:3], y = 1:3) %>%
ms_barchart(x = "x", y = "y")
# For button 2
gen_docx <- function(file) {
read_docx() %>%
body_add_par("Hello World") %>%
print(target = file)
}
# For button 3
gen_docx2 <- function(file, doc) {
file %>%
body_add_par("Hello World") %>%
body_add_docx(src = doc) %>%
print(target = file)
}
ui <- fluidPage(
titlePanel("Example"),
downloadButton("chart", "Get Chart"),
downloadButton("document", "Get New Doc"),
downloadButton("document2", "Get Doc from Template"),
tags$hr(),
tags$p("Example hyperlink works"),
tags$a(href='template.docx', target='_blank', 'Can only download from www folder', download = 'template.docx')
)
server <- function(input, output) {
output$chart <- downloadHandler(
filename = function() paste0("chart_", Sys.Date(), ".pptx"),
content = function(file) {
file_pptx <- tempfile(fileext = ".pptx")
gen_pptx(chart, file_pptx)
file.rename( from = file_pptx, to = file )
}
)
output$document <- downloadHandler(
filename = function() paste0("doc_", Sys.Date(), ".docx"),
content = function(file) {
file_docx <- tempfile(fileext = ".docx")
gen_docx(file_docx)
file.rename( from = file_docx, to = file )
}
)
output$document2 <- downloadHandler(
filename = function() paste0("doc_", Sys.Date(), ".docx"),
content = function(file) {
file_docx <- tempfile(fileext = ".docx")
gen_docx2(file_docx, mytemplate)
file.rename( from = file_docx, to = file )
}
)
}
shinyApp(ui = ui, server = server)
Do not use print to generate docx file in helper function gen_docx2.
Instead, you should use print as a last step in content function to return file to the downloadHandler.
Simple example:
gen_docx2 <- function(file, doc) {
file %>%
body_add_par("Hello World") %>%
body_add_docx(src = doc)
}
notice the function above does not print anything
outputdocument2 <- downloadHandler(
filename = function() {
paste0("doc_", Sys.Date(), ".docx")
},
content = function(file) {
doc <- read_docx() %>% gen_docx2(file_docx, mytemplate)
print(doc, target = file)
}
)
Use read_docx() to generate temporary word file and then use print(doc, target = file) in the end to pass it to the downloadHandler. The function file.rename is not needed.
I have simplified a lot the shiny app I'm trying to build, but, in the idea, I have two functions :
choose_input <- function(n1,n2,n3){
x1 <<- n1+n2
x2 <<- n2+n3
x3 <<- (n1*n2)/n3
}
createmydata <- function(n){
c1 <- c(1:n)
c2 <- c1+(x2*x3)
c3 <- c2+x1
df <- data.frame("column1"=c1,"column2"=c2,"column3"=c3)
return(df)
}
You'll tell me that I can do simply one function with these two because they are very simple, but in my app there are a lot of lines and I have to separate the two. Anyway, here is my simulated code :
ui <- fluidPage(
numericInput("n1",label="Choose the first parameter",min=0,max=100,value=3),
numericInput("n2",label="Choose the second parameter",min=0,max=100,value=4),
numericInput("n3",label="Choose the third parameter",min=0,max=100,value=5),
numericInput("n",label="Choose dataframe length",min=1,max=10000,value=100),
radioButtons("filetype", "File type:",
choices = c("csv", "tsv")),
downloadButton('downloadData', 'Download'),
tableOutput("data")
)
server <- function(input,output){
RE <- reactive({
choose_input(input$n1,input$n2,input$n3)
createmydata(input$n)
})
output$data <- renderTable({
RE()
})
output$downloadData <- downloadHandler(
filename = function() {
paste(name, input$filetype, sep = ".")
},
content = function(file) {
sep <- switch(input$filetype, "csv" = ",", "tsv" = "\t")
write.table(RE(), file, sep = sep,
row.names = FALSE)
}
)
}
shinyApp(ui = ui, server=server)
As you can see, I'd like to download the output table to a csv or excel file... I let you try the code and then try to click on the download button, it does not work...
Debugging
When I run the code up above and attempted to download the data set, I received the following warning and error message in the Console Pane within RStudio.
Warning: Error in paste: object 'name' not found
Stack trace (innermost first):
1: runApp
Error : object 'name' not found
This led me to examine the paste() function used within the filename argument in shiny::downloadHandler(). In your code, you use the object name without ever assigning it a value.
I replaced name with the text "customTable" within the filename argument inside of downloadHandler().
output$downloadData <- downloadHandler(
filename = function() {
paste( "customTable", input$filetype, sep = ".")
},
content = function(file) {
sep <- switch(input$filetype, "csv" = ",", "tsv" = "\t")
write.table(RE(), file, sep = sep,
row.names = FALSE)
}
)
Downloading Data in Browser
After running the app.R script, I clicked on the Open in Browser button to view the Shiny app in a new tab on Chrome. Once there, I was successfully able to download both a .csv and .tsv file after hitting the download button.
Note: I'm looking for a better reason as to why this action needs to occur, but for now, I came across this relevant SO post Shiny app: downloadHandler does not produce a file.
I have seen in several examples that the input for .Rmd files are coming from input objects.
I have checked the similar questions (also this one)
I have the following problem:
ui <- fluidPage(
fileInput(inputId = "File",label = "Upload file...",accept=c("zip","text")),
radioButtons('format', 'Document format', c('PDF', 'HTML', 'Word'),
inline = TRUE),
downloadButton('downloadReport'),
tableOutput("AEP")
)
)
The input object is a zip file. contains different folders. I extract the suitable folders and picking up my files in server function.
server <- function(input, output){
options(shiny.maxRequestSize=50*1024^2)
output$AEP <- renderTable({
infile=input$File
if (is.null(infile))
return(NULL)
report_list <- c("Park result.txt",
"Park result minus",
"Park result plus")
temp_files <- unzip(infile$datapath)
temp_files <- temp_files[grepl(paste(report_list, collapse = "|"), temp_files)]
T=length(temp_files)
t1=3*c(1:(T/3))
t2=c(1:T)
t2=t2[-t1]
p=c();for(i in 1:T){p[[i]]=c()}
for(i in 1:(length(t1))){p[[t1[i]]]=read.table(temp_files[t1[i]],skip=1,sep=";")}
for(i in 1:(length(t2))){p[[t2[i]]]=read.table(temp_files[t2[i]],skip=2,sep=";")}
...
And finally I have my files as p[[i]]. I do calculations on those p[[i]], at the end my output called AEP (which is a table). I wanna use that table in markdown report. now for having proper input for my Rmd file I need to add this function to server :
output$downloadReport <- downloadHandler(
filename = function() {
paste('my-report', sep = '.', switch(
input$format, PDF = 'pdf', HTML = 'html', Word = 'docx'
))
},
content = function(file) {
# Copy the report file to a temporary 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(tempdir(), "report.Rmd")
file.copy("report.Rmd", tempReport, overwrite = TRUE)
# Set up parameters to pass to Rmd document
params <- list(n = output$AEP)
# 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_file = file,
params = params,
envir = new.env(parent = globalenv())
)
}
)
Which I could not compile it because :
params <- list(n = output$AEP)
and Error : Reading objects from shinyoutput object not allowed.
Any Idea of how I should define params would be very much appreciated. (I have tried n=input$File and it did not worked)
I do not know also how should I proceed with reactive() function in this case.