I have a big shiny app with about 60 different inputs and it's still growing. Since I use this program a lot, I wanted the settings to be stored until next time I run the app. I made a csv-file that looks something like this:
input,value
input_a,10
input_b,#FFF000
input_c,hide
input_d,65400
I load the csv-file in ui.R and server.R with (not sure why I have to load it two times...)
config <- data.frame(lapply(read.csv(".//config.csv"), as.character), stringsAsFactors = FALSE)
and have inputs like this
sliderInput(
"input_a",
"Number of cats:",
min = 1,max = 50,
value = config[config$input %in% "input_a", "value"]
)
In server.R, I let input changes replace the value in the table and also save the table to the file
observe({
config[config$input %in% "input_a", "value"] <- input$input_a
config[config$input %in% "input_b", "value"] <- input$input_b
config[config$input %in% "input_c", "value"] <- input$input_c
config[config$input %in% "input_d", "value"] <- input$input_d
write.table(config, file = ".//config.csv", col.names = TRUE, row.names = FALSE, quote = FALSE, sep = ",")
})
I'm sure there is a better way to do this, I searched and checked the other similar questions, I started with dget and dput, but then decided to have all relevant settings in one simple file. Sorry if I missed the most relevant question when I searched.
What I don't like about this is that the program also saves the table when it loads the program, before I make any input changes.
How can I get rid of that unnecessary save every time I run the program?
I don't understand all the "reactivity" in shiny, it's still a bit to complicated for me, I don't really know anything about R or programming, just trying to optimize my program since it gets slower with every new "feature" I add.
I don't see any problem with keeping settings like that, but there might be a better way, and in anycase I would wrap it in a function like I did here.
And here is how you implement writing only "on exit" though (also please note the session parameter which is often not used):
library(shiny)
settingsdf <- data.frame(input=c("input_a","input_b","input_c"),
value=c(10,"#FF000","hide"),
stringsAsFactors=F)
setSetting <- function(pname,pval){
idx <- which(settingsdf$input==pname)
if (length(idx)==1){
print(pval)
settingsdf[ idx,2] <<- pval
}
}
shinyApp(
ui = fluidPage(
selectInput("region", "Region:", choices = colnames(WorldPhones)),
plotOutput("phonePlot")
),
server = function(input, output, session) {
output$phonePlot <- renderPlot({
if (length(input$region)>0){
setSetting("input_a",input$region)
barplot(WorldPhones[,input$region]*1000,
ylab = "Number of Telephones", xlab = "Year")
}
})
session$onSessionEnded(function() {
write.csv(settingsdf,"settings.csv")
})
},
options = list(height = 500)
)
Note that I am compressing the ui.R and server.R files into a single file which is not normally done but is nicer for these little examples.
This is not perfect code, I don't read the settings in and initialize the variables, and I use the <<- operator, which some people frown on. But it should help you along.
Update
Here is a more complex version that loads and saves the parameters, and encapsulates them for use. It is better, although it probably should use S3 objects...
library(shiny)
# Settings code
settingsdf <- data.frame(input=c("input_a","region"),
value=c(10,"Asia"),stringsAsFactors=F)
setfname <- "settings.csv"
setSetting <- function(pname,pval){
idx <- which(settingsdf$input==pname)
if (length(idx)==1){
settingsdf[ idx,"value"] <<- pval
}
}
getSetting <- function(pname){
idx <- which(settingsdf$input==pname)
if (length(idx)==1){
rv <- settingsdf[ idx,"value"]
return(rv)
} else {
return("")
}
}
readSettings <- function(){
if (file.exists(setfname)){
settingsdf <<- read.csv(setfname,stringsAsFactors=F)
}
}
writeSettings <- function(){
write.csv(settingsdf,setfname,row.names=F)
}
# ShinyApp
shinyApp(
ui = fluidPage(
selectInput("region","Region:", choices = colnames(WorldPhones)),
plotOutput("phonePlot")
),
server = function(input, output, session) {
readSettings()
vlastinput <- getSetting("region")
if (vlastinput!=""){
updateSelectInput(session, "region", selected = vlastinput )
}
output$phonePlot <- renderPlot({
if (length(input$region)>0){
vlastinput <- input$region
setSetting("region",vlastinput)
barplot(WorldPhones[,input$region]*1000,
ylab = "Number of Telephones", xlab = "Year")
}
})
session$onSessionEnded(function() {
writeSettings()
})
},
options = list(height = 500)
)
Yielding:
Related
I am new to making shiny apps. Essentially I am trying to make a shiny app that does the following:
ui allows you to load a df from your local computer
the df is read and then the user can select two other inputs from drop down menu -column names from the dataframe to plot a ggplot
gives the output ggplot
Here is what is happening
the df is read in fine
the dropdown select menu input in the ui work fine
the ggplot is just not responsive
Here is my code:
library(shiny)
library(readxl)
library(ggplot2)
library(dplyr)
ui <- fluidPage(
titlePanel("Upload Excel Sheet"),
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose Excel Sheet",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv")),
selectInput("x", "X-axis", choices = ""),
selectInput("y", "Y-axis", choices = "")
),
mainPanel(
plotOutput("plot1")
)
)
)
server <- function(input, output,session) {
data <- reactive({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read_excel(inFile$datapath)
})
observe({
updateSelectInput(session, "x", choices = names(data()))
updateSelectInput(session, "y", choices = names(data()))
})
output$plot1<- renderPlot({
data()%>%ggplot(aes(x=input$x, y=input$y))+geom_bar(stat="identity"))
})
}
shinyApp(ui = ui, server = server)
What I have tried:
I have replaced the plot output (renderPlot) with a table output (renderTable) and see that the code "reads" the df and spits out the table.
I have pulled the df in a separate piece of code (read_excel) into R and then run the ggplot command on it and it seems to be working fine.
I feel like I am missing something obvious here, and perhaps something critically basic. If possible please try the code on any excel sheet in your local dir.
Several things need to be adjusted here.
There is an extra close-paren with geom_bar(stat="identity")), removed.
ggplot2 aesthetics need to be either non-standard evaluation symbols (not workable here), or we need to use a technique described here: https://ggplot2.tidyverse.org/articles/ggplot2-in-packages.html#using-aes-and-vars-in-a-package-function-1. Namely, .data[[varname]] (with strings) or {{ varname }} (with user-provided NSE symbols). We'll use the former here.
Some optional things for more resiliency (and good code practices):
I suggest the use of req(.) liberally. See my examples. It not only handles some of the if (is.null(..)) you have to deal with, it also very well handles downstream reactivity.
You allow ".csv" but always use read_excel, I've fixed that by including a simple if.
Code.
server <- function(input, output,session) {
data <- reactive({
req(inFile <- input$file1$datapath)
if (grepl("csv$", inFile)) {
read.csv(inFile)
} else {
read_excel(inFile$datapath)
}
})
observe({
updateSelectInput(session, "x", choices = names(data()))
updateSelectInput(session, "y", choices = names(data()))
})
output$plot1<- renderPlot({
req(data(), input$x, input$y)
data() %>%
ggplot(aes(x=.data[[ input$x ]], y=.data[[ input$y ]])) +
geom_bar(stat="identity")
})
}
Extension: in addition to req, I also recommend becoming familiar with validate and need. For instance, if we update your data <- reactive(.) to be:
data <- reactive({
req(inFile <- input$file1$datapath)
if (grepl("csv$", inFile)) {
out <- readr::read_csv(inFile)
} else {
out <- readr::read_excel(inFile$datapath)
}
isnum <- sapply(out, is.numeric)
validate(
need(sum(isnum) >= 2, "We need at least two numeric columns")
)
out
})
(and no other changes anywhere else), then all dependent reactive blocks "see" the problem. For instance, if I feed a CSV with insufficient numeric columns, we see
where we would normally see
(where the plot is peeking out on the bottom).
i have a question regarding Shiny and the usage of Data frames.
I think i understood that i need to create isolated or reactive environmentes to interact with, but if i try to work with the Dataframe i get an error message:
Error in pfData: konnte Funktion "pfData" nicht finden
i tried to manipulate the dataframe by this code:
server <- function(input, output) {
observeEvent(input$go,
{
pf_name <- reactive({input$pfID})
pf_date <- reactive({input$pfDate})
if (pf_name()!="please select a PF") {
pfData <- reactive(read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=","))
MDur <- pfData()[1,15]
pfData <- pfData()[3:nrow(pfData()),]
Total = sum(pfData()$Eco.Exp...Value.long)
}
})
}
If i manipulate my Dataframe in the console it works just fine:
pfData <- pfData[3:nrow(pfData),]
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
can you help me?
Edit:
library(shiny)
ui <- fluidPage(
fluidRow(
column(6, offset =3,
wellPanel(
"Choose Mandate and Date",
fluidRow(
column(4,selectInput("pfID",label = "",
choices = list("please select a PF","GF25",
"FPM"),
selected = "please select a PF") ),
column(4, dateInput("pfDate",label="",value = Sys.Date()) ),
column(2, actionButton("go","Submit")),column(2,textOutput("selected_var"))
)
)
)
)
)
# Define server logic ----
server <- function(input, output) {
pfDataReactive <- reactive({
input$go
if (pf_name()!="please select a PF") {
pfData <- read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=",")
MDur <- pfData[1,15]
pfData <- pfData[3:nrow(pfData),]
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
pfData
output$selected_var <- renderText({paste(MDur)})
}
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Thank you
Stefan
Without a working example, it's imposible to be sure what you're trying to do, but it sounds like you need a reactive rather than using observeEvent.
Try something like
pfDataReactive <- reactive({
input$go
pfData <- read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=",")
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
pfData
})
And then use pfDataReactive() in your Shiny app's server function wherever you would refer to pfData in your console code.
The standalone reference to input$go ensures the reactive will update whenever input$go changes/is clicked/etc.
Update
There are still significant issues with your code. You've added an assignment to an output object as the last line of the reactive I gave you, so the reactive always returns NULL. That's not helpful and is one of the reasons why it "doesn't active at all"...
Second, you test for the existence of an reactive/function called pf_name when the relevant input object appears to be input$pfID. That's another reason why the reactive is never updated.
Note the change to the definition of input$pfID that I've made to improve the readability of the pfDataReactive object. (This change also probably means that you can do away with input$go entirely.)
As you say, I don't have access to your csv file, so I can't test your code completely. I've modified the body of the pfDataReactive to simply return the mtcars dataset as a string. I've also edited the code I've commented out to hopefully run correctly when you use it with the real csv file.
This code appears to give the behaviour you want,. Though, if I may make a subjective comment, I think the layout of your GUI is appaling. ;=)
library(shiny)
ui <- fluidPage(
fluidRow(
column(6, offset =3,
wellPanel(
"Choose Mandate and Date",
fluidRow(
column(4,selectInput("pfID",label = "",
# Modified to that "Pleaseselect a PF" returns NULL
choices = list("please select a PF"="","GF25", "FPM"),
selected = "please select a PF") ),
column(4, dateInput("pfDate",label="",value = Sys.Date()) ),
column(2, actionButton("go","Submit")),column(2,textOutput("selected_var"))
)
)
)
)
)
# Define server logic ----
server <- function(input, output) {
pfDataReactive <- reactive({
# Don't do anything until we have a PF csv file
req(input$pfID)
input$go
# Note the change to the creation of the file name
# pfData <- read.csv(file =paste(input$pfID,".csv",sep=""),sep=";",dec=",")
# pfData <- pfData[3:nrow(pfData),]
# Total = sum(pfData$Eco.Exp...Value.long)
# Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
# pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
# MDur <- pfData[1,15]
# If you want to print MDur in the selected_var output, MDur should be the retrun value from this reactive
# MDur
mtcars
})
output$selected_var <- renderText({
print("Yep!")
as.character(pfDataReactive())
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Next time, please, please, make more effort to provide a MWE. This post may help.
This is a good introduction to Shiny.
As I understand, eventReactive (or any reactive function) should not recalculate stuff whose related input did not change, but this is what's happening in my case. I'm pretty sure I'm doing something wrong but I just don't know what. In essence, I have two eventReactive functions, one involves a very time-consuming calculation, and the other mainly just plotting (should be quite quick). However, even when I change some inputs for plotting, the first eventReactive function is executed too (even though it's not needed).
Here is a shortened version of my code:
server <- function(input, output) {
res_tabl <-
eventReactive(c(input$recalc, input$recalc2), # this is a time-consuming calculation
ignoreNULL = FALSE, {
prep_sim(
gg_start = input$gg_start,
gg_end = input$gg_end
)
})
threeplots <-
eventReactive(c(input$recalc, input$recalc2), # this is for plotting
ignoreNULL = FALSE, {
prep_plot(
results_to_plot = res_tabl(),
yval_opt = input$yval_opt
)
})
output$esdc_plot_comb <- renderPlot({
threeplots()[[1]]
})
output$esdc_plot_tot <- renderPlotly({
threeplots()[[2]]
})
output$esdc_plot_comb2 <- renderPlot({
threeplots()[[1]]
})
output$esdc_plot_tot2 <- renderPlotly({
threeplots()[[2]]
})
output$esdc_table <- renderDataTable({
res_tabl()
})
}
What should I do so that when I press a single Action button and I only changed input$yval_opt, only the second eventReactive content would run? (Nothing should run until I click the button.)
Less importantly – and perhaps this should be a separate question – as you can see I render each of the two returned plots twice. Is there perhaps a more efficient way to do this?
(The full code is available here.)
This was tricky.
To avoid automatic calculation at App start-up, you should set ignoreNULL = T
This works on a single condition, but not on multiple conditions using c(recalc1,recalc2)
Solution is :
eventReactive(req(isTruthy(input$recalc1) | isTruthy(input$recalc2)), ignoreNULL = T,...
Added a reactiveVal() to keep track of last calculation update
I think following Minimal Reproducible example responds to your needs :
library(shiny)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Test"),
# Sidebar with a slider inpust
sidebarLayout(
sidebarPanel(
sliderInput("vizslider",
"viz percentage:",
min = 1,
max = 100,
value = 30),
sliderInput("calcslider",
"Calculation duration (s):",
min = 1,
max = 10,
value = 2),
actionButton("recalc1", "Calc 1"),
actionButton("recalc2", "Calc 2"),
),
# Show result
mainPanel(
textOutput("result")
)
)
)
# Define server logic
server <- function(input, output) {
lastcalc <- reactiveVal(0)
run <- reactive({})
calcresult <- eventReactive(req(isTruthy(input$recalc1) | isTruthy(input$recalc2)), ignoreNULL = T, {
if (lastcalc()==input$calcslider) {return("last calculation")} else {lastcalc(input$calcslider)}
cat("Start calc for ",input$calcslider, "seconds\n")
Sys.sleep(input$calcslider)
cat("End calc \n")
paste("calculation done in",input$calcslider,"seconds")
})
output$result <- eventReactive(c(input$recalc1,input$recalc2), ignoreNULL = T, {
req(calcresult())
paste("filter",input$vizslider,"% of a ",calcresult())
})
}
# Run the application
shinyApp(ui = ui, server = server)
I'm trying to create a dashboard using shiny in R, but I'm facing some little problems
I have:
db is my data.frame with:
db$domain:chr,
db$date:chr,
db$value:num.
So I've created:
db_4 <- reactive({ subset(db,db$domain %in% input$domain &
db$date<=input$daterange[2] & db$date>=input$daterange[1]})
the inputs are:
input$domain: selectinput with multiple choices,
input$date: daterangeinput.
I'm trying to create a table that gives me the sum of the db$value, aggregated by db$date. I've tried something like:
output$table2 <- rendertable ({aggregate(db_4()["value"], by=list(db_4()["date"]), sum) })
but I get always an empty table.
Can anybody help me in solving this little issue?
Thx a lot
I would highly recommend you to read this article about debugging.
In Shiny you can use the browser() function within both reactive and render functions. It should help you locate the problem (i.e.: data has the expected structure)
It seems the problem is with the aggregate function: db_4()["date"] returns a data.frame, where you need a vector.
Solution:
library(shiny)
db <- data.frame(
domain = letters[1:3],
date = seq(
from = as.Date("2019-01-01"),
to = as.Date("2019-06-01"),
by = "1 months"
),
value = runif(12)
)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("domain", "Domain", choices = unique(db$domain)),
dateRangeInput("daterange", "Date",
min = min(db$date), max = max(db$date),
start = min(db$date), end = max(db$date))
),
mainPanel(
tableOutput("table2")
)
)
)
server <- function(input, output, session) {
db_4 <- reactive( {
subset(db,
db$domain %in% input$domain &
db$date<=input$daterange[2] &
db$date>=input$daterange[1]
)
})
output$table2 <- renderTable( {
req(db_4()) # Don't render table when db_4() is NULL
# Uncomment next line to check if everything goes as expected
#browser()
aggregate(
data.frame(value = db_4()$value),
by=list(date = as.factor(db_4()$date)),
sum
)
})
}
shinyApp(ui, server)
Also I would highly recommend sharing the code of your minimal example including some dummy data, so that it can be copy-pasted in an instant. It would increase the chances of someone answering.
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.