dependencies in shiny objects - r

In my app, the user needs to pick a folder, and in that folder he needs to pick a file (the suffix of the file name is '.seg')
This code is working-
library(shiny)
ui <- shinyUI(fluidPage(
# select a folder
column(2, absolutePanel(fixed = TRUE, width = '180px',
selectInput("pick_a_folder", label = '', selected='choose a folder',
choices = setNames(as.list(c('choose a folder',
basename(list.dirs(recursive = FALSE)))),
c('choose a folder',
basename(list.dirs(recursive = FALSE))))))),
# select a file
column(2, absolutePanel(fixed = TRUE, width = '180px',
conditionalPanel(condition='!(input.pick_a_folder=="choose a folder")',
uiOutput('fileselection'))))
))
server <- shinyServer(function(input, output) {
# dinamic file selection. find the files list after folder is choosen
output$fileselection <- renderUI({
selectInput('pick_file', '', selected = 'choose a file',
choices=setNames(as.list(c('choose a file',basename(list.files(path=input$pick_a_folder,recursive=FALSE, pattern='\\.seg$')))),
c('choose a file',basename(list.files(path = input$pick_a_folder, recursive = FALSE, pattern='\\.seg$')))))
})
})
shinyApp(ui = ui, server = server)
The issue is that if I add a folder to the working directory after I ran the code, it will not appear.
So I tried to move the folder selection to the server, and make it dependent on a refresh button, but I get an error
Error in list.files: invalid 'path' argument
this is my code-
library(shiny)
ui <- shinyUI(fluidPage(
# refresh butten for root directory
column(1, absolutePanel(fixed=TRUE, actionButton("refresh_wd", "refresh"))),
# select a folder
column(2, absolutePanel(fixed = TRUE, width = '180px', uiOutput('folderselection'))),
# select a file
column(2, absolutePanel(fixed = TRUE, width = '180px',
conditionalPanel(condition='!(input.pick_a_folder=="choose a folder")',
uiOutput('fileselection'))))
))
server <- shinyServer(function(input, output) {
# refresh root directory
wd_folders <- eventReactive(input$refresh_wd, {
basename(list.dirs(recursive = FALSE))
})
output$folderselection <- renderUI({
selectInput('pick_a_folder', '', selected = 'choose a folder',
choices = setNames(as.list(c('choose a folder', wd_folders())),
c('choose a folder', wd_folders())))
})
# dinamic file selection. find the file list after folder is choosen
output$fileselection <- renderUI({
selectInput('pick_a_file', '', selected = 'choose a file',
choices=setNames(as.list(c('choose a file',basename(list.files(path=input$pick_a_folder,recursive=FALSE, pattern='\\.seg$')))),
c('choose a file',basename(list.files(path = input$pick_a_folder, recursive = FALSE, pattern='\\.seg$')))))
})
})
shinyApp(ui = ui, server = server)
Any help would be appreciated

Because you use eventReactive() the list of folders and even the folder selection will only be displayed after somebody clicked on the 'Refresh' button. You can avoid this by using ignoreNULL = FALSE :
wd_folders <- eventReactive(input$refresh_wd, {
basename(list.dirs(recursive = FALSE))
}, ignoreNULL = FALSE)
If you don't do this, the value of wd_folders() will be NULL to start with, so your condition for your conditionalPanel is fulfilled (it's not "select a folder") and hence your app tries to read the files in directory NULL. This gives you your error.
If you want to be extra safe, you can add validate(need()) to the UI rendering as well, eg:
output$fileselection <- renderUI({
validate(need(input$pick_a_folder, label = "Pick a folder first"))
validate(need(dir.exists(input$pick_a_folder),
label = "Something went wrong. Contact me."))
selectInput('pick_a_file', '', selected = 'choose a file',
...)
})
This isn't necessary to fix your problem, but I find it good practice in Shiny.

Here is a minimal example that auto refresh the folders every 5 seconds.
It does still produce an initial warning about path being invalid for the reasons #JoriMeys explained.
library(shiny)
ui <- shinyUI(fluidPage(
column(1,
absolutePanel(fixed=TRUE,
textOutput('wd'),
uiOutput('folderselection'),
conditionalPanel(
condition='!(input.pick_a_folder=="choose a folder")',
uiOutput('fileselection'))
)
)
)
)
server <- shinyServer(function(input, output) {
output$wd <- renderText(basename(
list.files(path = input$pick_a_folder,
recursive=FALSE)
)
)
button <- reactiveTimer(intervalMs = 5000)
# refresh root directory
wd_folders <- reactive({
button()
basename(list.dirs(recursive = FALSE))
})
output$folderselection <- renderUI({
selectInput('pick_a_folder', '',
choices = c('choose a folder', wd_folders()
)
)
})
# dinamic file selection. find the file list after folder is choosen
output$fileselection <- renderUI({
selectInput('pick_a_file', '',
selected = 'choose a file',
choices=c('choose a file',
basename(list.files(path = input$pick_a_folder,recursive=FALSE))))
})
})
shinyApp(ui = ui, server = server)

Related

(In R shiny) About upload multiple csv files and print all tables out

I want to upload two csv files and print both tables out.
Here is the code I wrote:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput(inputId = "files", label = "Choose CSV File", multiple = TRUE,accept = c(".csv")
)
),
mainPanel(
fluidRow(tableOutput("Policy1")),
fluidRow(tableOutput("Policy2")),
)
)
)
server <- function(input, output) {
data <- reactiveValues(file1 = NULL,
file2 = NULL)
output$Policy1 <- renderTable({
if(!is.null(input$files$datapath[1]))
data$file1 <- read.csv(input$files$datapath[1], header = TRUE)
data$file1
})
output$Policy2 <- renderTable({
if(is.null(input$files$datapath[2])) {return(1)}
else{return(NULL)}
})
}
shinyApp(ui, server)
and for the output$Policy2 part, I want to test when the is.null(input$files$datapath[2]) is true. I thought it should be true when I only upload one file or don't upload anything but
if I only upload one csv file, it didn't print out the table 1, which means is.null(input$files$datapath[2]) is false in this case.I don't know why this is the case.
And as a result, if I change the code to ask shiny print two tables for me and only upload one file, there will be an error, here is the code:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput(inputId = "files", label = "Choose CSV File", multiple = TRUE,accept = c(".csv")
)
),
mainPanel(
fluidRow(tableOutput("Policy1")),
fluidRow(tableOutput("Policy2")),
)
)
)
server <- function(input, output) {
data <- reactiveValues(file1 = NULL,
file2 = NULL)
output$Policy1 <- renderTable({
if(!is.null(input$files$datapath[1]))
data$file1 <- read.csv(input$files$datapath[1], header = TRUE)
data$file1
})
output$Policy2 <- renderTable({
if(!is.null(input$files$datapath[2]))
data$file2 <- read.csv(input$files$datapath[2], header = TRUE)
data$file2
})
}
shinyApp(ui, server)
where I only change a little part and here is the error :
which I assume is because I should return NULL when only one file inputed in, how can I fix this problem, thanks for any help
The value won't be NULL if it's missing. It's better to check that there are enough values checking the length of the vector or something. For example
output$Policy2 <- renderTable({
if(!is.null(input$files) && length(input$files$datapath)>=2)
data$file2 <- read.csv(input$files$datapath[2], header = TRUE)
data$file2
})

changing variables of a separate R script in a shiny app

I have a set of scripts which are run from below, with aspects of the final output influenced by lines 2-4
setwd()
inputyear = ""
inputmonth = ""
dataType = ""
source("1.R")
source("2.R")
source("3.R")
source("4.R")
source("5.R")
#input required file name
saveWorkbook(wb, "Workbook.xlsx", overwrite = TRUE)
I'd like to be able to change the input year, input month, dataType and the name of the workbook produced by the source() 1-5, from a shiny app, and then run the respective files and generate the excel file.
So far I have the following code, which does not produce any errors, but does not function as desired.
I have only included the 'server' section of the code to save space, and this is the part I need help with if possible;
ui<-shinyUI(fluidPage(theme = shinytheme("flatly"),
tags$head(
tags$style(HTML(
".shiny-output-error-validation {
color; green;
}
"))
),
basicPage(
headerPanel("Workbook"),
sidebarPanel(
selectInput("inputmonth","Select Publication Month",c("JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC")),
selectInput("inputyear","Select Year",c("2018/19","2019/20","2020/21")),
selectInput("dataType","Select Version",c("provisional","final"))),
textInput("WorkBookName","Enter File Name (include .xlsx)"),
actionButton("Generate", "Generate Workbook"))
))
server <- function(input, output, session){
observeEvent(input$Generate, {
validate(need(input$WorkBookName != "", "Please enter file name"))
req(input$inputmonth, input$inputyear, input$dataType, input$WorkBookName)
inputyear = input$inputmonth
inputmonth = input$inputyear
dataType = input$dataType
source("1.R",local = TRUE)
source("2.R", local = TRUE)
source("3.R", local = TRUE)
source("4.R", local = TRUE)
source("5.R", local = TRUE)
saveWorkbook(wb, paste0(input$WorkBookName, ".xlsx"), overwrite = TRUE)
})
}
shinyApp(ui, server)
How can I alter the server script to get the desired functionality?
edit: Full script added, sourced names removed
You'll somehow need to trigger the execution of your reactive code. Reactive code only is executed if it was invalidated. Please see this for further information.
In the following app the code will be executed once the Save Workbook button is clicked.
I don't know your UI and sourced R-scripts, so you might want to replace here accordingly:
library(shiny)
library(openxlsx)
library(shinythemes)
ui <- shinyUI(fluidPage(
theme = shinytheme("flatly"),
tags$head(tags$style(
HTML(".shiny-output-error-validation {
color; green;
}
")
)),
basicPage(
headerPanel("Workbook"),
sidebarPanel(
selectInput(
"inputmonth",
"Select Publication Month",
toupper(month.abb)
),
selectInput("inputyear", "Select Year", c("2018/19", "2019/20", "2020/21")),
selectInput("dataType", "Select Version", c("provisional", "final"))
),
textInput("WorkBookName", "Enter File Name (include .xlsx)"),
actionButton("Generate", "Generate Workbook"),
uiOutput("test")
)
))
server <- function(input, output, session) {
observeEvent(input$Generate, {
req(input$inputmonth,
input$inputyear,
input$dataType,
input$WorkBookName)
inputyear = input$inputmonth
inputmonth = input$inputyear
dataType = input$dataType
# source("1.R", local = TRUE)
# source("2.R", local = TRUE)
# source("3.R", local = TRUE)
# source("4.R", local = TRUE)
# source("5.R", local = TRUE)
#
# saveWorkbook(wb, paste0(input$WorkBookName, ".xlsx"), overwrite = TRUE)
output$test <- renderUI("Everything fine...")
})
}
shinyApp(ui, server)

Turning Data Conflict Error (409) into a message that clients can understand

I'm relatively new to using R and shiny. Currently, I'm getting the Error: Conflict (HTTP 409) when trying to access an html file from dropbox and this is fine, I know the reason. What I do have a problem with is trying to find a way to change Error code message.
I've tried a couple forms of validation and try-catches.
library(shiny)
library(rdrop2)
library(httr)
ui <- # Define UI for dataset viewer application
shinyUI(pageWithSidebar(
headerPanel("Test DropBox html Docs to Shiny"),
sidebarPanel(
selectInput("Cat", "Choose a Category:",
choices = c("A", "B", "C")),
selectInput("Year", "Choose a Year:",
choices = c("2012", "2011")),
downloadButton("downFile", "Download File"),
width = 2),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Html Pages", htmlOutput("viewReport"))),
width = 10)
)
)
#IMPORTANT: The two lines below needs to be run just one time unless the token is deleted
# Create Token
token <- drop_auth()
# Save token
saveRDS(token, "droptoken.rds")
token <- readRDS("droptoken.rds")
server <- shinyServer(function(input, output) {
# ---------------------------------------------------
filePutReport <- reactive(
paste(input$Cat, "_", input$Year, "_Doc.html", sep = "")
)
filePutReport2 <- reactive({
# Search if the file exists in DropBox
drop_download(path = paste("shiny_docs/shinydbtest/", filePutReport(), sep = ""),
overwrite = TRUE, local_path = "./www",
dtoken = token)
filePutReport()
})
# Show Html Pages
output$viewReport <- renderUI({
tags$iframe(seamless = "seamless", width = "1400", height = "1000",
src = filePutReport2()
)
})
###
output$downFile <- downloadHandler(
# generate bins based on input$bins from ui.R
filename = function() {
paste0(filePutReport() )
},
content = function(file){
file.copy(from = paste0("./www/", filePutReport2() ), to = file, overwrite = TRUE)
}
)
})
shinyApp(ui = ui, server = server)
Instead of simply "Error: Conflict (HTTP 409)", I would a message a client might be able to understand. Any and all suggestions are welcome. Thank you in advance for your help.
In my current environment I cannot establish a connection to dropbox, but please try the approach below. I first deleted the last line refering to filePutReport() in your filePutReport2() reactive, since they are the same and you want your call to drop_download to produce either a value (TRUE) or an invisible object of class "try-error". Therefore, you need to further wrap your call to drop_download in a try statement. This way filePutReport2() either contains the value TRUE or an invisible object of class "try-error". Then you should be able to use a need/validate function in your renderUI statement including a custom error message. I hope it's working, since I can't test it.
library(shiny)
library(rdrop2)
library(httr)
ui <- # Define UI for dataset viewer application
shinyUI(pageWithSidebar(
headerPanel("Test DropBox html Docs to Shiny"),
sidebarPanel(
selectInput("Cat", "Choose a Category:",
choices = c("A", "B", "C")),
selectInput("Year", "Choose a Year:",
choices = c("2012", "2011")),
downloadButton("downFile", "Download File"),
width = 2),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Html Pages", htmlOutput("viewReport"))),
width = 10)
)
)
#IMPORTANT: The two lines below needs to be run just one time unless the token is deleted
# Create Token
token <- drop_auth()
# Save token
saveRDS(token, "droptoken.rds")
token <- readRDS("droptoken.rds")
server <- shinyServer(function(input, output) {
# ---------------------------------------------------
filePutReport <- reactive(
paste(input$Cat, "_", input$Year, "_Doc.html", sep = "")
)
filePutReport2 <- reactive({
# Search if the file exists in DropBox
try({
drop_download(path = paste("shiny_docs/shinydbtest/", filePutReport(), sep = ""),
overwrite = TRUE, local_path = "./www",
dtoken = token)
}, silent = TRUE)
})
# Show Html Pages
output$viewReport <- renderUI({
validate(
need(filePutReport2(), 'Custom error message!'))
tags$iframe(seamless = "seamless", width = "1400", height = "1000",
src = filePutReport()
)
})
###
output$downFile <- downloadHandler(
# generate bins based on input$bins from ui.R
filename = function() {
paste0(filePutReport() )
},
content = function(file){
file.copy(from = paste0("./www/", filePutReport2() ), to = file, overwrite = TRUE)
}
)
})
shinyApp(ui = ui, server = server)

R SHINY: Clear/ update mainPanel depending on selectInput/numericInput choice

I'm pretty new to shiny (being playing around for about a week). And I'm trying to create an app that takes and input tab-separated text file and perform several exploratory functions. In this case I'm presenting a very simplified version of that app just to highlight what I want to do in a specific case:
Problem:
If you try the app with the sample data (or any data in the same format) you can notice that the app effectively performs the default summary table (if selectInput="summarize", then output$sumfile), but when you try to select "explore", the previous table gets removed from the mainPanel, and outputs the full file (selectInput="explore",then output$gridfile) in the place where it would be as if selectInput="summarize" was still selected.
If you re-select "summarize", excelOutput("sumfile") gets duplicated on the mainPanel.
My goal is simple:
excelOutput("sumfile") when selectInput="summarize" ONLY and
excelOutput("gridfile") when selectInput="explore" ONLY
without placement issues or duplications on the mainPanel
So far I've tried:
inFile=input$df
if(is.null(inFile))
return(NULL)
if(input$show=="summarize")
return(NULL)
or
inFile=input$df
if(is.null(inFile))
return(NULL)
if(input$show=="explore")
return(NULL)
To control what shows up on the mainPanel, but with placement and duplication issues.
sample data:
#Build test data
testdat<-data.frame(W=c(rep("A",3),
rep("B",3),
rep("C",3)),
X=c(letters[1:9]),
Y=c(11:19),
Z=c(letters[1:7],"",NA),
stringsAsFactors = FALSE)
#Export test data
write.table(testdat,
"your/path/file.txt",
row.names = FALSE,
sep = "\t",
quote = FALSE,
na="")
shiny app (app.R):
library(shiny)
library(excelR)
#function to summarize tables
Pivot<-function(df){
cclass<-as.character(sapply(df,
class))
df.1<-apply(df,
2,
function(x) unlist(list(nrows = as.numeric(NROW(x)),
nrows.unique = length(unique(x))-(sum(is.na(x))+length(which(x==""))),
nrows.empty = (sum(is.na(x))+length(which(x==""))))))
df.2<-data.frame(df.1,
stringsAsFactors = FALSE)
df.3<-data.frame(t(df.2),
stringsAsFactors = FALSE)
df.3$col.class<-cclass
df.3$col.name<-row.names(df.3)
row.names(df.3)<-NULL
df.3<-df.3[c(5,4,1,2,3)]
return(df.3)
}
ui <- fluidPage(
ui <- fluidPage(titlePanel(title=h1("Summary generator",
align="center")),
sidebarLayout(
sidebarPanel(
h3("Loading panel",
align="center"),
fileInput("df",
"Choose file (format: file.txt)",
accept = c("plain/text",
".txt")),
selectInput("show",
"Choose what to do with file",
choices=c("summarize","explore")),
p("**'summarize' will output a summary of the selected table"),
p("**'explore' will output the full selected editable table"),
tags$hr()
),
mainPanel(
excelOutput("gridfile"),
excelOutput("sumfile")
))))
server <- function(input, output) {
dat<-reactive({
fp<-input$df$datapath
read.delim(fp,
quote="",
na.strings="\"\"",
stringsAsFactors=FALSE)
})
#get summary
output$sumfile<-renderExcel({
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="explore") #if selectInput = "explore" return nothing
return(NULL)
dat.1<-data.frame(dat())
dat.2<-Pivot(dat.1)
excelTable(dat.2,
defaultColWidth = 100,
search = TRUE)
})
#get full file
output$gridfile<-renderExcel({
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="summarize") #if selectInput = "summarize" return nothing
return(NULL)
dat.1<-data.frame(dat())
excelTable(dat.1,
defaultColWidth = 100,
search = TRUE)
})
}
shinyApp(ui = ui, server = server)
One way to do what you want is to use observeEvent for your inputs input$show and input$df and renderExcel based on your selection of `input$show. Here is an updated version for your code:
library(shiny)
library(excelR)
#function to summarize tables
Pivot<-function(df){
cclass<-as.character(sapply(df,
class))
df.1<-apply(df,
2,
function(x) unlist(list(nrows = as.numeric(NROW(x)),
nrows.unique = length(unique(x))-(sum(is.na(x))+length(which(x==""))),
nrows.empty = (sum(is.na(x))+length(which(x==""))))))
df.2<-data.frame(df.1,
stringsAsFactors = FALSE)
df.3<-data.frame(t(df.2),
stringsAsFactors = FALSE)
df.3$col.class<-cclass
df.3$col.name<-row.names(df.3)
row.names(df.3)<-NULL
df.3<-df.3[c(5,4,1,2,3)]
return(df.3)
}
ui <- fluidPage(
ui <- fluidPage(titlePanel(title=h1("Summary generator",
align="center")),
sidebarLayout(
sidebarPanel(
h3("Loading panel",
align="center"),
fileInput("df",
"Choose file (format: file.txt)",
accept = c("plain/text",
".txt")),
selectInput("show",
"Choose what to do with file",
choices=c("summarize","explore")),
p("**'summarize' will output a summary of the selected table"),
p("**'explore' will output the full selected editable table"),
tags$hr()
),
mainPanel(
excelOutput("gridfile"),
excelOutput("sumfile")
))))
server <- function(input, output) {
dat<-reactive({
fp<-input$df$datapath
read.delim(fp,
quote="",
na.strings="\"\"",
stringsAsFactors=FALSE)
})
observeEvent({
input$show
input$df
}, {
inFile=input$df
if(is.null(inFile)) #if fileInput is empty return nothing
return(NULL)
if(input$show=="explore") {
output$gridfile<-renderExcel({
dat.1<-data.frame(dat())
excelTable(dat.1,
defaultColWidth = 100,
search = TRUE)
})
}
if(input$show=="summarize") {
output$sumfile<-renderExcel({
dat.1<-data.frame(dat())
dat.2<-Pivot(dat.1)
excelTable(dat.2,
defaultColWidth = 100,
search = TRUE)
})
}
})
}
shinyApp(ui = ui, server = server)
Hope it helps!

How to update UI on file change

Hello I'm building a shinydashboard using several excel files.
I inserted links to these files in the footer of the box and I want to refresh the shinydashboard when changing something in my excel file.
I don't want to run the whole R code each time.
How can I re-render the Output once the file content changes?
Here an example:
sidebar <- dashboardSidebar(
sidebarMenu( menuItem("Hello", tabName = "Hello", icon = icon("dashboard"))
))
body <- dashboardBody(
tabItems(
tabItem(tabName = "Hello",
box(title = "my file",
footer = a("df.xlsx", href="df.xlsx" ) ,
DT::dataTableOutput("df1"),style = "font-size: 100%; overflow: auto;",
width = 12, hight = NULL, solidHeader = TRUE, collapsible = TRUE, collapsed = TRUE, status = "primary")
)))
ui <- dashboardPage(
dashboardHeader(title = "My Dashboard"),
sidebar,
body)
server <- function(input, output) {
output$df1 <- renderDataTable({
df <- read_excel("df.xlsx")
DT::datatable(df, escape = FALSE, rownames=FALSE,class = "cell-border",
options =list(bSort = FALSE, paging = FALSE, info = FALSE)
)
})
}
shinyApp(ui, server)
To monitor the change in a file you could use the cheksum of the file like this:
library(shiny)
library(digest)
# Create data to read
write.csv(file="~/iris.csv",iris)
shinyApp(ui=shinyUI(
fluidPage(
sidebarLayout(
sidebarPanel(
textInput("path","Enter path: "),
actionButton("readFile","Read File"),
tags$hr()
),
mainPanel(
tableOutput('contents')
)))
),
server = shinyServer(function(input,output,session){
file <- reactiveValues(path=NULL,md5=NULL,rendered=FALSE)
# Read file once button is pressed
observeEvent(input$readFile,{
if ( !file.exists(input$path) ){
print("No such file")
return(NULL)
}
tryCatch({
read.csv(input$path)
file$path <- input$path
file$md5 <- digest(file$path,algo="md5",file=TRUE)
file$rendered <- FALSE
},
error = function(e) print(paste0('Error: ',e)) )
})
observe({
invalidateLater(1000,session)
print('check')
if (is.null(file$path)) return(NULL)
f <- read.csv(file$path)
# Calculate ckeksum
md5 <- digest(file$path,algo="md5",file=TRUE)
# If no change in cheksum, do nothing
if (file$md5 == md5 && file$rendered == TRUE) return(NULL)
output$contents <- renderTable({
print('render')
file$rendered <- TRUE
f
})
})
}))
If I understand the question correctly, I'd say you need the reactiveFileReader function.
Description from the function's reference page:
Given a file path and read function, returns a reactive data source
for the contents of the file.
The file reader will poll the file for changes, and once a change is detected the UI gets updated reactively.
Using the gallery example as a guide, I updated the server function in your example to the following:
server <- function(input, output) {
fileReaderData <- reactiveFileReader(500,filePath="df.xlsx", readFunc=read_excel)
output$df1 <- renderDataTable({
DT::datatable(fileReaderData(), escape = FALSE, rownames=FALSE,class = "cell-border",
options =list(bSort = FALSE, paging = FALSE, info = FALSE)
)
})
}
With that, any changes I saved to 'df.xlsx' were propagated almost instantly to the UI.

Resources