I have created a datatable which has selectInput widgets in one of the columns. Another column of the datatable should take inputs given in the first column, and use them to look up a number from my data source. The inputs are binding correctly in Shiny, by using preDrawCallback and drawCallback functions, but lookup values are not updating when the inputs change. Strangely, they do update when I do the lookup in a separate data table. A reproducible example is here:
library(shiny)
library(DT)
data <- data.frame(c(1:7),c(21:27))
shinyApp(
server = shinyServer(function(input, output) {
output$table <- DT::renderDataTable({
Rows <- c(1:7)
temp <- data.frame(Rows)
temp[,"Item"] <- ""
temp[,"Value"] <- ""
temp$Rows <- NULL
sapply(1:7, FUN = function(i) {
temp$Item[i] <<- as.character(selectInput(paste("Item.1.1",i, sep = "."), "",
choices = setNames(c(1:7),c(1:7)),
selected = 1,
multiple = FALSE))
})
sapply(1:7, FUN = function(i) {
temp$Value[i] <<- data[eval(parse(text = paste("input$Item.1.1",i, sep = "."))),2]
})
datatable(temp, escape = FALSE, rownames = FALSE,
options = list(sort = FALSE, paging = FALSE, searching = FALSE, dom = 't',
columnDefs = list(list(className = 'dt-center', targets = 0:1)),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
))
}, server = FALSE)
}),
ui = fluidPage(
dataTableOutput("table")
)
)
That gives the error "Error in temp$Value[i] <<- data[eval(parse(text = paste("input$Item.1.1", :
replacement has length zero".
I have tried adding this to server:
test <- reactive({
data.frame(c(ifelse(is.null(input$Item.1.1.1),"",data[eval(parse(text = paste("input$Item.1.1",1, sep = "."))),2]),
ifelse(is.null(input$Item.1.1.2),"",data[input$Item.1.1.2,2]),
ifelse(is.null(input$Item.1.1.3),"",data[input$Item.1.1.3,2]),
ifelse(is.null(input$Item.1.1.4),"",data[input$Item.1.1.4,2]),
ifelse(is.null(input$Item.1.1.5),"",data[input$Item.1.1.5,2]),
ifelse(is.null(input$Item.1.1.6),"",data[input$Item.1.1.6,2]),
ifelse(is.null(input$Item.1.1.7),"",data[input$Item.1.1.7,2])))
})
Then, when I comment out the appropriate sapply within my renderDataTable and instead assign temp[,"Value"] <- test(), I get 21 down the second column of my datatable, and it does not change when the selectInputs are changed.
As a test, I have tried including this in my serve, coupled with a corresponding dataTableOutput() in my ui:
output$test1 <- DT::renderDataTable({
test()
})
test1 behaves as expected if and only if the second sapply is commented out inside of renderDataTable. If it is not commented out, both tables have a column of unresponsive 21s.
This has been driving me batty all day, so any thoughts would improve my life greatly!
You are using the select input values too early:
sapply(1:7, FUN = function(i) {
temp$Value[i] <<- data[eval(parse(text = paste("input$Item.1.1",i, sep = "."))),2]
})
By the time you use these values, the select inputs have not even been rendered on the page yet, so not surprisingly, you get NULL's. You cannot assign NULL to tmp$Value[i].
Then regarding the failure with:
temp[,"Value"] <- test()
I don't understand what this means: test() returns a data frame, and temp[, "Value"] is a vector. I think you should use c() instead of data.frame() in the reactive.
Something off-topic since I really cannot help it: it is almost always a bad idea to use eval(parse(text = ...)). You can just use input[paste("Item.1.1", i, sep = ".")] instead of constructing the R code and eval() it. Both input$foo and input['foo'] give you the value of the input with id foo. The latter form is more suitable in this case.
Related
What I am trying to do?
I am building a Shiny app that imports data, runs some analysis, and allows the User to make selections regarding the analysis via drop downs in a data table. The initial choices available are specific to each row in the table based on values found in the data. I want the User to be able to augment the data so new values that weren’t found in the imported data are available as choices, too. It is this last part that is giving me trouble.
I’ve created an example based on mtcars to illustrate. The construct I have for creating an editable data table is based on ID's for each cell in a column as follows (thanks to some earlier help I had on Stack to figure it out). The snippet of code below is contained in an observeEvent when I load new data. [Note the full code is at the bottom]
selectInputIDmodel <<- paste0("sel_model", 1:nrow(v$cars()$cars_meta))
v$model_applied <- reactive({match_cars(v$cars())$model_applied})
v$initTbl <-
dplyr::tibble(
car = v$cars()$cars_meta$car,
make = v$cars()$cars_meta$make,
mpg = v$cars()$cars_meta$mpg,
model = sapply(selectInputIDmodel, function(x){as.character(selectInput(inputId = ns(x), label = "",
choices = v$model_applied()$model[v$model_applied()$car == v$cars()$cars_meta$car[which(selectInputIDmodel == x)]],
selected = v$cars()$cars_meta$model[which(selectInputIDmodel == x)]
))})
)
I've set up another observeEvent for when a new model is added. I expect I need to use updateSelectInput to update the choices under the model variable. I've tried this by recreating v$initTbl under this observeEvent, but haven't figured out how to work in the updateSelectInput instead of SelectInput. The former is calling for a "session" argument, so if I just substitute "updateSelectInput" I get an error saying that I cannot convert an environment to character. If I remove the "as.character" I get a "cannot unclass an environment" error.
Further Context
Below is further context for what I am trying to do followed by the code I have.
When running the app, the Load Data button imports the mtcars data and splits the car name into make and model fields. The model field in the display table is a drop down list and has as choices the various models that are found in the data for the specific make of car. The first one in each list is the default value. The User can select from the drop downs and use the Commit button to register the choices selected. The User can go back to make changes and Commit multiple times.
There are fields to allow the User to add a new model name for a particular make of car. Save Model should apply the new model entry as a drop down choice for the relevant make of car. This is what I haven’t been able to get working.
In order to be able to confirm the updates that were committed, once the User selects Commit the first time, I am showing the resultsTbl as verbatim output at the bottom of the page. The output refreshes every time the Commit button is clicked. It is the resultsTbl that I store and will use for onward processing in another module.
Here is a sequence of steps that should be able to be completed.
Step 1: Load Data
Step 2: Change the Model in the 2nd row from “RX4” to “RX4 Wag”
Step 3: Commit and see updates reflected in the resultsTbl
Step 4: Set Select Make to “Valiant”
Step 5: Set Add Model Name to “V”
Step 6: Save Model
Step 7: “V” should appear under “Valiant” as a selection in the drop down
Step 8: Commit and “V” should appear as the model for row 6 in resultsTbl
Step 9: Change the Model in the last row from “240D” to “280”
Step10: Commit and see update reflected in the resultsTbl
What have I tried?
The Load Data button triggers an observeEvent that does the following:
Sets up the data
Determines which models are available for which makes of car (for the drop downs)
Initiates the data table (initTbl)
I use a reactive (displayTbl) to capture the updates to feed the proxy table.
I then use a reactive (resultTbl) to store the captured values.
This all works fine.
I use Save Model as another observeEvent to update which models are available for which makes of car, to add new values to the drop downs where relevant.
I have not been able to figure how to make this work.
I believe I need some way to reinitialize the data table with the refreshed choices for the drop downs, whilst preserving any previously selected values. As noted above, I am unsure how to integrate updateSelectInput into the existing code.
Any help would be greatly appreciated.
Here is the current state of my code:
#********* LIBRARIES *************************************************
library(magrittr)
library(dplyr)
library(tidyselect)
library(shiny)
library(stringr)
library(purrr)
library(shinyjs)
library(zeallot)
library(DT)
#******** FUNCTIONS ***************************************************
# Creates the new data set / cars object
create_data2 <- function(){
#simulate data import
cars_df <- head(mtcars, 10)
#simulate creating meta table
cars_meta <- dplyr::tibble(car = rownames(cars_df), make = sub("([A-Za-z]+).*", "\\1", rownames(cars_df)), cars_df)
cars_meta$model <- NA
#simulate creating cars_list
names <- rownames(cars_df)
`%<-%` <- zeallot::`%<-%`
car <- list()
car[c("head", "m1", "m2")] %<-% data.frame(stringr::str_split(names, " ", simplify = TRUE))
car$m <- paste(car$m1, car$m2)
cars_list <- list()
for(h in car$head){
cars_list[[h]] <- list(car$m[car$head==h])
}
#simulate creating the cars_object
cars_object <- list()
cars_object$cars_df <- cars_df
cars_object$cars_meta <- cars_meta
cars_object$cars_list <- cars_list
return(cars_object)
}
# Updates the cars object with resultTbl
meta_table <- function(object, table){
tbl <- table
object$cars_meta <- tbl
return(object)
}
# Matches the models and makes of the cars
match_cars <- function(cars_object){
cv <- cars_object$cars_meta
car_match <- list()
for (car in cv$car){
x <- 1
for (model in cars_object$cars_list[[cv$make[cv$car == car]]][[1]]){
car_match[[paste0(car,"#",x)]][["model"]] <- model
x <- x + 1
}
}
model_applied <-
if(nrow(dplyr::bind_rows(car_match)) >0) {
dplyr::bind_rows(car_match) %>%
mutate(car = stringr::str_replace_all(names(car_match),"#\\d",""))
} else {
data.frame(car = "", drop = FALSE)
}
model_reduced <- model_applied %>%
dplyr::group_by(car) %>%
dplyr::slice(1) %>%
dplyr::ungroup()
cv <- cv %>%
select(-model) %>%
left_join(model_reduced, by = "car") %>%
select(car, make, mpg, model)
cars_object$cars_meta <- cv
cars_object$model_applied <- model_applied
return(cars_object)
}
# Adds a new make/model combination to cars_list of the cars object
new_model <- function(cars_object, make, new){
cars_object$cars_list[[make]] <- c(new, cars_object$cars_list[[make]][[1]])
return(cars_object)
}
#******** UI ********************************************************
mod_data_ui <- function(id) {
fluidPage(
actionButton(NS(id,"new_data"), "Load Data"),
br(),
DT::dataTableOutput(NS(id, 'dt')),
br(),
actionButton(NS(id, "commit_meta"), "Commit"),
br(),
verbatimTextOutput(NS(id,"results")),
br(),
uiOutput(NS(id,"make_set")),
br(),
uiOutput(NS(id, "model_value")),
br(),
uiOutput(NS(id, "save_model")),
br(),
verbatimTextOutput(NS(id,"meta"))
)
}
shiny_ui <- function() {
navbarPage(
title = div(span("Data",
style = "position: relative; top: 50%; transform: translateY(-50%);")),
tabPanel(
"Data Management",
mod_data_ui("data")
)
)
}
#**** SERVER ***********************************************************
mod_data_server <- function(id) {
shiny::moduleServer(id, function(input, output,session){
ns <- session$ns
v <- reactiveValues()
#place holders
selectInputIDmodel <- "model"
observeEvent(input$new_data, once = TRUE, {
data <- create_data2()
v$cars <- reactive({data})
selectInputIDmodel <<- paste0("sel_model", 1:nrow(v$cars()$cars_meta))
v$model_applied <- reactive({match_cars(v$cars())$model_applied})
v$initTbl <-
dplyr::tibble(
car = v$cars()$cars_meta$car,
make = v$cars()$cars_meta$make,
mpg = v$cars()$cars_meta$mpg,
model = sapply(selectInputIDmodel, function(x){as.character(selectInput(inputId = ns(x), label = "",
choices = v$model_applied()$model[v$model_applied()$car == v$cars()$cars_meta$car[which(selectInputIDmodel == x)]],
selected = v$cars()$cars_meta$model[which(selectInputIDmodel == x)]
))})
)
})
displayTbl <- reactive({
req(input$new_data)
dplyr::tibble(
car = v$cars()$cars_meta$car,
make = v$cars()$cars_meta$make,
mpg = v$cars()$cars_meta$mpg,
model = sapply(selectInputIDmodel, function(x){as.character(selectInput(inputId = ns(x), label = "",
choices = v$model_applied()$model[v$model_applied()$car == v$cars()$cars_meta$car[which(selectInputIDmodel == x)]],
selected = input[[x]]))})
)
})
output$dt <- DT::renderDataTable({
req(input$new_data)
DT::datatable(
v$initTbl, escape = FALSE, selection = 'none', rownames = FALSE,
options = list(paging = FALSE, ordering = FALSE, scrollx = TRUE, dom = "t",
preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')
)
)
})
dt_table_proxy <- DT::dataTableProxy(outputId = "dt")
observeEvent({sapply(selectInputIDmodel, function(x){input[[x]]})}, {
DT::replaceData(proxy = dt_table_proxy, data = displayTbl(), rownames = FALSE)
}, ignoreInit = TRUE)
v$resultTbl <- reactive({
dplyr::tibble(
car = v$cars()$cars_meta$car,
make = v$cars()$cars_meta$make,
mpg = v$cars()$cars_meta$mpg,
model = sapply(selectInputIDmodel, function(x){as.character(input[[x]])})
)
})
observeEvent(input$commit_meta, {
cars_updated <- meta_table(v$cars(), v$resultTbl())
v$cars <- reactive({cars_updated})
})
# add model manually
output$make_set <- renderUI({
req(input$new_data)
make <- v$cars()$cars_meta$make
#make_sel <- unique(make)
selectInput(NS(id, "make_set"), "Select Make", multiple = FALSE, choices = make)
})
output$model_value <- renderUI({
req(input$new_data)
textInput(NS(id, "model_value"), "Add Model Name")
})
output$save_model <- renderUI({
req(input$new_data)
actionButton(NS(id, "save_model"), "Save Model", style="color: #fff; background-color: #337ab7; border-color: #2e6da4")
})
observeEvent(input$save_model,{
car <- meta_table(v$cars(), v$resultTbl()) # This is the same step as under commit
v$cars <- reactive({match_cars(
new_model(
cars_object = car,
make = input$make_set,
new = input$model_value
)
)
})
v$model_applied <- reactive({match_cars(v$cars())$model_applied})
updateTextInput(session, "model_value", value = "")
})
output$meta <- renderPrint({
req (input$commit_meta > 0)
tf <- v$cars()$cars_meta
tf %>% print(n = Inf)
})
return(reactive(v))
})
}
shiny_server <- function(input, output, session) {
v <- mod_data_server("data")
}
#********* APP *******************************
svyStudyapp_app <- function(...) {
app <- shiny::shinyApp(
ui = shiny_ui,
server = shiny_server
)
shiny::runApp(app, ...)
}
use updateSelectInput inside an observeEvent or observe function. Pass in the Shiny session object, the input ID of the selectInput element and a vector of new choices.
like this
observeEvent(input$saveModelButton, {
updateSelectInput(session, "sel_model6", choices = c("V", "Other models"))
})
I would like to be able to upload a dataset, select a set of columns, transform the selected columns (i.e. apply a function), then download the modified file. I have been trying to do so with the following code:
library(shiny)
library(DT)
library(shinyWidgets)
library(plyr)
library(dplyr)
library(RecordLinkage)
library(readxl)
cleanup <- function(x){
x <- as.character(x) # convert to character
x <- tolower(x) # make all lowercase
x <- trimws(x, "both") # trim white space
return(x)
}
ui <- fluidPage(
h2("Record Linkage Data"),
fileInput("file1", "Upload file for cleaning", accept = c("xls", "csv"), multiple = F),
actionButton(inputId = "clean", label = "Clean Data"),
downloadButton("download1", "Download file1"),
pickerInput(width = "75%",
inputId = "pick_col1",
label = "Select columns to display",
choices = colnames(file1),
selected = colnames(file1),
options = list(
`actions-box` = T,
`selected-text-format` = paste("count > ", length(colnames(file1)) - 1),
`count-selected-text` = "Alle",
liveSearch = T,
liveSearchPlaceholder = T
),
multiple = T),
DT::dataTableOutput("mytable")
)
load_path <- function(path) {
req(input$file)
ext <- tools::file_ext(path)
if (ext == "csv"){
read.csv(path, header = T)
} else if (ext == "xls" || ext == "xlsx"){
read_excel(path)
} else{
stop("Unknown extension: '.", ext, "'")
}
}
server <- function(input, output, session){
file1 <- reactive(load_path(input$selection$datapath[[1]]))
#file2 <- reactive(load_path(input$selection$datapath[[2]]))
eventReactive(input$clean, {
output$mytable <- DT::renderDataTable({
data.frame(lapply(select(file1, input$pick_col1), cleanup))
})
})
output$download <- downloadhandler(
filename = function(){
paste0(tools::file_path_sans_ext(input$filename), ".csv")
},
content = function(file){
write.csv(data(), file)
}
)
}
shinyApp(ui, server)
When I run the above code, I get the error : Error in is.data.frame(x) : object 'file1' not found. I am unsure why this is but I have been struggling to understand naming things in shiny. For example: I want to upload file1, then transform it. Do I continue to refer to file1 when I want to download it? These may seem like silly questions but I am asking because I don't know and I'm trying to learn. There seem to be lots of different approaches.
I would like to:
1. Load a file
2. Select columns (pickerInput is what I have been trying, but selectInput would suffice I suppose)
3. via action button, apply a pre-specified function to the selected columns
4. download the transformed dataset as a .csv
I've encountered some problems
It is a very silly one (it happens to all of us). You should write downloadHandler instead of downloadhandler.
The main problem: Your pickerInput is trying to select the column names of the data frame file1 when it does not exists. When you run the application the code is trying to find a file1 data frame and look its column names, but since at that time you haven't uploaded anything yet, it throws an error.
On how you read files: I am not familiar with how you read files, I suggest you do something similar than what is done in this example. https://shiny.rstudio.com/gallery/file-upload.html. Note you need to use a read.* function and point the result to another name, df in the example.
How would I solve it:
1. Set choices and selected options to NULL by default. Something like the following should work:
pickerInput(width = "75%",
inputId = "pick_col1",
label = "Select columns to display",
choices = NULL,
selected = NULL,
options = list(
`actions-box` = T,
# `selected-text-format` = paste("count > ", length(colnames(file1)) - 1),
`count-selected-text` = "Alle",
liveSearch = T,
liveSearchPlaceholder = T
),
multiple = T)
Add an updatePickerInput in the server side within an observeEvent. Something like this should work.
observeEvent(input$file1, {
req(input$file1) # ensure the value is available before proceeding
df <- read.csv(input$file1$datapath)
updatePickerInput(session = session,
inputId = "pick_col1",
choices = colnames(df),
# ... other options)
})
I haven't looked much if there are other problems with the code.
I suggest you start from the example in the link shared and start modifying it until you get what you want.
If that does not work, let me know and I can try to figure it out
Good luck!
Inside Shiny, I'm rendering a data table.
I was reading the options for the DT package, and I'm founding that to do this I need to know Javascript.
https://rstudio.github.io/DT/options.html
m = as.data.frame(matrix(round(rnorm(100, 1e5, 1e6)), 20))
datatable(m, options = list(
rowCallback = JS(
"function(row, data) {",
"var num = '$' + data[3].toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');",
"$('td:eq(3)', row).html(num);",
"}")
), callback = JS("table.order([3, 'asc']).draw();"))
I don't know a lot about JS, so I'm asking this question. My biggest number right now is: 22381, and lowest 0.
I would like to display: 22381 as 22,381. If posible: 22381 as S/.22,381.
This is how my DataTable is rendering rightnow, I'm using the options to order (desc) the revenue.
output$products <- renderDataTable(products_ss(),
options = list(
order = list(list(2, 'desc'))
))
UPDATE 1: How to apply formatCurrency?
Applying it like this gives error:
You specified the columns: itemRevenue, but the column names of the data are
output$productos_sams <- renderDataTable(productos_samsung() %>%
formatCurrency(c('itemRevenue'), currency = ' S/.',
interval = 3, mark = ',', before = FALSE),
options = list(
order = list(list(2, 'desc'))
))
I've changed your answer to match my column name.
You are looking for DT::formatCurrency
library(DT)
datatable(m) %>%
formatCurrency(c('V1', 'V2', 'V3', 'V4', 'V5'), currency = ' S/.',
interval = 3, mark = ',', before = FALSE)
Update1:
library(shiny)
library(DT)
#Use DT::dataTableOutput and DT::renderDataTable as shiny also has these functions name
shinyApp(
ui = fluidPage(fluidRow(column(12, DT::dataTableOutput('tbl')))),
server = function(input, output) {
m <- reactive({m <- as.data.frame(matrix(round(rnorm(100, 1e5, 1e6)), 20))})
output$tbl = DT::renderDataTable(
datatable(m()) %>% formatCurrency(c('V1', 'V2', 'V3', 'V4', 'V5'), currency = ' S/.',
interval = 3, mark = ',', before = FALSE)
)
}
)
As in https://rstudio.github.io/DT/shiny.html The first argument of DT::renderDT() can be either a data object or a table widget returned by datatable(). The latter form can be useful when you want to manipulate the widget before rendering it in Shiny, e.g. you may apply a formatting function to a table widget:. So you need to datatable(m()) before passing it to another step.
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´m having problems combining two features while building a data table:
I use “bSearchable” to select 1 column that I want to use the search tool to filter
I use "checkboxInput" to select the columns the user wants to see.
Both work separately, but not together. If I uncheck a column in my menu input, the data disappears - like applying a filter and no data was found. How can I fix this?
library(shiny)
runApp(list(ui=(fluidPage(
pageWithSidebar(
headerPanel('Title'),
sidebarPanel(
helpText('Text about the table'),
checkboxInput('columns','I want to select the columns' , value = FALSE),
conditionalPanel(
condition= "input.columns == true",
checkboxGroupInput('show_vars', 'Select the columns that you want to see:', names(iris[1:4]),
selected = names(iris[1:4]))
),
downloadButton('downloadData', 'Download'),width = 3
),
mainPanel(
tags$head(tags$style("tfoot {display: table-header-group;}")),
dataTableOutput("mytable1"),width = 9
)
))
)
,
server=(function(input, output) {
library(ggplot2)
library(XLConnect)
#DATA
tabel<- reactive({
iris[,c(input$show_vars,"Species"), drop = FALSE]
})
# OUTPUT
output$mytable1 = renderDataTable({
tabel()},
options = list(
aoColumns = list(list(bSearchable = FALSE), list(bSearchable = FALSE),list(bSearchable = FALSE),
list(bSearchable = FALSE),list(bSearchable = TRUE)),
bFilter=1, bSortClasses = 1,aLengthMenu = list(c(10,25,50, -1), list('10','25', '50', 'Todas')),iDisplayLength = 10
)
)
output$downloadData <- downloadHandler(
filename = function() { paste('tabela_PSU','.xlsx', sep='') },
content = function(file){
fname <- paste(file,"xlsx",sep=".")
wb <- loadWorkbook(fname, create = TRUE)
createSheet(wb, name = "Sheet1")
writeWorksheet(wb, tabel(), sheet = "Sheet1")
saveWorkbook(wb)
file.rename(fname,file)
},
)
})
))
The problem is by filtering the data iris based on input$show_vars, you are changing the number of columns of the DataTable.
However, you have defined a fixed aoColumns option, which implies your DataTable has five columns (four non-searchable, one searchable).
Therefore, when you deselect any checkbox inputs, the filtered data doesn't match the specified options. As a result, nothing is displayed.
That is, although your data in the DataTable is reactive, the options, however, are NOT reactive.
If you read the renderDataTable's document carefully, you will see that you can pass two types of variables to the options argument:
options A list of initialization options to be passed to DataTables, or a function to return such a list.
The differences are:
If you specify options as a list, Shiny assumes that the options are fixed; But since you are dynamically filtering the data based on input$show_vars, you should dynamically change the options for aoColumns as well.
If you pass a function as an argument for options, Shiny will know that the options are also reactive. Hence Shiny will also update the options when the data (in your case, the data.frame encapsulated in the reactive variable named tabel) updates.
You may already know that reactive variables are themselves functions. They are evaluated in a reactive environment and when evaluated, they return the current state/value of the data. This is why you pass tabel() instead of tabel to renderDataTable.
The solution then, is to wrap the entire options list into a reactive variable (hence a function as well). Specifically, we want to dynmaically set the aoColumns option so that the number of bSearchable toggles matches the number of columns shown in the DataTable.
Below I only show the updated server part, since there's nothing needs to be changed in the UI part.
server.R
shinyServer(function(input, output) {
library(ggplot2)
library(XLConnect)
#DATA
tabel<- reactive({
iris[,c(input$show_vars,"Species"), drop = FALSE]
})
# wrap the `options` into a reactive variable (hence a function) so that it will
# be evaluated dynamically when the data changes as well.
# `dt_options` is reactive in the sense that it will reflect the number of rows
# visible based on the checkboxInput selections.
dt_options <- reactive({
# dynamically create options for `aoColumns` depending on how many columns are selected.
toggles <- lapply(1:length(input$show_vars), function(x) list(bSearchable = F))
# for `species` columns
toggles[[length(toggles) + 1]] <- list(bSearchable = T)
list(
aoColumns = toggles,
bFilter = 1, bSortClasses = 1,
aLengthMenu = list(c(10,25,50, -1), list('10','25', '50', 'Todas')),
iDisplayLength = 10
)
})
# OUTPUT
output$mytable1 = renderDataTable({
tabel()},
options = dt_options
)
output$downloadData <- downloadHandler(
filename = function() { paste('tabela_PSU','.xlsx', sep='') },
content = function(file){
fname <- paste(file,"xlsx",sep=".")
wb <- loadWorkbook(fname, create = TRUE)
createSheet(wb, name = "Sheet1")
writeWorksheet(wb, tabel(), sheet = "Sheet1")
saveWorkbook(wb)
file.rename(fname,file)
},
)
})
(Note that I separate the UI part and server part into ui.R and server.R.)