I have a simple shiny app.
First, I generated 2 csv files in my working directory:
write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)
write.csv(data.frame(a = 1:4, c = 11:14), "y.csv", row.names = F)
In my app, I want the user to:
read in 2 files (x.csv and y.csv) and...
Click on the button 'Run'!
After that I want server.R to write out 2 csv files - but also to print out certain messages for the user to see.
My code below works, but currently the messages for the user look very ugly and each is sitting on a dull gray background. Two questions:
Is my method the only method to print messages for the user? Or maybe there is a more elegant one?
How could I modify the gray background, font size, color, etc?
Thank you so much!
library(shiny)
library(shinyjs)
# ui code:
ui <- fluidPage(
useShinyjs(),
br(),
# User should upload file x.csv here:
fileInput("file_x", label = h5("Upload file 'x.csv'!")),
br(),
# User should upload file y.csv here:
fileInput("file_y", label = h5("Upload file 'y.csv'!")),
br(),
# Users clicks the button:
actionButton("do_it", "Run!"),
br(),
hidden(p("First, please upload one of the 2 files above!",
id = "p_nofiles",
style = "font-weight:bold;color:red;")),
br(),
verbatimTextOutput("message_1"),
br(),
verbatimTextOutput("message_2"),
br(),
verbatimTextOutput("message_3")
)
# server code:
server <- function(input, output, session) {
observeEvent(input$do_it, {
# If there file_x input is NULL, show the message in p_nofile
if (is.null(input$file_x) | is.null(input$file_y)) {
shinyjs::show("p_nofiles")
} else {
# if both files are selected, hide the p_nofiles message
shinyjs::hide("p_nofiles")
# Check my button's value:
output$print_action <- renderPrint({input$do_it})
# Read in file x_csv:
infileX <- input$file_x
if (is.null(infileX)) {
return(NULL)
}
x <- read.csv(infileX$datapath)
# Read in file y_csv:
infileY <- input$file_y
if (is.null(infileY)) {
return(NULL)
}
y <- read.csv(infileY$datapath)
#-------------------------------------------------------------------------------------------
# MESSAGES I WANT THE USER TO SEE:
# MESSAGE 1 - always there: What names do x and y have in common?
mes1 <- paste0("x and y have these columns in common: ",
intersect(names(x), names(y)), "\n")
output$message_1 <- renderText({mes1})
# MESSAGE 2 - with 2 alternative texts: Do x and y have the same number of rows?
if (nrow(x) == nrow(y)) {
mes2 <- "x and y have the same number of rows!\n"
} else {
mes2 <- "x has a different number of rows than y\n"
}
output$message_2 <- renderText({mes2})
# MESSAGE 3 - to be printed only under one condition:
# Do x and y have a different number of columns? Print only it's different, otherwise - nothing
if (ncol(x) != ncol(y)) {
mes3 <- "x and y do NOT have the same number of columns!\n"
output$message_3 <- renderText({mes3})
} else {output$message_3 <- renderText({NULL})}
#-------------------------------------------------------------------------------------------
# Writing out the same file x - but under a different name:
filenameX <- paste0("x", input$do_it, ".csv")
write.csv(x, file = filenameX, row.names = FALSE)
# Writing out the same file y - but under a different name:
filenameY <- paste0("y", input$do_it, ".csv")
write.csv(y, file = filenameY, row.names = FALSE)
}
})
}
shinyApp(ui, server)
I edited your code, try this. What you need to pay attention is the part that has showModal(...) in server.
library(shiny)
library(shinyjs)
UI code:
ui <- fluidPage(
useShinyjs(),
br(),
# User should upload file x.csv here:
fileInput("file_x", label = h5("Upload file 'x.csv'!")),
br(),
# User should upload file y.csv here:
fileInput("file_y", label = h5("Upload file 'y.csv'!")),
br(),
# Users clicks the button:
actionButton("do_it", "Run!"),
br(),
hidden(p("First, please upload one of the 2 files above!",
id = "p_nofiles",
style = "font-weight:bold;color:red;"))
# br(),
# verbatimTextOutput("message_1"),
# br(),
# verbatimTextOutput("message_2"),
# br(),
# verbatimTextOutput("message_3")
)
Server code:
server <- function(input, output, session) {
observeEvent(input$do_it, {
# If there file_x input is NULL, show the message in p_nofile
if (is.null(input$file_x) | is.null(input$file_y)) {
shinyjs::show("p_nofiles")
} else {
# if both files are selected, hide the p_nofiles message
shinyjs::hide("p_nofiles")
# Check my button's value:
output$print_action <- renderPrint({input$do_it})
# Read in file x_csv:
infileX <- input$file_x
if (is.null(infileX)) {
return(NULL)
}
x <- read.csv(infileX$datapath)
# Read in file y_csv:
infileY <- input$file_y
if (is.null(infileY)) {
return(NULL)
}
y <- read.csv(infileY$datapath)
#-------------------------------------------------------------------------------------------
# MESSAGES I WANT THE USER TO SEE:
# MESSAGE 1 - always there: What names do x and y have in common?
mes1 <- paste0("x and y have these columns in common: ",
intersect(names(x), names(y)), "\n")
# output$message_1 <- renderText({mes1})
# MESSAGE 2 - with 2 alternative texts: Do x and y have the same number of rows?
if (nrow(x) == nrow(y)) {
mes2 <- "x and y have the same number of rows!\n"
} else {
mes2 <- "x has a different number of rows than y\n"
}
# output$message_2 <- renderText({mes2})
# MESSAGE 3 - to be printed only under one condition:
# Do x and y have a different number of columns? Print only it's different, otherwise - nothing
if (ncol(x) != ncol(y)) {
mes3 <- "x and y do NOT have the same number of columns!\n"
# output$message_3 <- renderText({mes3})
} else {mes3 <- renderText({NULL})}
showModal(modalDialog(
title = "Mensagens to User",
"More Text",
mes1,
HTML("<br />"),
mes2,
HTML("<br />"),
mes3,
easyClose = TRUE,
footer = "Footer"
))
#-------------------------------------------------------------------------------------------
# Writing out the same file x - but under a different name:
filenameX <- paste0("x", input$do_it, ".csv")
write.csv(x, file = filenameX, row.names = FALSE)
# Writing out the same file y - but under a different name:
filenameY <- paste0("y", input$do_it, ".csv")
write.csv(y, file = filenameY, row.names = FALSE)
}
})
}
shinyApp(ui, server)
Related
I am creating an app to allow user to upload two excel files and carry over the comments one to the other one, then to download the merged file. The downloadhandler is not working when I tried to run it on the published server, however it running properly locally in rstudio. Any thoughts/suggestions?
library(plyr)
library(dplyr)
library(tidyr)
library(readxl)
library(xlsx)
library(openxlsx)
ui <- fluidPage(
br(),
titlePanel("Excel File Merging Tool"),
br(),
br(),
sidebarLayout(
sidebarPanel(
fileInput("file1", label = h3("Upload New File"), multiple = FALSE, buttonLabel = "Browse", placeholder = "No file selected"),
fileInput("file2", label = h3("Upload Old File"), multiple = FALSE, buttonLabel = "Browse", placeholder = "No file selected"),
actionButton("actionMerge", label = "Merge Uploaded Files"),
hr(),
downloadButton('downloadData', 'Download Merged File')
),
mainPanel(
)
)
)
#Defined Funtions
read_excel_allsheets <- function(filename, tibble = FALSE) {
sheets <- readxl::excel_sheets(filename)
x <- lapply(sheets, function(X) readxl::read_excel(filename, sheet = X))
if(!tibble) x <- lapply(x, as.data.frame)
names(x) <- sheets
x
}
server <- function(input, output) {
getData <- eventReactive(input$actionMerge, {
inFile1 <- input$file1
if (is.null(inFile1)){
return(NULL)
} else {
mydata1= read_excel_allsheets(inFile1$datapath)}
inFile2 <- input$file2
if (is.null(inFile2)){
return(NULL)
} else {
mydata2= read_excel_allsheets(inFile2$datapath)}
wb <- createWorkbook()
#find tabs not in old file
newSheets <- (names(mydata1))[which(!(names(mydata1)) %in% (names(mydata2)))]
if (length(newSheets) > 0){
for (n in newSheets)
{
mydata6 <- bind_rows(mydata1[n])
addWorksheet(wb, sheetName = names(mydata1[n]))
writeData(wb, names(mydata1[n]), mydata6)
}}
for (i in names(mydata1)){
for (j in names(mydata2)){
if (i == j ){
if ((nrow(as.data.frame(mydata1[i]))) == 0 | (nrow(as.data.frame(mydata2[j]))) == 0 )
{
mydata6 <- bind_rows(mydata1[i])
addWorksheet(wb, sheetName = names(mydata1[i]))
writeData(wb, names(mydata1[i]), mydata6)
}
else {
if (ncol(bind_rows(mydata1[i])) == ncol(bind_rows(mydata2[j])) )
{
mydata6 <- bind_rows(mydata1[i])
addWorksheet(wb, sheetName = names(mydata1[i]))
writeData(wb, names(mydata1[i]), mydata6)
}
else {
# validate(
# column_mismatch(mydata1[i], mydata2[j])
# )
drop_in_key <- c("Earliest data creation time", "Latest data update time", "Timestamp of last save in clinical views", "Date time value from the source file name",
"Lowest Date of Rec, Pg, Inst or Subj", "Record Minimum Created Datetime Stamp", "Record Maximum Updated Datetime Stamp", "Accessible to Jreview Timestamp")
mydatax0 = bind_rows(mydata1[i])
mydatax = bind_rows(mydata1[i])[,!(names(bind_rows(mydata1[i])) %in% drop_in_key)]
mydatanew <- mydatax %>% unite(col="Key", 1:(ncol(mydatax)-1), sep=";", remove=FALSE)
mydatanew$Newflag <- "New"
mydatanew0 = mydatanew %>% select(Key, Newflag)
mydatanew1 = bind_cols(mydatanew0,mydatax0)
mydatay0 = bind_rows(mydata2[j])
mydatay = bind_rows(mydata2[j])[,!(names(bind_rows(mydata2[j])) %in% drop_in_key)]
mydataold <- mydatay %>% unite(col="Key", 1:(ncol(mydatay)-1), sep=";", remove=FALSE)
mydataold$Oldflag <- "Old"
mydataold0 <- mydataold %>% select(Oldflag, Key)
mydataold1 <- bind_cols(mydataold0,mydatay0)
mydataold2 = select(mydataold1, Key, Oldflag, (ncol(bind_rows(mydata1[i]))+3):((ncol(mydataold1))))
mydata3 <- merge(x=mydatanew0, y=mydataold2, by="Key", all=TRUE)
mydata4 <- subset(mydata3, Newflag == "New")
mydata5 <- merge(x=mydatanew1, y=mydata4, by="Key", all.y=TRUE)
drop <- c("Key", "Newflag.x", "Oldflag", "Newflag.y")
mydata6 = mydata5[,!(names(mydata5) %in% drop)]
addWorksheet(wb, sheetName = names(mydata1[i]))
writeData(wb, names(mydata1[i]), mydata6)
}}}
else
NULL
}
}
saveWorkbook(wb, file = "aaa.xlsx" , overwrite = TRUE)
})
output$downloadData <- downloadHandler(
filename = function() {
paste0(input$file2, ".xlsx")
},
content = function(file) {
file.copy("aaa.xlsx", file)
})
}
shinyApp(ui = ui, server = server)```
Here's a toy shiny app that provides a solution that is safe for concurrent users. All operations are done on either (a) temporary files that shiny controls, or (b) in the directory of one of these temp files, using tempfile to create the new filename. Both of those assure new-file uniqueness, so no filename collisions. (I believe shiny's method is temporary directories under a temp-directory, at least that's what I'm seeing in my dev env here. So ... seemingly robust.)
The some_magic_function function is mostly because I didn't want to generate an example with openxlsx and sample datas and such, mostly my laziness. For your code, remove all of the if (runif... within the tryCatch and replace with whatever you need, ensuring your code ends by returning the filename with the new data (or updated) data.
... but keep the tryCatch! It will ensure that the function always returns "something". If all code succeeds, then the function will return the filename with new/updated data. If something goes wrong, it returns a class "error" string that can be used to communicate to the user (or otherwise react/recover).
Last thing, though it's just icing on my cupcake here: I use the shinyjs package to disable the 'merge' and 'download' buttons until there is valid data. Frankly, once the two file-selection inputs have something set, the "merge" button will likely never be disabled. However, if there's ever a problem during the merge/update, then the download button will be disabled (until a merge/update happens without error).
library(shiny)
library(shinyjs)
# a naive function that just concatenates the files, first removing
# the header row from the second file
some_magic_function <- function(f1, f2) {
# put the output file in the same directory as 'f2'
d <- dirname(f2)
if (!length(d)) d <- "."
output_file <- tempfile(tmpdir = d, fileext = paste0(".", tools::file_ext(f2)))
tryCatch({
if (runif(1) < 0.2) {
# purely for StackOverflow demonstration
stop("Something went wrong")
} else {
# add your stuff here (and remove the runif if/else)
writeLines(c(readLines(f1), readLines(f2)[-1]), output_file)
output_file # you must return this filename
}
}, error = function(e) e)
# implicitly returning the output_file or an error (text with class 'error')
}
shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
titlePanel("Tool"),
sidebarLayout(
sidebarPanel(
fileInput("file1", label = "File #1", multiple = FALSE, placeholder = "No file selected"),
fileInput("file2", label = "File #2", multiple = FALSE, placeholder = "No file selected"),
actionButton("btn", label = "Merge uploaded files"),
hr(),
downloadButton("dnld", "Download merged file")
),
mainPanel(
tableOutput("tbl"),
hr(),
verbatimTextOutput("bigtext")
)
)
),
server = function(input, output, session) {
# start with neither button enabled
for (el in c("btn", "dnld")) shinyjs::disable(el)
# disable the 'merge' button until both files are set
observeEvent({
input$file1
input$file2
}, {
req(input$file1, input$file2)
shinyjs::toggleState("btn", isTRUE(file.exists(input$file1$datapath) && file.exists(input$file2$datapath)))
})
# this is the "workhorse" of the shiny app
newfilename <- eventReactive(input$btn, {
req(input$file1, input$file2)
some_magic_function(input$file1$datapath, input$file2$datapath)
})
# prevent the download handler from being used if the new file does not exist
observeEvent(newfilename(), {
cond <- !is.null(newfilename()) &&
!inherits(newfilename(), "error") &&
file.exists(newfilename())
shinyjs::toggleState("dnld", cond)
})
output$dnld <- downloadHandler(
filename = function() paste0("merged_", input$file2),
content = function(f) {
file.copy(newfilename(), f)
}
)
# some sample output, for fun
output$tbl <- renderTable({
req(newfilename(),
!inherits(newfilename(), "error"),
file.exists(newfilename()))
read.csv(newfilename(), nrows = 10, stringsAsFactors = FALSE)
})
output$bigtext <- renderText({
if (inherits(newfilename(), "error")) {
# if we get here then there was a problem
as.character(newfilename())
} else "(No problem)"
})
}
)
Notes:
shiny::req is supposed to ensure the data has something useful and "truthy" in it (see shiny::isTruthy). Normally it is good with detecting nulls, NA, empty variables, etc ... but it "passes" something that has class "error", perhaps counter-intuitive. That's why I had to be a little more explicit with conditions in some of the reactive blocks.
One impetus for having the merge/update functionality within an external not-shiny-requiring function (some_magic_function here) is that it facilitates testing of the merge functionality before adding the shiny scaffolding. It's difficult to test basic functionality when one is required to interact with a browser for every debugging step of basic functionality.
Below is my code. It might seem a bit long but actually it's a VERY simple app.
The user is supposed to upload a tiny data frame (x.csv if you are in the US or x_Europe.csv if you are in Europe). Then the user should click on the button to start calculations. And then at the end the user should be able to download the results of those calculations as a data frame.
My problem: after I upload the file, when I click on the 'do_it' action button - nothing happens. I can see it because nothing is being printed to my console. WHY? After all, my function 'main_calc' should be eventReactive to input$do_it? Why do all the calculations inside main_calc start happening ONLY after the user tries to download the results?
Important: It is important to me to keep the 'Data' function separately from main_calc.
Thank you very much!
First, generate one of these 2 files in your working directory:
# generate file 'x.csv' to read in later in the app:
write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F) # US file
write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)
This is the code for the shiny app:
library(shiny)
ui <- fluidPage(
# User should upload file x here:
fileInput("file_x", label = h5("Upload file 'x.csv'!")),
br(),
actionButton("do_it", "Click Here First:"),
br(),
br(),
textInput("user_filename","Save your file as:", value = "My file x"),
downloadButton('file_down',"Save the output File:")
)
server <- function(input, output, session) {
#----------------------------------------------------------------------
# Function to read in either European (csv2) or American (csv) input:
#----------------------------------------------------------------------
ReadFile <- function(pathtofile, withheader = TRUE){
test <- readLines(pathtofile, n = 1)
if (length(strsplit(test, split = ";")[[1]]) > 1) {
print("Reading European CSV file")
outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
europe.file = 1)
} else {
print("Reading US CSV file")
outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
europe.file = 0)
}
return(outlist)
}
#----------------------------------------------------------------------
# Data-related - getting the input file
#----------------------------------------------------------------------
Data <- reactive({
print("Starting reactive function 'Data'")
# Input file:
infile_x <- input$file_x
myx <- ReadFile(infile_x$datapath)$myinput
# European file?
europe <- ReadFile(infile_x$datapath)$europe.file
print("Finishing reactive function 'Data'")
return(list(data = myx, europe = europe))
})
#----------------------------------------------------------------------
# Main function that should read in the input and 'calculate' stuff
# after the users clicks on the button 'do_it' - takes about 20 sec
#----------------------------------------------------------------------
main_calc <- eventReactive(input$do_it, {
req(input$file_x)
# Reading in the input file:
x <- Data()$data
print("Done reading in the data inside main_calc")
# Running useless calculations - just to kill time:
myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
print("Starting calculations")
for (i in seq_len(10)) {
set.seed(12)
mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
temp <- solve(mymatr) %*% myvector
}
print("Finished calculations")
# Creating a new file:
y <- temp
result = list(x = x, y = y)
print("End of eventReactive function main_calc.")
return(result)
}) # end of main_calc
#----------------------------------------------------------------------
# The user should be able to save the output of main_calc as a csv file
# using a string s/he specified for the file name:
#----------------------------------------------------------------------
output$file_down <- downloadHandler(
filename = function() {
paste0(input$user_filename, " ", Sys.Date(), ".csv")
},
content = function(file) {
print("Europe Flag is:")
print(Data()$europe)
if (Data()$europe == 1) {
x_out <- main_calc()$x
print("Dimensions of x in downloadHandler are:")
print(dim(x_out))
write.csv2(x_out,
file,
row.names = FALSE)
} else {
x_out <- main_calc()$x
print("Dimensions of x in downloadHandler are:")
print(dim(x_out))
write.csv(x_out,
file,
row.names = FALSE)
}
}
)
} # end of server code
shinyApp(ui, server)
Below is the solution - based on MrFlick's suggestions:
# generate file 'x.csv' to read in later in the app:
# write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)
# write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)
library(shiny)
library(shinyjs)
ui <- fluidPage(
# User should upload file x here:
fileInput("file_x", label = h5("Upload file 'x.csv'!")),
br(),
actionButton("do_it", "Click Here First:"),
br(),
br(),
textInput("user_filename","Save your file as:", value = "My file x"),
downloadButton('file_down',"Save the output File:")
)
server <- function(input, output, session) {
#----------------------------------------------------------------------
# Function to read in either European (csv2) or American (csv) input:
#----------------------------------------------------------------------
ReadFile <- function(pathtofile, withheader = TRUE){
test <- readLines(pathtofile, n = 1)
if (length(strsplit(test, split = ";")[[1]]) > 1) {
print("Reading European CSV file")
outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
europe.file = 1)
} else {
print("Reading US CSV file")
outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
europe.file = 0)
}
return(outlist)
}
#----------------------------------------------------------------------
# Data-related - getting the input file
#----------------------------------------------------------------------
Data <- reactive({
print("Starting reactive function Data")
# Input file:
infile_x <- input$file_x
myx <- ReadFile(infile_x$datapath)$myinput
# European file?
europe <- ReadFile(infile_x$datapath)$europe.file
print("Finishing reactive function 'Data'")
return(list(data = myx, europe = europe))
})
#----------------------------------------------------------------------
# Main function that should read in the input and 'calculate' stuff
# after the users clicks on the button 'do_it' - takes about 20 sec
#----------------------------------------------------------------------
# Creating reactive Values:
forout_reactive <- reactiveValues()
observeEvent(input$do_it, {
print("STARTING observeEvent")
req(input$file_x)
# Reading in the input file:
x <- Data()$data
print("Done reading in the data inside observeEvent")
# Running useless calculations - just to kill time:
myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
print("Starting calculations")
for (i in seq_len(10)) {
set.seed(12)
mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
temp <- solve(mymatr) %*% myvector
} # takes about 22 sec on a laptop
print("Finished calculations")
# Creating a new file:
y <- temp
forout_reactive$x = x
forout_reactive$y = y
print("End of observeEvent")
}) # end of main_calc
#----------------------------------------------------------------------
# The user should be able to save the output of main_calc as a csv file
# using a string s/he specified for the file name:
#----------------------------------------------------------------------
output$file_down <- downloadHandler(
filename = function() {
paste0(input$user_filename, " ", Sys.Date(), ".csv")
},
content = function(file) {
print("Europe Flag is:")
print(Data()$europe)
if (Data()$europe == 1) {
y_out <- forout_reactive$y
print("Dimensions of y in downloadHandler are:")
print(dim(y_out))
write.csv2(y_out,
file,
row.names = FALSE)
} else {
y_out <- forout_reactive$y
print("Dimensions of y in downloadHandler are:")
print(dim(y_out))
write.csv(y_out,
file,
row.names = FALSE)
}
}
)
} # end of server code
shinyApp(ui, server)
Here is a simple app that may help elucidate how eventReactive() works:
library(shiny)
run_data <- function() {
paste0("Random number generated in eventReactive: ", runif(1))
}
ui <- basicPage(
actionButton("run1", "Invalidate eventReative()"),
actionButton("run2", "Trigger observeEvent()"),
verbatimTextOutput("data")
)
server <- function(input, output, session) {
# Initialize reactiveValues list
# to use inside observeEvent()
rv <- reactiveValues(data = NULL)
# This eventReactive() doesn't run when run1 button is
# clicked. Rather, it becomes invalidated. Only when
# data() (the reactive being returned) is actually
# called, does the expression inside actually run.
# If eventReactive is not invalidated by clicking run1
# then even if data() is called, it still won't run.
data <- eventReactive(input$run1, {
showNotification("eventReactive() triggered...")
run_data()
})
# Every time run2 button is clicked,
# this observeEvent is triggered and
# will run. If run1 is clicked before run2,
# thus invalidating the eventReactive
# that produces data(), then data() will
# contain the output of run_data() and
# rv$data will be assigned this value.
observeEvent(input$run2, {
showNotification("observeEvent() triggered")
rv$data <- data()
})
# Renders the text found in rv$data
output$data <- renderText({
rv$data
})
}
shinyApp(ui, server)
In this example, run1 invalidates the eventReactive(), and run2 triggers the observeEvent() expression. In order for the data (in this case just a random number) to print, run1 must be clicked prior to run2.
The key takeaway is that the input(s) (buttons) that eventReactive() listens to don't trigger eventReactive(). Instead, they invalidate eventReactive() such that when the output from eventReactive() is required, then the expression inside eventReactive() will run. If it is not invalidated or the output is not needed, it will not run.
Goal: I'm working on a bioinformatics project. I'm currently trying to implement R code that dynamically creates tabPanels (they are essentially carbon copies except for the data output).
Implementation: After doing some research I implemented this solution. It works in a way (the panels that I'm "carbon copying" are created), but the data that I need cannot be displayed.
Problem: I'm sure that the way I'm displaying my data is fine. The problem is that I can't use the same output function to display the data as seen here. So let me get to the code...
ui.R
library(shiny)
library(shinythemes)
library(dict)
library(DT)
...# Irrelevant functions removed #...
geneinfo <- read.table(file = "~/App/final_gene_info.csv",
header = TRUE,
sep = ",",
na.strings = "N/A",
as.is = c(1,2,3,4,5,6,7))
ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
theme = shinytheme("cerulean"),
tabPanel("Home",
#shinythemes::themeSelector(),
fluidPage(
includeHTML("home.html")
)),
tabPanel("Gene Info",
h2('Detailed Gene Information'),
DT::dataTableOutput('table')),
tabPanel("File Viewer",
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "gene", label = "Choose a Gene", choice = genes, multiple = TRUE),
selectInput(inputId = "organism", label = "Choose an Organism", choice = orgs),
selectInput(inputId = "attribute", label = "Choose an Other", choice = attributes),
width = 2),
mainPanel(
uiOutput('change_tabs'),
width = 10))),
tabPanel("Alignment")
)
I'm using uiOutput to generate tabs dynamically on the server side....
server.R
server <- function (input, output, session) {
# Generate proper files from user input
fetch_files <- function(){
python <- p('LIB', 'shinylookup.py', python=TRUE)
system(sprintf('%s %s %s', python, toString(genie), input$organism), wait = TRUE)
print('Done with Python file generation.')
# Fetch a temporary file for data output
fetch_temp <- function(){
if(input$attribute != 'Features'){
if(input$attribute != 'Annotations'){
chosen <- toString(attribute_dict[[input$attribute]])
}
else{
chosen <- toString(input$sel)
extension <<- '.anno'
}
}
else{
chosen <- toString(input$sel)
extension <<- '.feat'
}
count = 0
oneline = ''
f <- paste(toString(genie), toString(input$organism), sep = '_')
f <- paste(f, extension, sep = '')
# Writes a temporary file to display output to the UI
target <- p('_DATA', f)
d <- dict_fetch(target)
temp_file <- tempfile("temp_file", p('_DATA', ''), fileext = '.txt')
write('', file=temp_file)
vectorofchar <- strsplit(toString(d[[chosen]]), '')[[1]]
for (item in vectorofchar){
count = count + 1
oneline = paste(oneline, item, sep = '')
# Only 60 characters per line (Find a better solution)
if (count == 60){
write(toString(oneline), file=temp_file, append=TRUE)
oneline = ''
count = 0
}
}
write(toString(oneline), file=temp_file, append=TRUE)
return(temp_file)
}
# Get the tabs based on the number of genes selected in the UI
fetch_tabs <- function(Tabs, OId, s = NULL){
count = 0
# Add a select input or nothing at all based on user input
if(is.null(s)==FALSE){
selection <- select(s)
x <- selectInput(inputId = 'sel', label = "Choose an Annotation:", choices = selection$keys())
}
else
x <- ''
for(gene in input$gene){
if(count==0){myTabs = character()}
count = count + 1
genie <<- gene
fetch_files()
file_tab <- lapply(sprintf('File for %s', gene), tabPanel
fluidRow(
titlePanel(sprintf("File for %s:", gene)),
column(5,
pre(textOutput(outputId = "file")),offset = 0))
)
addTabs <- c(file_tab, lapply(sprintf('%s for %s',paste('Specific', Tabs), gene), tabPanel,
fluidRow(
x,
titlePanel(sprintf("Attribute for %s:", gene)),
column(5,
pre(textOutput(outputId = OId), offset = 0)))
))
# Append additional tabs every iteration
myTabs <- c(myTabs, addTabs)
}
return(myTabs)
}
# Select the proper file and return a dictionary for selectInput
select <- function(ext, fil=FALSE){
f <- paste(toString(genie), toString(input$organism), sep = '_')
f <- paste(f, ext, sep = '')
f <- p('_DATA', f)
if(fil==FALSE){
return(dict_fetch(f))
}
else if(fil==TRUE){
return(toString(f))
}
}
# Output gene info table
output$table <- DT::renderDataTable(
geneinfo,
filter = 'top',
escape = FALSE,
options = list(autoWidth = TRUE,
options = list(pageLength = 10),
columnDefs = list(list(width = '600px', targets = c(6))))
)
observe({
x <- geneinfo[input$table_rows_all, 2]
if (is.null(x))
x <- genes
updateSelectizeInput(session, 'gene', choices = x)
})
# Output for the File tab
output$file <- renderText({
extension <<- '.gbk'
f <- select(extension, f=TRUE)
includeText(f)
})
# Output for attributes with ony one property
output$attributes <- renderText({
extension <<- '.kv'
f <- fetch_temp()
includeText(f)
})
# Output for attributes with multiple properties (features, annotations)
output$sub <- renderText({
f <- fetch_temp()
includeText(f)
})
# Input that creates tabs and selectors for more input
output$change_tabs <- renderUI({
# Fetch all the appropriate files for output
Tabs = input$attribute
if(input$attribute == 'Annotations'){
extension <<- '.anno'
OId = 'sub'
s <- extension
}
else if(input$attribute == 'Features'){
extension <<- '.feat'
OId = 'sub'
s <- extension
}
else{
OId = 'attributes'
s <- NULL
}
myTabs <- fetch_tabs(Tabs, OId, s = s)
do.call(tabsetPanel, myTabs)
})
}
)
Explanation: Now I'm aware that there's a lot to look at here.. But my problem exists within output$change_tabs (it's the last function), which calls fetch_tabs(). Fetch tabs uses the input$gene (a list of genes via selectizeInput(multiple=TRUE)) to dynamically create a set of 2 tabs per gene selected by the user.
What's Happening: So if the user selects 2 genes then 4 tabs are created. With 5 genes 10 tabs are created... And so on and so forth... Each tab is EXACTLY THE SAME, except for the data.
Roadblocks: BUT... for each tab I'm trying to use the same output Id (since they are EXACTLY THE SAME) for the data that I want to display (textOutput(outputId = "file")). As explained above in the second link, this simply does not work because HTML.
Questions: I've tried researching several solutions, but I would rather not have to implement this solution. I don't want to have to rewrite so much code. Is there any way I can add a reactive or observer function that can wrap or fix my output$file function? Or is there a way for me to add information to my tabs after the do.call(tabsetPanel, myTabs)? Am I thinking about this the right way?
I'm aware that my code isn't commented very well so I apologize in advance. Please feel free to critique my coding style in the comments, even if you don't have a solution. Please and thank you!
I've come up with a very VERY crude answer that will work for now...
Here is the answer from #BigDataScientist
My Issue with BigDataScientist's Answer:
I can't dynamically pass data to the outputs. The output functions are not interpreted until they are needed... So if I wanted to pass the for loop iterator that you created (iter) into the dynamically created outputs, then I wouldn't be able to do that. It can only take static data
My Solution:
I end up taking advantage of sys.calls() solution I found here in order to get the name of the function as a string. The name of the function has the info I need (in this case a number).
library(shiny)
library(shinythemes)
myTabs <<- list()
conv <- function(v1) {
deparse(substitute(v1))
}
ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
theme = shinytheme("cerulean"),
tabPanel("Gene Info",
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 5,
value = 3)
),
# Show a plot of the generated distribution
mainPanel(
uiOutput('changeTab')
)
)
)
)
server <- function(input, output) {
observe({
b <<- input$bins
myTabs <<- list()
# Dynamically Create output functions
# Dynamically Create formatted tabs
# Dynamically Render the tabs with renderUI
for(iter in 1:b){
x <<- iter
output[[sprintf("tab%s", iter)]] <- renderText({
temp <- deparse(sys.calls()[[sys.nframe()-3]])
x <- gsub('\\D','',temp)
x <- as.numeric(x)
f <- sprintf('file%s.txt', x)
includeText(f)
})
addTabs <<- lapply(sprintf('Tab %s', iter), tabPanel,
fluidRow(
titlePanel(sprintf("Tabble %s:", iter)),
column(5,
pre(textOutput(outputId = sprintf('%s%s','tab', iter))))))
myTabs <<- c(myTabs, addTabs)
}
myTabs <<- c(myTabs, selected = sprintf('Tab %s', x))
output$changeTab <- renderUI({
do.call(tabsetPanel, myTabs)
})
})
}
# Run the application
shinyApp(ui = ui, server = server)
I think your being a victim of this behavior. Try:
for (el in whatever) {
local({
thisEl <- el
...
})
}
like Joe suggests in the first reply to the Github issue I linked to. This is only necessary if you're using a for loop. lapply already takes el as an argument, so you get this "dynamic evaluation" benefit (for lack of a better name) for free.
For readability, I'm going to quote most of Joe's answer here:
You're the second person at useR that I talked to that was bitten by this behavior in R. It's because all the iterations of the for loop share the same reference to el. So when any of the created reactive expressions execute, they're using whatever the final value of el was.
You can fix this either by 1) using lapply instead of a for loop; since each iteration executes as its own function call, it gets its own reference to el; or 2) using a for loop but introducing a local({...}) inside of there, and creating a local variable in there whose value is assigned to el outside of the reactive.
I am trying to make a webap that provides inventory information based on user queries. All of the data is located in a flat file and I am currently running R version 3.2.5 (2016-04-14) on windows 7(64bit).
I am stuck trying to figure out how to provide the corresponding pdf based on an input query after the user submits the query, followed by clicking a new action button to open the pdf. All of the pdf's( thousands of them) are stored in my www directory btw...
I have looked at a lot of posts, but they all pertain to opening a single pdf file with iframe or window.open() in the UI script. That works fine, but I can not figure out how to generate the object on the server side form multiple files.
My code:
shinyUI(fluidPage(
tags$head(
tags$style(type="text/css", ".dataTables_filter {display: none; }", "tfoot {display:none;}",HTML("
#import url('//fonts.googleapis.com/css?family=Arima+Madurai|Cabin:400,700');
h1 {
font-family: 'Arima+Madurai', bold;
font-weight: 500;
line-height: 1.1;
}
"))
),
headerPanel("Chem-Share Search Page"),
# Application title
title="Chem-Share Search Page",
# Sidebar with controls to select a dataset
sidebarLayout(
sidebarPanel(
conditionalPanel("Search by Chemicals",
selectizeInput("obs", "Query Chemical Name",choices=NULL, options = list(maxOptions = 5137)),
textInput("txt","Query Barcode, CAS#, or CAT#"),
actionButton("submit", "Submit")),
actionButton('pdfview', 'View SDS', onclick = "window.open('2-Propanol.pdf')") #### this works when I know the pdf, but how to make it work from subseting a list of pdf's storred locally
),
# Show a summary of the dataset and a data table
mainPanel(
tabsetPanel(
tabPanel('By Name', dataTableOutput('mytable1')),
tabPanel('By Catalog #', dataTableOutput('mytable2')),
tabPanel('By Barcode', dataTableOutput('mytable3')),
tabPanel('By CAS #',dataTableOutput('mytable4')),
tags$head(
tags$style(type = "text/css", "a{color: #000000;}")
)
)
)
),
includeHTML("www/mail_to.html"))
)
Server
require(dplyr);require(data.table);require(xlsx)
locals<- getwd()
shinyServer(function(input, output,session) {
values <- reactiveValues(default = 0)
omw_inventory_2016 <- as.data.table(read.csv("2016_inventory_database.csv",stringsAsFactors = F, encoding = 'UTF-8',na.strings = c("NA","N/A","","none","lookup","look.up","look up")))
omw_inventory_2016<-omw_inventory_2016[Transaction.Type!="Disposal"][Transaction.Type!="Dispose"]
searching <- unique(omw_inventory_2016$Product.Name)
uom <- read.csv("Inventory_Instruction_.csv",stringsAsFactors = F)
new_omw <- merge(omw_inventory_2016,uom,by="Unit.of.Measure",all.x = TRUE)
omw_inventory_2016<- new_omw;rm(new_omw)
sds <- as.data.table(read.csv("sds_list.csv",stringsAsFactors = F))
sds<- sds %>% select(Product.Name,Product.Code,sds1)
import <-read.xlsx("2310.xlsx",sheetName = "Sheet2",stringsAsFactors = F,header = T)
updateSelectizeInput(session,"obs",choices=searching,server=TRUE)
observeEvent(input$submit,{values$default <- input$submit})
setkey(omw_inventory_2016,"Product.Name")
output$downloadData <- downloadHandler(
filename = function(){paste("chem_share_template",".csv",sep = "")},
content = function(file){
write.csv(import,file)
}
)
# ### **I am trying to subset the pdf name to generate the filepath**
# **Does not work**
# output$pdfview <- renderText({
# pdfs<-sds[Product.Code %chin% input$txt,sds1]
# # pdfs<- paste("file:///",locals,"/www/",pdfs,sep = "")
# # pdfs
#
# })
# **I have tried this too**
# view_pdf<- function(important){
# pdfs<-sds[Product.Code %chin% input$txt,sds1]
# pdfs<- paste('"file:///',locals,'/www/',pdfs,'"',sep = "")
# paste('src=',pdfs,',style="height:100%; width:100%; scrolling:yes"',sep="")
# }
#
# output$pdfview <- renderText({view_pdf(input$txt)})
#
#
output$mytable1 <- renderDataTable({
if(values$default==0){
omw_inventory_2016[grep("Ethynyltrimethylsilane",omw_inventory_2016$Product.Name,ignore.case = T),.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM )]
}
else{
omw_inventory_2016[Product.Name==input$obs,.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
})
output$mytable2 <- renderDataTable({
if(values$default==0){
omw_inventory_2016[grep("Ethynyltrimethylsilane",omw_inventory_2016$Product.Name,ignore.case = T),.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
else{
omw_inventory_2016[Product.Code %chin% input$txt,.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
})
output$mytable3 <- renderDataTable({
if(values$default==0){
omw_inventory_2016[grep("Ethynyltrimethylsilane",omw_inventory_2016$Product.Name,ignore.case = T),.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
else{
omw_inventory_2016[MBC %chin% input$txt,.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
})
output$mytable4 <- renderDataTable({
if(values$default==0){
omw_inventory_2016[grep("Ethynyltrimethylsilane",omw_inventory_2016$Product.Name,ignore.case = T),.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
else{
omw_inventory_2016[Ingredient.CAS %chin% input$txt,.(Chemical=Product.Name,Manufacture=Manufacturer.Name,Catalog=Product.Code, Floor=Floor,Amount=Amount.per.Container,UOM=UoM)]
}
})
})
So I asked the following, R Shiny Dynamic Input, a couple of days ago and although the answer is correct given the question, I now want to elaborate some more since I am unable to edit the code given to answer my new question. So originally I wanted to be able to ask the user to specify a number, say k, that would then dynamically generate k fields for the user to fill out. Now, the code given assumes that the output is a numeric, however, I want the user to be able to specify a vector of 5 values in each of the 1,...,k fields. Since, after specifying k, the inputs are going to be k vectors of length 5 of numerical values, I want to be able to store these values in a k by 5 matrix. That way I can use those values to conduct data manipulations later. If it helps, here is the code from the original answer:
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(textOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputGroup = renderUI({
input_list <- lapply(1:input$numInputs, function(i) {
# for each dynamically generated input, give a different name
inputName <- paste("input", i, sep = "")
numericInput(inputName, inputName, 1)
})
do.call(tagList, input_list)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderText({
paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
})
})
# Run the application
shinyApp(ui = ui, server = server)
Edit
Here is updated code that still doesn't completely work:
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(tableOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix <- as.matrix(all, ncol=5)
as.data.frame(matrix)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderText({
paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
})
})
# Run the application
shinyApp(ui = ui, server = server)
You only need to make a few changes:
Change mainPanel(textOutput("inputValues")) to mainPanel(tableOutput("inputValues")) (this is not essential, it just shows the values in a table/matrix format so you can see them)
Change numericInput(inputName, inputName, 1) to textInput(inputName, inputName, "1 2 3 4 5")
Change output$inputValues <- renderText({...... to
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix = as.matrix(read.table(text=all))
data.frame(matrix)
})
matrix is what you want: a k by 5 matrix.
Note that I did not do any input verification. It is assumed that user will enter 5 numbers in each input separated by spaces. If they do not, output might be either wrong or you'll see an error. You may need to implement some input checking here to ensure that it is 5 numbers and not anything else.
Complete code
library(shiny)
ui <- shinyUI(fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
numericInput("numInputs", "How many inputs do you want", 4),
# place to hold dynamic inputs
uiOutput("inputGroup")
),
# this is just a demo to show the input values
mainPanel(tableOutput("inputValues"))
)
))
# Define server logic required to draw a histogram
server <- shinyServer(function(input, output) {
# observe changes in "numInputs", and create corresponding number of inputs
observeEvent(input$numInputs, {
output$inputGroup = renderUI({
input_list <- lapply(1:input$numInputs, function(i) {
# for each dynamically generated input, give a different name
inputName <- paste("input", i, sep = "")
textInput(inputName, inputName, "1 2 3 4 5")
})
do.call(tagList, input_list)
})
})
# this is just a demo to display all the input values
output$inputValues <- renderTable({
all <- paste(lapply(1:input$numInputs, function(i) {
inputName <- paste("input", i, sep = "")
input[[inputName]]
}))
matrix = as.matrix(read.table(text=all))
data.frame(matrix)
})
})
# Run the application
shinyApp(ui = ui, server = server)