Here are the requirements.
1)I need to browse and upload an excel file (with package readxl), which is used to arrive at some calculations which I need to display in shiny window as a different table output
2) Manually edit some data in the uploaded file and it should automatically reflect in the results displayed
3) We should be able to download the edited file.
I have written so far. I have columns ID, exposure and frequency in input data. For each ID I need to calculate a variable using corresponding exposure and frequency which would be displayed. I need to manually edit frequency and Exposure using ID ( which is unique ). I have added an "update" button. But change is not permanent. It goes back as soon as I click update button once more
library(shiny)
ui = fluidPage(
titlePanel("HEllo world"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose xlsx file',
accept = c(".xlsx")),
actionButton("go", "update"),
numericInput('NewVal', 'Enter new Frequency',NULL),
numericInput('NewExp', 'Enter new Exposure',NULL)),
mainPanel(
textInput('ID', 'Enter ID'),
dataTableOutput('contents')
)))
server = function(input,output){
ef <- eventReactive(input$go, {
infile <- input$file1
if(is.null(infile))
return(NULL)
file.rename(infile$datapath,paste(infile$datapath, ".xlsx", sep=""))
data<-read_excel(paste(infile$datapath, ".xlsx", sep=""), 1)
if(input$ID!="" && input$go>0){
for( i in 1:nrow(data)){
if( input$ID == data$'ID'[i]){
if(!is.na(input$NewVal)){
data$' Frequency'[i] <- input$NewVal
}
if(!is.na(input$NewExp)){
data$'Exposure'[i] <- input$NewExp
}
}}}
data
}, ignoreNULL = FALSE)
output$contents <- renderDataTable({ef()})}
shinyApp(ui,server)
UPDATE!:As per one answer, I have made some changes to my code. The new code seems to be working fine. Here is the working code, for anyone who might need help with the same issue.
ui = fluidPage(
titlePanel("HEllo world"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose xlsx file',
accept = c(".xlsx")),
actionButton("go", "update"),
numericInput('NewVal', 'Enter new Frequency',NULL),
numericInput('NewExp', 'Enter new Exposure',NULL)),
mainPanel(
textInput('ID', 'Enter ID'),
tableOutput('contents')
)))
server = function(input,output){
# Reactive value to save input data frame for use elsewhere
original <- reactiveValues()
observeEvent(input$file1, {
theFile <- input$file1
if(is.null(theFile)) {
return(NULL)}
**file.rename(theFile$datapath,paste(theFile$datapath, ".xlsx", sep=""))**
original$oldData <- read_excel(paste(theFile$datapath, ".xlsx", sep = ""), 1)
})
observeEvent(input$go, {
original$newData <- original$oldData
if(input$ID !="") {
for( i in 1:nrow(original$oldData)){
if( input$ID == original$oldData$'ID'[i]){
if(!is.na(input$NewVal)){
original$newData$'Frequency'[i] <- input$NewVal
}
if(!is.na(input$NewExp)){
original$newData$'Exposure'[i] <- input$NewExp
}
}
}
**original$oldData<-original$newData** }
})
output$contents <- renderTable({
if(!is.null(original$newData)) {
original$newData}
else {
original$oldData}
})
}
shinyApp(ui = ui, server = server)
Some of the comments seem to be on the right track of what's going on here. There's several solutions that could be used, but I'll just share what's most intuitive to me. Also, I'll only be changing the server function.
server = function(input,output){
# Reactive value to save input data frame for use elsewhere
original <- reactiveValues()
observeEvent(input$file1, {
theFile <- input$file1
if(is.null(theFile)) {return(NULL)}
original$oldData <- read_excel(paste(theFile$datapath, ".xlsx", sep = ""), 1)
})
observeEvent(input$goButton2, {
original$newData <- original$oldData
if(input$ID !="") {
for( i in 1:nrow(data)){
if( input$ID == dat$'ID'[i]){
if(!is.na(input$NewVal)){
original$newData$' Frequency'[i] <- input$NewVal
}
if(!is.na(input$NewExp)){
original$newData$'Exposure'[i] <- input$NewExp
}
}
}
}
})
output$contents <- renderDataTable({
if(!is.null(original$newData)) {original$newData}
else {original$oldData}
})
}
This won't change the table output until the go button is clicked. I haven't tested it fully, since I don't have your data, but I believe this should set you on the right track at the bare minimum... I like observe statements, because they cause side effects and seem more open ended than eventReactives or functions.
This only helps with the initial issues of having the correct changes made and continued showing in the output. If this works, it should be fairly easy to add a download function, which saves the file whenever it's updated.
Update 1
The code below should do what you would want it to do. I've added two different capabilities for saving the new data frame. The commented out code saves the data automatically whenever the update button is pressed. The code that's there without comments around it creates a download button for downloading the data. I've also added a line that calculates a new value based off frequency and exposure. Named this column Value in data set. Hope this helps!
#### Example app for Exchange answer
library(shiny)
library(readxl)
ui = fluidPage(
titlePanel("HEllo world"),
sidebarLayout(
sidebarPanel(
fileInput('file1', 'Choose xlsx file',
accept = c(".xlsx")),
actionButton("go", "update"),
numericInput('NewVal', 'Enter new Frequency',NULL),
numericInput('NewExp', 'Enter new Exposure',NULL),
# Download button (goes with download handler below)
# Use if desire is to save at press of button
downloadButton("save", "Download")
),
mainPanel(
textInput('ID', 'Enter ID'),
dataTableOutput('contents')
)
)
)
server = function(input,output){
# Reactive value to save input data frame for use elsewhere
original <- reactiveValues()
observeEvent(input$file1, {
theFile <- input$file1
if(is.null(theFile)) {
original$oldData <- NULL
} else {
original$oldData <- read_excel(theFile$datapath, 1)
}
})
observeEvent(input$go, {
original$newData <- original$oldData
if(input$ID !="") {
for(i in 1:nrow(original$oldData)){
if(input$ID == original$oldData$'ID'[i]){
if(!is.na(input$NewVal)){
original$newData$'Frequency'[i] <- input$NewVal
}
if(!is.na(input$NewExp)){
original$newData$'Exposure'[i] <- input$NewExp
}
### Make sure a column in your data set is named Value for this
# Calculate a new column
original$newData$'Value'[i] <- (original$newData$'Exposure'[i]*
original$newData$'Frequency'[i])
}
}
original$oldData<-original$newData
}
### Use this to automatically save table when update is clicked
# write.csv(original$newData,
# file = #Desired Pathname,
# row.names = FALSE)
})
output$contents <- renderDataTable({
if(!is.null(original$newData)) {
original$newData}
else {
original$oldData
}
})
### Use this code below if desired saving is through download button
# Server code for download button
output$save <- downloadHandler(
filename = function() {
paste0("newData - ", Sys.Date(), ".csv")
},
content = function(con) {
if (!is.null(original$newData)) {
dataSave <- original$newData
} else {
dataSave <- original$oldData
}
con <- ## Desired save location... could just use `getwd()` to
# save to working directory
write.csv(dataSave, con)
}
)
}
shinyApp(ui = ui, server = server)
Related
I have an R shiny app that gets a .csv import from a user and searches the imported data across a built-in data frame, then gives the % match in the output. The UI is very simple, with a few different inputs (import .csv, a slider, and some radio buttons). What I want is to be able to take the reactive table output and print this to a .csv that the user can download to their machine. The server side of the app looks something like this:
server <- function(input, output){
rvals <- reactiveValues()
observeEvent(input$file_1,{
req(input$file_1)
rvals$csv <<- read.csv(input$file_1$datapath, header = TRUE)
#some data processing here
})
output$contents <- renderTable({
if(input$select == 1){
x <- function
}else if(input$select == 2){
x <- function
}else if(input$select == 3){x <- function}
#some more data processing and formatting here
return(x)
},digits = 4)
}
I would like to have the data table x be able to become a .csv that can be downloaded by clicking a download button. In the server, I added the following code, but when I try to download the data it just downloads a blank file and says "SERVER ERROR" in my downloads manager on my machine.
output$downloadData <- downloadHandler(
filename = "thename.csv",
content = function(file){
write.csv(x, file)
}
In the console I also get the error message:
Warning: Error in is.data.frame: object 'x' not found [No stack trace available]
The object you create inside the expression of renderTable is not available outside of it. Instead you could assign it to the reactive values you set up. Below is a working example (note that I have tried to replicate your code so the data will not be available until you click on "Upload CSV", which here just calls mtcars).
library(shiny)
ui = fluidPage(
sidebarPanel(
actionButton(inputId = "uploadCsv", label = "Upload CSV:", icon = icon("upload")),
selectInput(inputId = "preProc", label = "Pre-processing", choices = c("Mean"=1,"Sum"=2)),
downloadButton("downloadData", label = "Download table")
),
mainPanel(
h4("My table:"),
tableOutput("contents")
)
)
server <- function(input, output) {
rvals <- reactiveValues(
csv=NULL,
x=NULL
)
observeEvent(input$uploadCsv,{
rvals$csv <- mtcars # using example data since I don't have your .csv
# rvals$csv <- read.csv(input$file_1$datapath, header = TRUE)
#some data processing here
})
output$contents <- renderTable({
# Assuing the below are functions applied to your data
req(
input$preProc,
!is.null(rvals$csv)
)
if(input$preProc == 1){
rvals$x <- data.frame(t(colMeans(mtcars)))
}else {
rvals$x <- data.frame(t(colSums(mtcars)))
}
return(rvals$x)
},digits = 4)
output$downloadData <- downloadHandler(
filename = "myFile.csv",
content = function(file){
write.csv(rvals$x, file)
}
)
}
shinyApp(ui,server)
EventReactive already outputs a reactive value, you don't need to create an extra reactiveVal, see example below :
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Test"),
mainPanel(
actionButton("show", "Download"),
textOutput("result")
)
)
server <- function(input, output) {
csvfile <- eventReactive(req(input$show), ignoreNULL = T, {
"Content of file"
})
output$result <- reactive(
paste("result : ",csvfile()))
}
# Run the application
shinyApp(ui = ui, server = server)
I would also avoid to use <<-operator in a reactive expression.
I have a shiny app which takes a csv file as input and after clicking 'submit' should display a jsoneditOutput. Besides this I have used a reset button which when clicked should reset the file input. But when I click submit I get: Error in read.table: 'file' must be a character string or connection.
library(shiny)
library(shinyjs)
library(tidyverse)
library(listviewer)
library(jsonlite)
library(SACCR)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
useShinyjs(),
fileInput('inFile', 'Choose 1st file'),
actionButton('submit', 'Submit'),
tags$hr(),
actionButton('reset', 'Reset')
),
mainPanel(
jsoneditOutput("choose")
)
)
)
server <- function(input, output, session) {
rv <- reactiveValues(
data = NULL,
clear = FALSE
)
########1st
observe({
req(input$inFile)
req(!rv$clear)
rv$data <- read.csv(input$inFile$datapath,header = T)
})
observeEvent(input$inFile, {
rv$clear <- FALSE
}, priority = 1000)
observeEvent(input$reset, {
rv$data <- NULL
rv$clear <- TRUE
reset('inFile')
}, priority = 1000)
output$choose <- renderJsonedit({input$submit
jsonedit(jsonlite::fromJSON(SACCR::SACCRCalculator(isolate(rv$data), JSON=TRUE)))
})
}
shinyApp(ui, server)
So the issue is with this line:
jsonedit(jsonlite::fromJSON(SACCR::SACCRCalculator(isolate(rv$data), JSON=TRUE)))
The SACCRCalculator function needs a .csv file, not an R dataframe. Try replacing rv$data with input$inFile$datapath.
Also, the SACCRCalculator function requires three files in total; the trades, CSA, and collaterals. So that line will need to be expanded to include all three files. It should end up looking something like:
SACCRCalculator(input$trades$datapath, input$csa$datapath, input$collaterals$datapath, JSON=TRUE)
I have written a code to read csv or excel file in shiny app. But what is happening is that, whatever I select first (say Excel file), the output is displayed. But once I switch to other (csv) the excel is still there and csv is not displayed. Not sure what wrong is there in the code. Could anyone please help me?
library(shinydashboard)
library(readxl)
ui <- dashboardPage(
dashboardHeader(title = "Loading data"),
dashboardSidebar(fileInput("datafile","Choose the csv file",multiple = TRUE,
accept = c("text/csv","text/comma-separated-values,text/plain",".csv")),
("Or"),
fileInput("datafile1","Choose the excel file",multiple = TRUE,
accept = c(".xlsx"))),
dashboardBody(
fluidRow(box(uiOutput("filter_70"),width = 5000))
))
server <- function(input,output){
output$contents <- renderTable({
file_to_read <- input$datafile
if(is.null(file_to_read))
return(NULL)
read.csv(file_to_read$datapath)
})
output$contents1 <- renderTable({
file_to_read1 <- input$datafile1
if(is.null(file_to_read1))
return(NULL)
read_excel(file_to_read1$datapath)
})
output$filter_70 <- renderUI(
if (!is.null(input$datafile)) {
tableOutput("contents")
} else if (!is.null(input$datafile1)) {
tableOutput("contents1")
}
)
}
shinyApp(ui, server)
as pointed out by #Rohit, reactive value once gets selected, it won't go back to null.
Keep your main structure unchanged, all you need is to monitor which file last changed. This requires creating a reactiveVal (last_selected) and then use observeEvent to track.
last_selected <- reactiveVal(NA)
observeEvent(input$datafile, {
last_selected("csv")
})
observeEvent(input$datafile1, {
last_selected("excel")
})
output$filter_70 <- renderUI({
req(last_selected())
if (last_selected()=="csv") {
tableOutput("contents")
} else if (last_selected()=="excel") {
tableOutput("contents1")
}
})
I want to add a download link in each row of a datatable in shiny.
So far I have
server <- function(input, output) {
v<-eventReactive(input$button,{
temp<-data.frame(TBL.name=paste("Data ",1:10))
temp<-cbind(
temp,
#Dynamically create the download and action links
Attachments=sapply(seq(nrow(temp)),function(i){as.character(downloadLink(paste0("downloadData_",i),label = "Download Attachments"))})
)
})
# Table of selected dataset ----
output$table <- renderDataTable({
v()
}, escape = F)}
ui <- fluidPage(
sidebarPanel(
actionButton("button", "eventReactive")
),
mainPanel(
dataTableOutput("table")
)
)
I have the download links in the table for each row. Now I want to add a different file location for each row. For example, each download link will result in a download of a different zip-folder. Can I use downloadHandler for this?
I do not believe you can embed downloadButtons/downloadLinks directly in a datatable. However, you can create hidden downloadLinks that get triggered by links embedded in your table. This produces the same end result. To do so you must:
Dynamically generate downloadLinks/downloadButtons.
Use css to set their visibility to hidden.
Embed normal links/buttons in the table
Set the onClick field of these links to trigger the corresponding hidden downloadLink.
Here is code from an example using the mtcars dataset.
library(tidyverse)
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(HTML("
.hiddenLink {
visibility: hidden;
}
"))
),
dataTableOutput("cars_table"),
uiOutput("hidden_downloads")
)
server <- function(input, output, session) {
data <- mtcars
lapply(1:nrow(data), function(i) {
output[[paste0("downloadData", i)]] <- downloadHandler(
filename = function() {
paste("data-", i, ".csv", sep="")
},
content = function(file) {
write.csv(data, file)
}
)
})
output$hidden_downloads <- renderUI(
lapply(1:nrow(data), function(i) {
downloadLink(paste0("downloadData", i), "download", class = "hiddenLink")
}
)
)
output$cars_table <- renderDataTable({
data %>%
mutate(link = lapply(1:n(),
function(i)
paste0('<a href="#" onClick=document.getElementById("downloadData',i, '").click() >Download</a>')
))
}, escape = F)
}
shinyApp(ui, server)
Since each downloadLink label must correspond to a name in output, I don't think there is a way to create an arbitrary set of downloads using the standard Shiny download* functions.
I solved this using DT and javascript. DT allows javascript to be associated with a datatable. The javascript can then tell Shiny to send a file to the client and the client can force the data to be downloaded.
I created a minimal example gist. Run in RStudio with:
runGist('b77ec1dc0031f2838f9dae08436efd35')
Safari is not supporting .click() anymore since v12.0. Hence, I adapted the hidden link solution from abanker with the dataTable/actionButton described by P Bucher, and the .click() workaround described here. Here is the final code:
library(shiny)
library(shinyjs)
library(DT)
# Random dataset
pName <- paste0("File", c(1:20))
shinyApp(
ui <- fluidPage( useShinyjs(),
DT::dataTableOutput("data"),
uiOutput("hidden_downloads") ),
server <- function(input, output) {
# Two clicks are necessary to make the download button to work
# Workaround: duplicating the first click
# 'fClicks' will track whether click is the first one
fClicks <- reactiveValues()
for(i in seq_len(length(pName)))
fClicks[[paste0("firstClick_",i)]] <- F
# Creating hidden Links
output$hidden_downloads <- renderUI(
lapply(seq_len(length(pName)), function(i) downloadLink(paste0("dButton_",i), label="")))
# Creating Download handlers (one for each button)
lapply(seq_len(length(pName)), function(i) {
output[[paste0("dButton_",i)]] <- downloadHandler(
filename = function() paste0("file_", i, ".csv"),
content = function(file) write.csv(c(1,2), file))
})
# Function to generate the Action buttons (or actionLink)
makeButtons <- function(len) {
inputs <- character(len)
for (i in seq_len(len)) inputs[i] <- as.character(
actionButton(inputId = paste0("aButton_", i),
label = "Download",
onclick = 'Shiny.onInputChange(\"selected_button\", this.id, {priority: \"event\"})'))
inputs
}
# Creating table with Action buttons
df <- reactiveValues(data=data.frame(Name=pName,
Actions=makeButtons(length(pName)),
row.names=seq_len(length(pName))))
output$data <- DT::renderDataTable(df$data, server=F, escape=F, selection='none')
# Triggered by the action button
observeEvent(input$selected_button, {
i <- as.numeric(strsplit(input$selected_button, "_")[[1]][2])
shinyjs::runjs(paste0("document.getElementById('aButton_",i,"').addEventListener('click',function(){",
"setTimeout(function(){document.getElementById('dButton_",i,"').click();},0)});"))
# Duplicating the first click
if(!fClicks[[paste0("firstClick_",i)]])
{
click(paste0('aButton_', i))
fClicks[[paste0("firstClick_",i)]] <- T
}
})
}
)
I experienced an unexpected behavior of my R shiny code and I'm asking myself if this is bug or if I don't understand how req() works.
I have an app where the user first uploads a *.txt file containing data with a location id, a date, and other data. Then the user has to choose two numerical values. The data is checked for NAs in the col date. If there are no NAs a text should appear telling the user everything is fine.
Below are two versions of my code. In output$txt <- renderText({ I use req() to test if all inputs are set and the file is loaded.
The differences between the two codes are the ordering of the last two arguments in req. Whereas in the first version, the green text appears already when both numeric inputs are set even the file is not uploaded, the second code behaves as expected; the user has to choose the numeric value and has to choose a file before the green bar with text appears.
Why makes the ordering of the arguments in req() a difference?
Code 1:
library(shiny)
library(lubridate)
# UI
ui <- fluidPage(
fluidRow(
column(4,
wellPanel(
fileInput("file1", label = h4("file upload")),
numericInput("in1", label = h4("first input"),value = 2.5, step=0.5),
numericInput("in2", label = h4("second input"),value = NULL, step=0.5)
)
),
column(8,
h4(textOutput("txt"), tags$style(type="text/css", "#txt {vertical-align:top;align:center;color:#000000;background-color: #4cdc4c;}")),
h2(""),
textOutput("fileinfo"),
tableOutput("tab_data")
)
)
)
# SERVER
server <- function(input, output) {
output$txt <- renderText({
req(input$in1, input$in2, fl_data(), input$file1)
"your data is ok and you have chosen input 1 and 2"
})
fl_data <- reactive({
validate(
need(input$file1 != "", "upload data and choose input 1 and 2...")
)
inFile <- input$file1
if (is.null(inFile)) {
return(NULL)
} else {
dd <- read.table(inFile$datapath, sep=";", stringsAsFactors=FALSE, header=TRUE)
dd[,2] <- ymd(dd[,2])
if (sum(is.na(dd[,2]))>0) dd <- NULL
}
})
output$tab_data <- renderTable({head(fl_data()[,1:4])})
output$fileinfo <- renderPrint({input$file1})
}
# Run the application
shinyApp(ui = ui, server = server)
Code 2:
library(shiny)
library(lubridate)
# UI
ui <- fluidPage(
fluidRow(
column(4,
wellPanel(
fileInput("file1", label = h4("file upload")),
numericInput("in1", label = h4("first input"),value = 2.5, step=0.5),
numericInput("in2", label = h4("second input"),value = NULL, step=0.5)
)
),
column(8,
h4(textOutput("txt"), tags$style(type="text/css", "#txt {vertical-align:top;align:center;color:#000000;background-color: #4cdc4c;}")),
h2(""),
textOutput("fileinfo"),
tableOutput("tab_data")
)
)
)
# SERVER
server <- function(input, output) {
output$txt <- renderText({
req(input$in1, input$in2, input$file1, fl_data())
"your data is ok and you have chosen input 1 and 2"
})
fl_data <- reactive({
validate(
need(input$file1 != "", "upload data and choose input 1 and 2...")
)
inFile <- input$file1
if (is.null(inFile)) {
return(NULL)
} else {
dd <- read.table(inFile$datapath, sep=";", stringsAsFactors=FALSE, header=TRUE)
dd[,2] <- ymd(dd[,2])
if (sum(is.na(dd[,2]))>0) dd <- NULL
}
})
output$tab_data <- renderTable({head(fl_data()[,1:4])})
output$fileinfo <- renderPrint({input$file1})
}
# Run the application
shinyApp(ui = ui, server = server)
req short-circuits, just like the && and || operators. As soon as it comes across an unavailable value (args evaluated left-to-right), it stops the reactive chain and doesn't care about any further args.
In the second example, input$file1 prevents fl_data() from ever executing if missing, so the validation in fl_data never occurs. But rather than order the req args like in the first example, I would just remove the check for input$file1 in output$txt, as it's already being checked in fl_data.