I have a graph that I only want to display if it wouldn't take too long to render, otherwise I want to have a button the user can press to render it.
My problem is that the graph renders before being hidden in some circumstances.
Open this minimally reproduced example up and try the following steps
UI.r
shinyUI(fluidPage(
titlePanel("when to render"),
fluidRow(
column(2,radioButtons("TheChoice", label= "choices",
choices = list("only one" = 1,"just two" = 2,"three" = 3,"all four" = 4),selected = 1))
),
fluidRow(
mainPanel(tabsetPanel(tabPanel("Blank page",NULL),
tabPanel("plot output",
verbatimTextOutput("validRows"),
conditionalPanel(
condition = "output.validRows < 3",
plotOutput("thePlot")
))
)
)
)
))
Server.r
library(shiny)
library(ggplot)
library(reshape2)
`[` <- function(...) base::`[`(...,drop=FALSE)
myTable <- matrix(1:20,ncol=4)
colnames(myTable) <- c("Index","catone","cattwo","catthree")
rownames(myTable) <- c("first","second","third","fourth","fifth")
shinyServer(function(input, output,session) {
TableIndex <- reactiveValues(X = NULL)
observe({
print("updating TableIndex")
if(input$TheChoice == 1) TableIndex$X <- 1 else TableIndex$X <- 1:input$TheChoice
})
graphedTableData <- reactive({
return(myTable[TableIndex$X,2])
})
dataSubset <-
reactive({
print(paste0("calculating subset with ", length(TableIndex$X)," rows"))
renderedData <- graphedTableData()
renderedData2 <- melt(renderedData,id=rownames(renderedData))
colnames(renderedData2)<-c("catone","cattwo","catthree")
return(renderedData2)
})
output$thePlot <- renderPlot({
print(paste0("Rendering Plot with ", length(TableIndex$X)," rows"))
theSubset <- dataSubset()
ggplot(data=theSubset,aes(x=catone,y=cattwo,colour=catthree)) +
geom_line()
})
output$validRows <- reactive({print("updating validRows");length(TableIndex$X)})
})
click on the plot output tab: a plot renders and is then output (with one data row used)
select choice 2: a plot renders and is then output (with two data rows used)
select choice 3: a plot renders and is THEN hidden :(
select choice 4: nothing is rendered/output (good)
now going between 3 and 4, nothing happens (good)
By looking at the console output, you can see that "TableIndex" is updated first, so it seems like this should never render with choice 3, since the plot would immediately disappear before it was updated. Presumably, that updating already triggered the reactive() and renderPlot() though.
How do I prevent these functions from executing in this order? I know I could just short circuit the reactive() and renderPlot() by checking TableIndex$X in the first line, but that seems hacky and I'm just learning shiny so I'm hoping for a cleaner solution
Bonus points if you implement the appearance of a render button instead of displaying nothing for choices 3 and 4. I've yet to attempt that but I believe it would be something with renderUI() ?
(TableIndex is calculated separately and then accessed in this way because in real life, it is finding the relevant indices from a large table and then applying those same indices to other tables. Assume that the index finding is fast)
Related
I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input.
I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput, and I have an observe that will feed them with the content using renderValueBox.
My problem is: the code in the renderValueBox, for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop.
Here is a min reprex:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
# Function
compute <- function(id)
{
print(paste("Compute ", id))
return(id)
}
# UI
ui = shinyUI(fluidPage(
titlePanel("Compare"),
useShinydashboard(),
sidebarLayout(
sidebarPanel(
numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
),
mainPanel(
uiOutput("boxes")
)
)
))
# Server
server = shinyServer(function(input, output, session) {
data <- reactiveValues(
ids = list()
)
output$boxes <- renderUI({
print("boxes")
box_list <- list()
id_list <- list()
for(id in 1:(input$numitems)) {
id_box <- paste0("box_", id)
print(paste("boxes - ", id_box))
id_list <- append(id_list, id_box)
box_list <- append(
box_list,
tagList(
shinydashboard::valueBoxOutput(id_box)
)
)
data$ids <- id_list
}
print("boxes end")
fluidRow(box_list)
})
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
}
print("end observe")
})
})
# Run
shinyApp(ui = ui , server = server)
Here is the result:
And the console output:
As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.
I tried using force, computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.
Do anyone know a way to force execution during the loop ? Or see another way of achieving the same result ?
Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.
Anyway, one way to correct this (found here) is to encapsulate the render inside the local function, like this:
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
local({
tmp <- id_box
output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
})
}
print("end observe")
})
Now the compute is still called after the end of the observe, but the tmp variable has the correct value:
The result is what I wanted:
For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.
I am creating a shiny app to analyze data in a database. I have set up a slider bar to select a range of values and also have two input boxes to adjust the range on the sliders.
In my simplified code below, the slider works fine, but when you try to update the slider by inputting numbers, I get the following error:
Error: Result must have length 10, not 0
The slider itself still works fine and does what it should in tandem with the selectInput, but as soon as you try to input a number into min or max and hit update, it gives back this error.
Looking online it seems this might be a problem with dplyr/filter(), but I couldn't really find any solutions for my problem and I'm not really sure if that's actually the problem here.
Below is some simplified code with some dummy data. For the slider, I am using the code found here to update the values: R shiny - Combine the slider bar with a text input to make the slider bar more user-friendly
library(shiny)
library(ggplot2)
library(readxl)
library(DT)
library(dplyr)
#Fake Data
MSGRAIN <- data.frame("Year" = c(2018,2018,2018,2017,2016,2010,2010,2000,2000,2000),
"SiteNameNew" = c('A','B','B','B','C','C','C','C','D','D'),
"RiverMile" = 550:559)
ui <- fluidPage(
# Selection Bar
fluidRow(
#Select by River Mile (Manual Input)
column(5,
controlledSliderUI('RiverMile')
),
#Select By Site
column(5,
selectInput("SiteNameNew",
"Site Name:",
c("All", unique(as.character(MSGRAIN$SiteNameNew))
)
)
),
column(6,h4(textOutput('test'))
),
#Create a new row for the table
DT::dataTableOutput("table")
)
)
server <- function(input, output, session) {
range <- callModule(controlledSlider, "RiverMile", 550, 559, c(550,559)
)
range$max <- 559
# Current Year
cyear <- as.numeric(format(Sys.Date(), "%Y"))
# Output to show if selected area has been tested in the last 5 years
output$test <- renderText({
data <- MSGRAIN %>%
filter(SiteNameNew == input$SiteNameNew,
RiverMile >= range$min,
RiverMile <= range$max
)
if (max(data$Year) >= cyear-5){
"This site has been tested in the last 5 years."
} else if (max(data$Year) <= cyear-5){
"This site has not been tested in the last 5 years."
} else {
"Cannot Determine"
}
})
output$table <- DT::renderDataTable(DT::datatable({
data <- MSGRAIN
# Sorts data based on Site Name selected
if (input$SiteNameNew !="All"){
data <- data[data$SiteNameNew == input$SiteNameNew,]
}
# Sorts data based on River Mile selected
if (range !="All"){
data <- data[data$RiverMile >= range$min & data$RiverMile <= range$max,]
}
# Show Data Table
data
})
)
}
# Run the application
shinyApp(ui = ui, server = server)
I think it might be a combination of the code from the link + my code causing the issue, but I am new to shiny and not really sure where things are going wrong. I only have a pretty general understanding of how the observeEvent code is working to update my slider.
I'm stuck on the final, and most critical step in building out my first pretty basic web-app using Shiny, and I'm struggling with what I think should be a pretty basic task. The idea is for the app to observe two inputs and then output a single value from a dataframe based on both of them.
Example Code to make it very clear.
Server
DF X Y Z
1 A B C
2 C D E
3 F G H
UI
InputA: Row - 2
InputB: Column - Z
Output: E
At the moment I've run into a total wall with this because I'm not getting an error. The App starts without a problem, and every other widget on this tab and others are working. At the moment, the text boxes just aren't doing anything. One can type values into the first two, but then nothing outputs and no error message is provided. It's frustrating because it feels at though I'm missing something very obvious.
Here is the actual code
Server
server <- function(input, output, session) {
#Download Data and create data table.
rlwin <- read.csv("rlwinClean.csv")
...
observe({
Lead <- as.character(input$Lead)
CalcTime <- as.character(input$CalcTime)
addtext <- paste(rlwin[rlwin$Time == CalcTime, Lead])
updateTextInput(session,"winProbability", value=addtext)
})
}
...
The UI:
ui <- (navbarPage(theme=shinytheme("sandstone"), title=h3("Rocket League Win Probability"),
#Tab1 ----
tabPanel("Win Probability Model",
#The Plot
plotOutput("modPlot", height="800px"),
#Probabililty Calculator
h4("Win Probability Calculator"),
textInput(inputId="CalcTime", label="Enter Time on Clock Remaining in Game", placeholder="0:00 to 5:00"),
textInput(inputId="Lead", label="Enter Lead or Deficit", placeholder="-4 to 4"),
br(""),
textInput(inputId="winProbability",label="Win Probability",placeholder="50%"),
br("")
),
...
Edit: Found an answer to this. It's probably a bit long winded
Server
...
observeEvent(input$runCalc,{
time <- subset(react, GameClock == input$CalcTime)
all <- subset(time, select = input$Lead)
val <- paste(all)
updateTextInput(session, inputId = "probText", value = val)
})
....
UI was effectively unchanged
Try assigning your observer
my_observer <- observe({ .... })
The following code works for me. Note that I have renamed some of your inputs because it was difficult to keep track of where your inputs were supposed to be used.
The error was occurring because addText in your original code was empty (the logical conditions returned no records). You will see below how I printed output to the console to debug this.
library(shiny)
ui <- (navbarPage( title=h3("Rocket League Win Probability"),
#Tab1 ----
tabPanel("Win Probability Model",
textInput(inputId="column_name", label="Column name", placeholder="X, Y or Z"),
textInput(inputId="row_number", label="Row number", placeholder="1, 2 or 3"),
br(""),
textInput(inputId="winProbability",label="Win Probability",placeholder="50%"),
br("")
)
))
server <- function(input, output, session) {
#Download Data and create data table.
rlwin = data.frame(DF = c(1,2,3), X = c("A","B","C"), Y = c("B","D","G"), Z = c("C","E","H"))
observe({
col_name <- as.character(input$column_name)
row_num <- input$row_number
addtext <- paste(rlwin[row_num, names(rlwin) == col_name])
# print("debug print out")
# print(col_name)
# print(row_num)
# print(addtext)
updateTextInput(session,"winProbability", value=addtext)
})
}
shinyApp(ui, server)
The aim of this exercise is to allow users to compare two different models based on their inputs. To do this, I have created an action button that asks users to specify their base model, and a reset button that takes the dataset back to before the baseline was added. The "base" logical determines whether the user wishes to include the base or not.
Once the add baseline actionbutton is clicked, the current state of the data.frame is saved and grouping variable is renamed with "baseline" added before it (using paste). Users can select a different model which renders in comparison to this static base.
For some reason, I cannot get the observe event to change the dataset. The observe event creates the baseline dataset fine (tested with print() ), however, the if() function does not alter "data" and therefore stops the base added to the ggplot. The code is written like this for two reasons. 1) by including the if() function after the observe event, any further changes to data only changes "data", it then gets added to the unchanged baseline data. 2) Also allows for the creation of the reset button which simply resets the data.frame to before the rbinding took place.
This small issue has infuriated me and I cannot see where I am going wrong. Cheers in advance for any help people can provide. There are simplier ways to do this (open to suggestions), however, the iris data is only an example of the function, and the actual version is more complex.
library("ggplot2")
if (interactive()) {
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
output$plot <- renderPlot({ # create plot
base <- "no" # create baseline indicator which we can change once the observeevent below is changed
data <- iris
data <- iris[which(data$Species == input$rows),] # Get datasubset based on user input
observeEvent(input$base, { # If base is Pressed, run code below:
baseline <- data # Make Baseline Data by duplicating the users' specification
baseline$Species <- paste("Baseline",
data$Species, sep = "_") # Rename the grouping variable to add Baseline B4 it
base <- "yes" # Change our indicator of whether a baseline had been made to yes
}) # Close observe Event
observeEvent(input$reset, {
base <- "no" # This is placed before the rbind so that if we want to reset it will stop the merging of the two dataframes before it happens.
})
if (base == "yes") {
data <- rbind(data, baseline) # Run once the observe event has changed baseline to yes.This is kept seperatel that way any subsequent changes to data will not effect
# the final data. This command will simple add the base onto the changed "data" before plotting
}
observeEvent(input$reset, {
base <- "no"
})
ggplot(data, aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species)) + # variable = each dataset selected, value = respective values for that model
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
}) # Close Render Plot
} # Close Serve Function
shinyApp(ui, server)
}
EXAMPLE TWO WITH REACTIVE OBJECT
library(shiny)
library(ggplot2)
library("tidyr")
library("dplyr")
library("data.table")
# Lets make a fake dataset called "Data". Has 4 variable options and
the Ages each data point relates to.
Ages <- 1:750
Variable1 <- rnorm(n=750, sd = 2, mean = 0)
Variable2 <- rnorm(n=750, sd = 1, mean = 2)
Variable3 <- rnorm(n=750, sd = 8, mean = 6)
Variable4 <- rnorm(n=750, sd = 3, mean = 3)
Data <- as.data.frame(cbind(Ages, Variable1, Variable2, Variable3,
Variable4) )
### UI
ui <- fluidPage(
checkboxGroupInput(inputId = "columns",
label = h4("Which Variables would you like in your
model?"), # Input Checkbox
choices = c("Variable1", "Variable2", "Variable3",
"Variable4")),
plotOutput(outputId = "plot"),
# Lets have our plot
actionButton("base", "Create baseline"),
# Baseline action
actionButton("reset", "Reset baseline") # Reset Action
) # Close UI
server <- function(input, output) {
output$plot <- renderPlot({
validate(need(!is.null(input$columns), 'Please tick a box to show a
plot.')) # Place a please choose columns for null input
data <- gather(select(Data, "Ages", input$columns), variable, value, -
Ages) ## Just doing a little data manipulation to change from wide to
long form. This allows for calculations down the track and easier
plotting
# Now we can modify the data in some way, for example adding 1. Will
eventually add lots of model modifications here.
data$value <- data$value + 1
rVals <- reactiveValues() # Now we create the reactive
values object
rVals[['data']] <- data # Making a reactive values
function. Place Data as "data".
observeEvent(input$base,{
baseline <- data
baseline$variable <- paste("Baseline",
baseline$variable, sep = "_")
# Rename Variables to Baseline preamble
rVals[['baseline']] <- baseline
# Put the new data into the reactive object under "baseline"
})
observeEvent(input$reset,{ # Reset button will wipe the
data
rVals[['baseline']] <- NULL
})
if(!is.null(rVals[['baseline']])) # if a baseline has been .
created, then
{rVals[['final']] <- bind_rows(rVals[['data']], rVals[['baseline']])
# Here we can simply bind the two datasets together if Baseline exists
} else {rVals[['final']] <- rVals[['data']]}
# Otherwise we can use keep it as it is
## Make our Plot !
ggplot(rVals[['final']], aes(x=Ages, y = as.numeric(value), colour =
variable)) + # variable = each dataset selected, value = respective
values for that model
labs(x="Age", y="value") +
geom_line()
}) ## Close the render plot
} ## Close the server
shinyApp(ui, server)
You have observer inside reactive expression, i have seen this causing problems on number of occasions when i was correcting shiny code. Create reactive expression (your plot function) and observers only to specify which is the baseline value of species (character string) then feed this to filtering data inside the plot function:
library(shiny)
library(ggplot2)
ui <- fluidPage(
selectInput("rows", label = h3("Choose your species"),
choices = list("setosa", "versicolor", "virginica")
),
actionButton("base", "Create baseline"),
actionButton("reset", "Reset baseline"),
plotOutput(outputId = "plot")
) # close fluid page
server <- function(input, output) {
rVals = reactiveValues()
rVals[['data']] = iris
rVals[['baseline']] = NULL
output$plot <- renderPlot({
# here we duplicate table to manipulate it before rendering
# the reason for duplicate is that you dont want to affect your
# base data as it may be used elsewhere
# note that due to R's copy-on-write this may be expensive operation and
# have impact on app performance
# in all cases using data.table package is recommended to mitigate
# some of the CoW implications
render.data = rVals[['data']][rVals[['data']][['Species']] %in% c(rVals[['baseline']],input$rows),]
# here manipulate render.data
# and then continue with plot
ggplot(data=render.data,
aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species,group=Species)
) +
labs(x="Hypothetical X", y="Hypothetical X") +
geom_line()
})
observeEvent(input$base,{
rVals[['baseline']]=input$rows
})
observeEvent(input$reset,{
rVals[['baseline']]=NULL
})
}
shinyApp(ui, server)
I have encountered this problem while developing an app, and reproduced it here in a simplified script using Fruits df.
Basically, i have selectInput box to select a Year, which is a column in Fruits. I create unique list of Years, and feed it into selectInput box.
Then, ideally, i wanted my plot to display only the records for the year I selected. However, as you'll see in my code - the second you uncomment a block of 3 lines to accomplish that, - the plot stops displaying even though there doesn't seem to be any errors. Anybody knows why is this? Thanks in advance!!!
Related question - while debugging this i saw that the input$explore_year is at first "Null". I'm trying to handle this in the code but not sure why the selected="2010" doesn't take care of it automatically.
library(shiny)
library(googleVis)
library(DT)
listOfFruits <- sort(unique(Fruits$Year), decreasing = FALSE)
ui <- fluidPage(title = "Fruits Bug Recreated",
fluidRow(
column(3,
wellPanel(
uiOutput("choose_year"),
br()
)),
column(9,
tags$hr(),
htmlOutput("view")
)),
fluidRow(DT::dataTableOutput("tableExplore"))
)
server <- function(input, output) {
output$view <- renderGvis({
#Uncomment these 3 lines to see how the plot stops displaying.
# local_exloreYear <- input$explore_year
# if (is.null(local_exloreYear)) {local_exloreYear <- "2010"}
# FruitsSubset <- subset(Fruits, Year == local_exloreYear)
#------------I wanted to use the commented line below instead of the
#that follows
#gvisBubbleChart(FruitsSubset, idvar="Fruit",
#-------------
gvisBubbleChart(Fruits, idvar="Fruit",
xvar="Sales", yvar="Expenses",
colorvar="Year", sizevar="Profit",
options=list(
hAxis='{minValue:70, maxValue:125, title:"Sales"}',sortBubblesBySize=TRUE,
vAxis='{title: "Expenses",minValue:60, maxValue:95}'
))
})
# Drop-down selection box for dynamic choice of minutes in the plans to compare
output$choose_year <- renderUI({
selectInput("explore_year", "Select Year", as.list(listOfFruits),selected ="2010")
})
output$tableExplore <- DT::renderDataTable(DT::datatable({
FruitsSubset <- subset(Fruits, Fruits$Year == input$explore_year)
myTable <-FruitsSubset[,c(1,2,3,4,5,6)]
data <- myTable
data
},options = list(searching = FALSE,paging = FALSE)
))
}
shinyApp(ui = ui, server = server)
Like i wrote in the comments you can solve it by make the rendering conditional on the input being non-NULL.
output$view <- renderGvis({
if(!is.null(input$explore_year)){
...
}
})
Nevertheless, I donĀ“t think it is really intended that you have to do that, as in other render functions it is not required e.g. in the DT::renderDataTable(), where you also use the same input (being NULL initially).
Therefore, I would suggest reporting it as a bug.