I'm new to Shiny (worked with R for a few months) and i'm trying to figure out what a 'Shiny' way of handling multiple uploaded files is.
My goal is to have an interface where the user can upload multiple files. From these files a report must be generated, in the form of a large number of graphs and plots. From what I learned in the Shiny tutorials, on the server side all objects are isolated from eachother (meaning they cant be read, unless you call another function explicitly).
As you can see in my code below, this means that I have to duplicate the processing code for every single plot. This seems inefficient. What would be the 'Shiny' way of handling this?
Also, i left out a bunch of code that is not absolutely necessary for the example. In essence, I need to do a lot more processing and i dont want to duplicate all that code for every plot.
I am specifically asking about the server-side code. I want to read in multiple files that contain different contents. The operations in the server-side code are just place-holders, I don't actually want to cbind anything but I put that there to keep this code simple. I want to be able to do whatever I want to the imported data frames.
library(shiny)
# Define UI for application
ui <- fluidPage(
# Sidebar with file input
sidebarLayout(
sidebarPanel(
fileInput("people", NULL, multiple = FALSE, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file"),
fileInput("info", NULL, multiple = FALSE, accept = ".csv",
buttonLabel = "Browse...", placeholder = "info file"),
),
# Show the results of the data processing
mainPanel(
imageOutput("plot"),
tableOutput("base_data")
)
)
)
# Define server logic required to process the data
server <- function(input, output) {
output$base_data <- renderTable({
if(is.null(input$people) | is.null(input$info)) {
} else {
people_file <- input$people
info_file <- input$info
people <- read.csv(people_file$datapath, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(info_file$datapath, stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
rbind(people, info)
}
})
output$plot <- renderImage({
if(is.null(input$people) | is.null(input$info)) {
outfile <- tempfile(fileext='.png')
png(outfile, width = 1200, height = 800, res = 200)
dev.off()
list(src = outfile, width = 1200, height = 800)
} else {
people_file <- input$people
info_file <- input$info
people <- read.csv(people_file$datapath, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(info_file$datapath, stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
outfile <- tempfile(fileext='.png')
png(outfile, width = 1200, height = 800, res = 200)
plot(nrow(people), nrow(info), type="b")
dev.off()
list(src = outfile, width = 1200, height = 800, alt = "questions"))
}
}, deleteFile = TRUE)
}
# Run the application
shinyApp(ui = ui, server = server)
Here's an example of what i want in pseudocode:
[[ui]]
fileInput("people")
fileInput("info")
show(plot)
show(plot2)
show(df)
[[serverside]]
files <- source(input) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
}
contents <- plot(output) {
some_function(files$people, files$info)
plot(contents)
}
contents2 <- plot(output) {
some_other_function(files$people, files$info)
plot2 <- plot(contents2)
}
df <- table(output) {
cbind(files$people, files$info)
}
This is in pseudocode what i have now, which is not efficient.
[[ui]]
fileInput("people")
fileInput("info")
show(plot)
show(plot2)
show(df)
[[serverside]]
contents <- plot(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
contents <- some_function(people, info)
plot(contents)
}
contents2 <- plot(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
contents <- some_other_function(people,info)
plot(contents)
}
df <- table(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
cbind(people, info)
}
I will have to agree with heds1 thats a bit difficult to get your desired result. Since we dont have access to your csvs i created some dummy ones.
Reproducible data / csvs:
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
write.csv2(x = 6:10, file = "people2.csv", row.names = FALSE)
If i understand you correctly you would like to avoid repeating the code for every uploaded file.
In order to loop over your files/ datasets we will have to collect them in one data structure.
One way of doing so would be to allow upload multiple files:
fileInput(..., multiple = TRUE)
Ui Side:
The ui side you could create with a loop in renderUI():
output$plots <- renderUI({
lapply(paste("people", 1:length(data)), plotOutput)
})
Server side:
The server side you can create with a loop over:
output[[paste("people", nr)]] <- renderPlot({
plot(plotData)
})
Local assignment
Finally you will have to use local() to avoid that only the data of the last iteration of the loop is taken:
local({
LOCAL_VARIABLE <- data[[nr]]
....
})
Full reproducible example:
library(shiny)
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
write.csv2(x = 6:10, file = "people2.csv", row.names = FALSE)
ui <- fluidPage(
fileInput(inputId = "people", label = NULL, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file", multiple = TRUE),
uiOutput("plots")
)
server <- function(input, output, session) {
observeEvent(input$people, {
data <- lapply(input$people$datapath, read.csv2)
for(nr in 1:length(data)){
local({
plotData <- data[[nr]]
output[[paste("people", nr)]] <- renderPlot({
plot(plotData)
})
})
}
output$plots <- renderUI({
lapply(paste("people", 1:length(data)), plotOutput)
})
})
}
shinyApp(ui, server)
Edit:
Reuse the imported (and transformed) data:
library(shiny)
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
ui <- fluidPage(
fileInput(inputId = "people", label = NULL, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file", multiple = FALSE),
plotOutput("plot"),
tableOutput("table"),
verbatimTextOutput("text")
)
server <- function(input, output, session) {
global <- reactiveValues()
observeEvent(input$people, {
data <- read.csv2(input$people$datapath)
# DO LOTS OF OPERATIONS ON data
global$data <- data
# FROM HERE ON USE: global$data
})
output$plot <- renderPlot({
req(global$data)
plot(global$data)
})
output$table <- renderTable({
global$data
})
output$text <- renderText({
toString(global$data)
})
}
shinyApp(ui, server)
Related
I'm trying to implement a shiny app that contains some optional checkboxes. I would like to know how do I perform an analysis with a certain selection only if it is selected and, with that, the table with the analysis made from the selection also appears on the screen.
I would like the objects inside the rbind function (below) to be included only if they are selected in the checkboxes:
ameacadas <- rbind(ameacadas_BR,ameacadas_BR2, ameacadas_pa)
External files can be found at: https://github.com/igorcobelo/data_examples (The 'minati.csv' file is the input data).
My code is presented below:
# global
library(shiny)
library(tidyverse)
# ui
ui <- navbarPage(title = "Minati Flora.",
tabPanel(title = "Home",
br(),
hr(),
# Upload csv file
sidebarLayout(
sidebarPanel(
fileInput(
inputId = "csvFile",
label = "Upload",
accept = c(".csv")
),
checkboxInput('BR1','Federal1'),
checkboxInput('BR2','Federal2'),
checkboxInput('PA','ParĂ¡'),
downloadButton("download", "Download")
),
mainPanel(
tableOutput("modifiedData")
)
)
),
tabPanel(title = "About"),
inverse = T)
# server
server <- function(input, output) {
rawData <- eventReactive(input$csvFile, {
req(input$csvFile)
df <- read.csv(input$csvFile$datapath,sep=';',check.names = F,fileEncoding = "Latin1")
#read extern files
ameacadas_BR <- read.csv("ameacadas_BR.csv",sep=';',check.names = F,fileEncoding = "Latin1")
legis_BR <- "Portaria MMA N. 148/2022"
ameacadas_BR2 <- read.csv("ameacadas_BR2.csv",sep=';',check.names = F,fileEncoding = "Latin1")
legis_BR2 <- "Decreto Federal N. 5.975/2006"
ameacadas_pa <- read.csv("ameacadas_PA.csv",sep=';',check.names = F,fileEncoding = "Latin1")
legis_pa <- "Resolucao COEMA/PA N. 54/2007"
#Rbind all files selected
ameacadas <- rbind(ameacadas_BR,ameacadas_BR2, ameacadas_pa)
#General calculate
colnames(df)[1] <- "Especie" #coluna especies
ameacadas <- ameacadas %>%
group_by(Especie) %>%
mutate(Categoria_Ameaca = toString(Categoria_Ameaca),
Legislacao = toString(Legislacao))
ameacadas <- ameacadas[!duplicated(ameacadas[,1]),]
arv_com_ameacadas <- df %>% left_join(ameacadas, by = "Especie")
})
output$modifiedData <- renderTable({rawData() })
output$download <- downloadHandler(
filename = function() {paste("Minati_Flora_", Sys.Date(), ".csv", sep = "")},
content = function(file){
write.csv(rawData(), file, row.names = FALSE)
}
)
}
# Run the application
shinyApp(ui = ui, server = server)
The below reproducible code allows the user to select either a data table or a plot of the data for viewing (via input$view). I'm trying to create a conditional around the downloadHandler() so that if the user is viewing the data table and chooses to download, then the data is downloaded; otherwise if the user is viewing the plot and chooses to download then a plot in PNG format is downloaded. I'm running into issues around input$view reactivity. How would I modify the code below to conditionally download whichever (data or plot) the user is viewing?
The code as posted below works for viewing either data or plot, but only allows the data table to be downloaded. Offending lines of code that otherwise cause a crash are commented out.
Reproducible code:
library(shiny)
library(ggplot2)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('viewData','viewPlot'),
selected = 'viewData',
inline = TRUE
),
conditionalPanel("input.view == 'viewData'",tableOutput("DF")),
conditionalPanel("input.view == 'viewPlot'",plotOutput("plotDF")),
downloadButton("download","Download",style = "width:20%;")
)
server <- function(input, output, session) {
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
data1 <- reactiveValues()
inputView <- reactive(input$view) # attempt to make input$view reactive
observeEvent(input$view,{data1$plot <- ggplot(data, aes(Period,Value)) + geom_line()})
output$DF <- renderTable(data)
output$plotDF <- renderPlot(data1$plot)
output$download <-
# if(inputView() == 'viewData'){
downloadHandler(
filename = function()
paste("dataDownload","csv",sep="."),
content = function(file){
write.table(
data,
na = "",
file,
sep = ",",
col.names = TRUE,
row.names = FALSE)
}
)
# }
# else{
# downloadHandler(
# filename = function(){paste("plotDownload",'.png',sep='')},
# content = function(file){
# ggsave(file,plot=data1$plot)
# }
# )
# }
}
shinyApp(ui, server)
Try this
library(shiny)
library(ggplot2)
ui <- fluidPage(
radioButtons("view",
label = "View data or plot",
choiceNames = c('Data','Plot'),
choiceValues = c('viewData','viewPlot'),
selected = 'viewData',
inline = TRUE
),
conditionalPanel("input.view == 'viewData'",tableOutput("DF")),
conditionalPanel("input.view == 'viewPlot'",plotOutput("plotDF")),
#downloadButton("download","Download",style = "width:20%;")
uiOutput("plotrtable")
)
server <- function(input, output, session) {
data <- data.frame(Period = c(1,2,3,4,5,6),Value = c(10,20,15,40,35,30))
data1 <- reactiveValues()
inputView <- reactive(input$view) # attempt to make input$view reactive
observeEvent(input$view,{data1$plot <- ggplot(data, aes(Period,Value)) + geom_line()})
output$DF <- renderTable(data)
output$plotDF <- renderPlot(data1$plot)
output$plotrtable <- renderUI({
if(input$view == 'viewData'){downloadButton("download","Download",style = "width:20%;") }
else {downloadButton("downloadp","Download",style = "width:20%;") }
})
output$download <- downloadHandler(
filename = function()
paste("dataDownload","csv",sep="."),
content = function(file){
write.table(
data,
na = "",
file,
sep = ",",
col.names = TRUE,
row.names = FALSE)
}
)
output$downloadp <- downloadHandler(
filename = function(){paste("plotDownload",'.png',sep='')},
content = function(file){
ggsave(file,plot=data1$plot)
}
)
}
shinyApp(ui, server)
I use Timevis package.
first of all I read an excel file with missions.
In my code the user can see all the missions on a time line, and he can edit/add/remove any missions.
after the user make a change I can see the update table below.
I want to save to my excel file every update that the user make.
this is my code:
library(shiny)
library(timevis)
library(readxl)
my_df <- read_excel("x.xlsx")
data <- data.frame(
id = my_df$id,
start = my_df$start,
end = my_df$end,
content = my_df$content
)
ui <- fluidPage(
timevisOutput("appts"),
tableOutput("table")
)
server <- function(input, output) {
output$appts <- renderTimevis(
timevis(
data,
options = list(editable = TRUE, multiselect = TRUE, align = "center")
)
)
output$table <- renderTable(
input$appts_data
)
}
shinyApp(ui, server)
You can use actionButton/ observe to call saveworkbook (package openxlsx) to save your changes. Technically you are not saving these changes, but replacing the file with an identical file containing the changes.
library(shiny)
library(openxlsx)
library(timevis)
library(readxl)
my_df <- read_excel("x.xlsx")
data <- data.frame(
id = my_df$id,
start = my_df$start,
end = my_df$end,
content = my_df$content
)
mypath = paste0(getwd(), "/x.xlsx") # Path to x.xlsx
ui <- fluidPage(
timevisOutput("appts"),
tableOutput("table"),
actionButton("save", "Save")
)
server <- function(input, output) {
output$appts <- renderTimevis(
timevis(
data,
options = list(editable = TRUE, multiselect = TRUE, align = "center")
))
observeEvent(input$save,
{
my_df<- createWorkbook()
addWorksheet(
my_df,
sheetName = "data"
)
writeData(
wb = my_df,
sheet = "data",
x = input$appts_data,
startRow = 1,
startCol = 1
)
saveWorkbook(my_df, file = mypath,
overwrite = TRUE)
})
output$table <- renderTable(
input$appts_data
)
}
shinyApp(ui, server)
I am unable to print the entire output of bucket list inputs (verbatimTextOutput("bucket_outputs")). See:
library(shiny)
library(sortable)
example_Table = cbind(c(1,2,3,4),c("a","b","c","d"))
ui <- fluidPage(
uiOutput("Dyanmic_Bucket"),
uiOutput("Dyanmic_Bucket_2"),
###The following output is incomplete
verbatimTextOutput("bucket_outputs")
)
server <- function(input, output, session) {
rank_list_items <- lapply(seq(nrow(example_Table)), function(x) {
add_rank_list(
text = example_Table[x,1],
labels = example_Table[x,2]
)
})
output$Dyanmic_Bucket <- renderUI({
do.call("bucket_list", args = c(
list(header = "",
group_name = "Dyanmic_Bucket",
orientation = "horizontal"),
rank_list_items
))
})
rank_list_items_2 <- lapply(seq(nrow(example_Table)), function(x) {
add_rank_list(
text = example_Table[x,1],
labels = example_Table[x,2]
)
})
output$Dyanmic_Bucket_2 <- renderUI({
do.call("bucket_list", args = c(
list(header = "",
group_name = "Dyanmic_Bucket",
orientation = "horizontal"),
rank_list_items_2
))
})
output$bucket_outputs = renderPrint(input$Dyanmic_Bucket)
}
shinyApp(ui, server)
Initially, it only prints the output of Dyanmic_Bucket_2, but even this is inconsistent upon re-ordering, or doubling up some of the options.
Renaming the group name of each bucket will not work, as I would then no longer be able to drag and drop across the different rows.
I would like to see a printed output of both dynamically created buckets. Help much appreciated.
I am developing a shiny application which save the data entered on the user interface. I have refered the url on shiny rstudio page so by using this page, the code i have written is as mentioned below:
outputDir <- "C:\\Users/dell/Desktop/"
saveData <- function(data) {
data <- t(data)
fileName <- sprintf("%s_%s.csv", as.integer(Sys.time()), digest::digest(data))
write.csv(
x = data, sep = ",",
file = file.path(outputDir, fileName),
row.names = FALSE, quote = TRUE
)
}
loadData <- function() {
files <- list.files(outputDir, full.names = TRUE)
data <- lapply(files, read.csv, stringsAsFactors = FALSE)
data <- do.call(rbind, data)
data
}
library(shiny)
fields <- c("name", "staff_name")
shinyApp(
ui = fluidPage(
titlePanel("attendance System"),
DT::dataTableOutput("responses", width = 300), tags$hr(),
textInput("name", "Accession Number", ""),
selectInput("staff_name", "Staff Name",
c("Rajiv" = "RT",
"Arvind " = "AKS",
"Ashutosh " = "AS")),
actionButton("submit", "Submit")
),
server = function(input, output, session) {
formData <- reactive({
data <- sapply(fields, function(x) input[[x]])
data
})
observeEvent(input$submit, {
saveData(formData())
})
output$responses <- DT::renderDataTable({
input$submit
loadData()
})
}
)
The above code create a new file for each entry. I am looking for a single file in which all entry to be added.
This will give you a unique file name based on time of save and content of the file:
fileName <- sprintf("%s_%s.csv", as.integer(Sys.time()), digest::digest(data))
You can give it a single name like:
fileName <- 'input_bu.csv'
Like #ismirsehregal, I'd recommend bookmarking for this though.
after looking various solutions. I reached at below code to save the data in a single file as it is entered.
library(shiny)
outputDir <- "C:\\Users/dell/Desktop/"
saveData <- function(data) {
data <- as.data.frame(t(data))
if (exists("responsesiq")) {
responsesiq <<- rbind(responsesiq, data)
} else {
responsesiq <<- data
}
fileName <- "test_igntu.csv"
write.csv(
x = responsesiq, sep = ",",
file = file.path(outputDir, fileName),
row.names = FALSE, quote = TRUE
)
}
fields <- c("acc", "staff_name")
shinyApp(
ui = fluidPage(
titlePanel("Attendance System"),
DT::dataTableOutput("responsesiq", width = 300), tags$hr(),
numericInput("acc", "AccNumber", ""),
selectInput("staff_name", "Staff Name",
c("Rajiv" = "RT",
"Arvind" = "AKS",
"Ashutosh" = "AS")),
actionButton("submit", "Submit")
),
server = function(input, output, session) {
# Whenever a field is filled, aggregate all form data
formData <- reactive({
data <- sapply(fields, function(x) input[[x]])
data
})
# When the Submit button is clicked, save the form data
observeEvent(input$submit, {
saveData(formData())
})
}
)