Use download button with dataframe inside observe event - r

I have a application that allows user to upload the data, then an action button to trigger some calculation. I then have some progress bar to show user how long the process would take. Once the calculation finish, I would like to add a download button so user can download the calculation result.
I am not sure how to access the datatable created inside observe event function so I can use it in the downloadhandler function?
Here is my code:
server <- function(input, output) {
options(shiny.maxRequestSize=200*1024^2)
file_name <- reactive({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
return (stringi::stri_extract_first(str = inFile$name, regex = ".*(?=\\.)"))
})
output$myFileName <- renderText({ paste("Claim data selected:",file_name()) })
mydata <- reactive({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
tbl <- read.csv(inFile$datapath,sep=";")
return(tbl)
})
output$my_output_data <- DT::renderDataTable({
mydata() },
options = list(
lengthChange = FALSE,
autowidth = TRUE,
columnDefs = list(list(width = '70%', targets = 1)))
)
output$summary <- renderText({
dt.size <- nrow({mydata()})
paste("There are",dt.size,"records.", sep =" ")
})
observeEvent(input$goButton1,{
output$table1 <- DT::renderDataTable({
withProgress(message = 'Calculation in progress...',
value = 0, {function1({mydata()},progress=TRUE)})
})
output$table1 <- DT::renderDataTable(function1({mydata()}))
output$downloadData <- downloadHandler(
filename = function() {
paste("DLR result-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
#fwrite("output$table 1 should be here", file)
})
})
}

The issue with the code you have given is that the fread function from the data.table library takes a data.frame or data.table argument. Here you have given it a DT javascript DataTable object. My code uses base R data.frames instead of data.table, but you should be able to adapt it accordingly.
library(shiny)
library(shinydashboard)
ui = dashboardPage(
dashboardHeader(title = "File Download"),
dashboardSidebar(),
dashboardBody(
fluidPage(
fluidRow(
box(width=12,
title = "UploadDownload",
fileInput("file1", label="File1 upload"),
downloadButton("downloadData", "Download")
)
),
fluidRow(
box(width=12,
title = "DataTable",
textOutput("myFileName"),
DT::dataTableOutput("my_output_data")
)
)
)
)
)
server = function(input, output) {
file_name = reactive({
req(input$file1)
return(gsub("\\..*$", "", input$file1$name))
})
output$myFileName = renderText({
paste("Claim data selected:",file_name())
})
mydata = reactive ({
req(input$file1)
tbl = read.csv(input$file1$datapath)
return(tbl)
})
mydata2 = reactive ({
tbl = mydata()
# a calculation that will take some time
withProgress(message="Adding another column", detail="this may take some time",
{
n = dim(tbl)[2]
tbl$newcolumn = NULL
for (i in 1:n) {
tbl$newcolumn[i] = sample.int(10,1)
incProgress(1/n)
Sys.sleep(5/n)
}
})
})
output$my_output_data = DT::renderDataTable(
mydata(),
options = list(
lengthChange=FALSE,
autowidth=TRUE,
columnDefs=list(list(width='70%', targets=1))
)
)
output$downloadData = downloadHandler(
filename = function() {
paste("DLR results-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(mydata2(), file)
}
)
}
shinyApp(ui, server)

Related

Shiny: Use filtered datatable in further functions

How do you use the filtered output from datatables in further functions?
I am making a Shiny app where the data is uploaded and filtered by the user with a datatable. I then want to use the filtered data to perform some functions. I give one below (calcs()). I am getting the Error: invalid 'x' type in 'x && y'.
The .csv file is:
Day: 1,2,3
Subject: 1,2,3
Location: 1,1,2
ui.R
shinyUI(fluidPage(theme = shinytheme("spacelab"),
# title and subtitles
titlePanel(
h1(HTML("</b>Test App</b>"),
style="text-align:center")),
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose Input .csv File",
multiple = TRUE,
accept = c("text/csv",
"text/comma-separated-values,text/plain",
".csv")
),
downloadButton("downloadData",
"Download Results (.xlsx)")
),
mainPanel(title = "Raw Data",
DT::dataTableOutput("table"),
)
)
))
server.R
shinyServer <- function(input, output, session) {
datasetInput <- reactive({
if(is.null(input$file1)) return(NULL)
csv_data <- read.csv(input$file1$datapath)
})
output$table = DT::renderDataTable(datasetInput(),
filter = "top",
server = "FALSE")
calcs <- reactive({
remove_pos_baseline <- as.data.frame(input$table_rows_all) %>%
group_by(Location) %>%
mutate(count = first(Subject))
})
data_list <- reactive({
list(
table = calcs(),
patient_results = datasetInput()
)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("Output", datasetInput()$Location[1], "-", Sys.Date(), ".xlsx", sep="")
},
content = function(file) {
write_xlsx(data_list(), path = file)
}
)
}

Shiny: How to disable download button when there no data?

I'm working inside a module which queries some data and then shows it on a DT::datatable, I added a download button so I can download the data with the filters applied.
I already called useShinyjs() in the main ui file of the app.
But I want to disable the download button in case there is no data.
I've tried the following.
observeEvent(data(), {
if (!nrow(data()) > 0) {
shinyjs::disable("download")
} else {
shinyjs::enable("download")
})
However the next error message shows up, and the app crashes as soon as I run it.
Expecting a single string value: [type=character; extent=0]
ui Code:
module_ui <- function(id) {
ns <- NS(id)
tagList(
fluidRow(
tabBox(
title = tagList(
downloadButton(ns("download"), label = "Download data")
),
width = 12,
tabPanel(
title = HTML("Documentation"),
div(style = 'overflow-x: scroll;font-size:90%', DTOutput(ns("table")))
)
)
)
)
}
server Code:
module_server <- function(id,
connection,
update_button,
update_button_name) {
moduleServer(
id = id,
module = function(input, output, session) {
ns <- session$ns
# 1 . Data -----
data <- eventReactive(list(update_button()), {
data <- dbGetQuery(
connection,
glue::glue("SELECT * FROM Process;)
return(data)
}, ignoreNULL = FALSE, ignoreInit = FALSE)
# 2 . Table -----
output$table<- renderDT({
shiny::validate(
shiny::need(!is_null(data()) && nrow(data()) > 0, 'No data...')
)
datatable(
data = data(),
selection = "single",
style = "bootstrap",
rownames = FALSE,
filter = 'top',
options = list(
searchHighlight = TRUE,
dom = 'tipr',
pageLength = 20,
columnDefs = list(
list(visible = F, targets = c(0)),
list(width = "200px", targets = "_all")
)
)
)
}, server = TRUE)
# 3 . Download -----
observeEvent(data(), {
if (nrow(data()) > 0) {
shinyjs::enable("download")
} else {
shinyjs::disable("download")
}
})
output$download <- downloadHandler(
filename = "Documentation.xlsx",
content = function(file) {
openxlsx::write.xlsx(
x = data() %>% slice(input$tabla_rows_all),
file = file,
asTable = FALSE,
row.names = FALSE
)
}
)
Many thanks in advance to whoever can help!
Use shinyjs::toggleState() instead. Here is a reproducible example:
library(shiny)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
fileInput(
inputId = "file1", label = "Choose a file to upload:", accept = ".csv"
),
tableOutput(
outputId = "table1"
),
downloadButton(
outputId = "download_data", class = "btn-success"
)
)
server <- function(input, output, session) {
the_data <- reactive({
req(input$file1)
read.csv(input$file1$datapath)
})
output$table1 <- renderTable({
the_data() |> head()
})
# <-- observe if there's any input file -->
observe({
# mandatory condition: there should be an input file
mand_condition <- \() {
!is.null(input$file1)
}
shinyjs::toggleState(
id = "download_data", condition = mand_condition()
)
})
output$download_data <- downloadHandler(
filename = \() {
input$file1$name
},
content = function(file) {
write.csv(the_data(), file)
}
)
}
shinyApp(ui, server)

Download filtered tableOutput in Shiny

I have the following data:
> data
products id
1 P1 280386
2 P1 285184
3 P2 293154
4 P1 294245
I have built a simple shiny code. I first filter the table and then I want to download the filtered table. I write the following
library(shiny)
library(shinyWidgets)
library(tidyverse)
library(DT)
data <- read.csv("Desktop/data.csv")
products <- unique(data$products)
ui <- fluidPage(
fluidRow(
column(4,
selectInput("product", "Product", products,
multiple = TRUE),
downloadButton("download", "Download")),
column(8,
tableOutput("errorTable")
)
)
)
server <- function(input, output, session) {
output$errorTable <- renderTable({
subset(data, products == input$product)
}
)
output$download <- downloadHandler(
filename = function() {
paste("data-",Sys.Date(), ".csv", sep = "")
},
content = function(file) {
write.csv(data, file)
}
)
}
shinyApp(ui, server)
However, this code only downloads the full table, not the filtered one. I have searched some question but none explained this case specifically. thanks in advance
Try this
library(shiny)
library(shinyWidgets)
library(tidyverse)
library(DT)
data <- read.csv("Desktop/data.csv")
products <- unique(data$products)
ui <- fluidPage(
fluidRow(
column(4,
selectInput("product", "Product", products,
multiple = TRUE),
downloadButton("download", "Download")),
column(8,
tableOutput("errorTable")
)
)
)
server <- function(input, output, session) {
#you need to create a reactive object with a NULL starting value
listofitems <- reactiveValues(data = NULL )
#observe the changes in input$product and update the reactive object
observeEvent( input$product, {
print("Hello: observeEvent for input$product is triggered")
#debug using browser()
listofitems$data <- subset(data, products == input$product)
showNotification("Products updated",
type = "message",
duration = 4,
closeButton = TRUE)
}, ignoreInit = T,ignoreNULL = TRUE)
output$errorTable <- renderTable({
listofitems$data
}
)
output$download <- downloadHandler(
filename = function() {
paste("data-",Sys.Date(), ".csv", sep = "")
},
content = function(file) {
write.csv(listofitems$data, file)
}
)
}
shinyApp(ui, server)

Render images for interactive display from folder loaded with shinyDirChoose

The code below works if the user chooses several images. How can I implement this with shinyDirChoose, so that the user only chooses the folder where the images are located.
Problem: I don't know how to get the local datapath which is stored in the files() object. This path is needed for rendering the images.
Couldn't find any good answers in the web so far.
library(shiny)
ui <- shinyUI(fluidPage(
tags$script('
$(document).on("keydown", function (e) {
Shiny.onInputChange("rightCursor", [e.which,e.timeStamp]);
});
'),
sidebarLayout(
sidebarPanel(
fileInput(inputId = 'files',
label = 'Select an image or several images',
multiple = TRUE,
accept=c('image/png', 'image/jpeg'))
),
mainPanel(
uiOutput('images'),
tableOutput('files')
)
)
))
server <- shinyServer(function(input, output) {
rv <- reactiveValues(page = 1)
output$files <- renderTable(input$files)
files <- reactive({
files <- input$files
files$datapath <- gsub("\\\\", "/", files$datapath)
files
})
output$images <- renderUI({
if(is.null(input$files)) return(NULL)
imagename = paste0("image", rv$page)
image_output <- imageOutput(imagename)
})
observe({
if(is.null(input$files)) return(NULL)
for (i in 1:nrow(files()))
{
print(i)
local({
my_i <- i
imagename = paste0("image", my_i)
output[[imagename]] <-
renderImage({
list(src = files()$datapath[my_i], width = 400, height = 400,
alt = "Image failed to render")
}, deleteFile = FALSE)
})
}
})
navPage <- function(direction) {
rv$page <- rv$page + direction
}
observeEvent(input$rightCursor,{
navPage(1)
print(rv$page)
})
})
shinyApp(ui=ui,server=server)
The user only chooses the folder where the images are located, instead of one or several files.
Here is a way.
library(shiny)
library(shinyFiles)
ui <- fluidPage(
tags$head(
tags$script('
$(document).on("keydown", function (e) {
Shiny.onInputChange("rightCursor", [e.which,e.timeStamp]);
});
')
),
mainPanel(
shinyDirButton("dir", "Input directory", "Upload"),
verbatimTextOutput("dir", placeholder = TRUE),
uiOutput('images')
))
server <- function(input, output) {
shinyDirChoose(
input, "dir", roots = c(home = "~"), filetypes = c("png", "jpg")
)
folder <- reactiveVal()
output$dir <- renderText({
folder()
})
observeEvent(input$dir, {
if (!"path" %in% names(input$dir)) return()
home <- normalizePath("~")
folder(
file.path(
home,
paste(unlist(input$dir$path[-1]), collapse = .Platform$file.sep)
)
)
})
files <- eventReactive(folder(), {
list.files(folder(), full.names = TRUE)
})
page <- reactiveVal(1)
output$images <- renderUI({
req(files())
imagename = paste0("image", page())
imageOutput(imagename)
})
observeEvent(files(), {
for (i in 1:length(files()))
{
print(i)
local({
my_i <- i
imagename = paste0("image", my_i)
output[[imagename]] <-
renderImage({
list(src = files()[my_i], width = 400, height = 400,
alt = "Image failed to render")
}, deleteFile = FALSE)
})
}
})
observeEvent(input$rightCursor,{
page(page()+1)
})
}
# Run the application
shinyApp(ui = ui, server = server)

How to download editable data table in shiny

In data table, we can use argument editable to make the table editable. I'm making a shiny app in which table is both editable and downloadable.
My question is how I can download a datatable after I edit it?
Here is my app code:
library(shiny)
library(DT)
server <- function(input, output) {
df = iris
output$data = DT::renderDataTable ({
DT::datatable(df, editable = list(
target = 'row',
disable = list(columns = c(1, 3, 4))
))
})
output$downloadData <- downloadHandler(
filename = function() {
#paste(input$dataset, ".csv", sep = "")
},
content = function(file) {
write.csv(df, file, row.names = FALSE)
}
)
}
ui <- fluidPage(
DT::dataTableOutput('data'),
downloadButton("downloadData", "Download")
)
shinyApp(ui = ui, server = server)
When you edit a cell of a datatable named "XXX", the info about the cell edit is in input$XXX_cell_edit. This info contains the indices of the edited cell and its new value. So you can do:
library(shiny)
library(DT)
dat <- iris[1:3, ]
ui <- fluidPage(
downloadButton("downloadData", "Download"),
DTOutput("table")
)
server <- function(input, output){
output[["table"]] <- renderDT({
datatable(dat, editable = "cell")
})
df <- reactiveVal(dat)
observeEvent(input[["table_cell_edit"]], {
cell <- input[["table_cell_edit"]]
newdf <- df()
newdf[cell$row, cell$col] <- cell$value
df(newdf)
})
output[["downloadData"]] <- downloadHandler(
filename = function() {
"mydata.csv"
},
content = function(file) {
write.csv(df(), file, row.names = FALSE)
}
)
}
shinyApp(ui, server)
Alternatively, as suggested by #MrGumble, you can use the embedded button of Datatables instead of a downloadHandler. This is more stylish.
library(shiny)
library(DT)
dat <- iris[1:3, ]
ui <- fluidPage(
DTOutput("table")
)
server <- function(input, output){
output[["table"]] <- renderDT({
datatable(dat, editable = "cell", extensions = "Buttons",
options = list(
dom = "Bfrtip",
buttons = list(
"csv"
)
))
})
observeEvent(input[["table_cell_edit"]], {
cellinfo <- input[["table_cell_edit"]]
dat <<- editData(dat, input[["table_cell_edit"]], "table")
})
}
shinyApp(ui, server)
You can add a download button directly to a DT datatable, which offers the user to download the current data in the table, see R Shiny: How to add download buttons in DT::renderDataTable
If you however want to use the edited data for server-side calculations, you are are on the right track, but need to save the edited table into the data.frame using replaceData. See e.g. https://yihui.shinyapps.io/DT-edit/

Resources