I know this question has been addressed multiple times and I've researched those instances that I've found, but have not found a solution that works.
I'm using an actionButton() as a Run button-- the intent is to not re-run the simulation unless the actionButton is clicked. I've used observe({ }) and isolate({ }) as I've seen discussed here, but still have several problems. I'll discuss the problems, then include the code below. The app is in early stages presently, and ONLY the second simulation on the drop down menu is partly implemented, so after starting the app select "Dorriefish growth/reproduction tradeoffs" from the selectInput() menu. Don't be concerned by any other rough spots at this time.
When the app begins, the run button behaves as expected in the sense that moving the top two sliders (S and p) does not cause the simulation to run or display output in a separate graphics device (reps.df == 1 is a special case requiring a dev.new()). At this point, input$runSim (the run button) == 0 because it has not been clicked yet.
First problem: when the run button is clicked, the graphics device opens and the simulation runs as expected, then ANOTHER graphics device opens and the same simulation displays again. Be patient while the first simulation display finishes-- a second will open on top of it. When those are closed (by clicking on their window controls), the next time the run button is pressed the same simulation is displayed a third time, then another graphics device opens and displays a new simulation run. If those graphics devices are closed, subsequent clicks on the run button behave as expected, opening one graphics device and displaying one, unique simulation run each time. Why do the first two clicks on the run button produce TWO graphics devices and repeat the first simulation run three times? (Pounds head on desk.)
Second problem: after the first time the run button is pushed, input$runSim increments and thereafter any movement of the S and p sliders causes a new graphics device to open and display a new simulation run. I don't want this behavior-- I need the simulation to display ONLY when the run button is clicked, not automatically when other input parameters are changed. I want the nominal behavior of the submitButton(), but I've read online that an actionButton with observe and isolate is the better practice. For the life of me I just cannot get it to isolate the run button function from the other sliders changing when input$runSim is greater than zero, i.e. after the first time it is clicked. There does not appear to be any obvious way to reset the value of that actionButton variable back to zero after a simulation run has been displayed, so after that first click the button no longer prevents new simulations whenever any other input variable is updated. (Pounds head some more.)
In summary: 1) why do the first two clicks on the run button each cause two graphics device windows to open, and why do they display the first simulation run three times? 2) How can I make the simulation run and display ONLY when the run button is clicked after the first time it is clicked?
Here is my (very preliminary) code:
ui.r
# filename: ui.r
# Michael Camann, Humboldt State University, 2014
################################################################################
#
# Define the user interface
#
################################################################################
library(shiny)
shinyUI(
fluidPage(
titlePanel("BIOL 330 ecological simulations"),
sidebarLayout(
sidebarPanel("",
helpText(HTML("<h3 style='text-align:center;'>Control Panel</h3>"), align='center'),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
# choose a simulation from a drop down menu
selectInput("sim", HTML("<b>Select a simulation to explore:</b>"),
# list the simulations available
c("No simulation selected (Introduction)" = "none",
"Dorriefish growth/reproduction trade-offs" = "dorriefish",
"Optimal foraging" = "optfor")
),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
conditionalPanel(condition="input.sim=='dorriefish'",
helpText(HTML("<b>Simulation model parameters:</b>")),
sliderInput("S.df", label=div(HTML("Specify <em>S</em>, the switch point mass (g) for
transition from somatic growth to reproduction:")),
min = 1, max = 50, value = 10, step=5),
sliderInput("p.df", label=div(HTML("Specify <em>p</em>, the probability of mortality
by predation in any given time step:")),
min = 0, max = 1, value = 0.12, step=0.01),
sliderInput("reps.df", label=div(HTML("Specify the number of full simulations to
run:")), min = 1, max = 100, value = 1, step=1)
),
# bottom controls common to all panels
conditionalPanel(condition="input.sim!='none'",
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
fluidRow(column(4, actionButton("runSim", "Run simulation")),
column(4, actionButton("saveSim", "Save output file")),
column(4, actionButton("printSim", "Save/print plot"))),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray')
)
),
mainPanel("",
# no model selected-- show information page
conditionalPanel(condition="input.sim=='none'",
includeHTML("www/simNoneText.html")
),
tabsetPanel(id="outTabs", type="tabs",
tabPanel("Plot", plotOutput("modl.plot")
),
tabPanel("Summary"
),
tabPanel("R Code"
)
)
)
)
)
)
server.r
# filename: server.r
# Michael Camann, Humboldt State University, 2014
library(shiny)
################################################################################
#
# Dorrie fish reproduction/growth trade-offs
#
################################################################################
# This function implements the Doriefish simulation exercise distributed in Java
# with Cain et al Ecology, 2nd ed.
# Arguments:
#
# S The switch point in grams for transitioning from growth to reproduction.
# Default = 10 g.
#
# p Probability of predation. Default = 0.12.
#
# show Logical. If TRUE (default), print and plot single simulation output. Set
# to FALSE when running multiple simulations in order to average the number
# of offspring across multiple simulations, in which case viewing the
# results of individual simulations isn't necessary.
df.sim <- function(S=10, p=0.12, show=TRUE)
{
if (S < 10 | S > 50) stop("S must be between 10 and 50 grams.")
doriefish <- 10 # cohort size
imass <- 5 # initial mass
mass <- rep(imass, doriefish) # init vector of dorrie fish mass
dead <- rep(FALSE, doriefish) # init vector of mortality status
mhistory <- matrix(dead, nrow=1, byrow=TRUE) # init a matrix of mortality history
offspring.t <- 0 # init offspring count
total.offspring <- vector() # storage vector
while(any(dead==FALSE)) # continue until all are dead
{
repro.t <- mass >= S & !dead # who reproduces during this time step?
offspring.t <- offspring.t + sum(mass[repro.t==TRUE]/5) # one offspring per 5 g body mass
total.offspring <- c(total.offspring, offspring.t) # keep track of offspring each time step
mass[!dead & !repro.t] <- mass[!dead & !repro.t] + 5 # incr mass by 5 g if not reproducing
die.t <- sapply(1:length(dead), function(x) runif(1) <= p) # stochastic selection of some for predation
dead <- dead | die.t # update the mortality vector
mhistory <- rbind(mhistory, dead) # update the mortality history matrix
}
rownames(mhistory) <- c(1:nrow(mhistory))
rval <- list(S=S, p=p, mass=mass, mhistory=mhistory, offspring.t=total.offspring,
offspring=total.offspring[length(total.offspring)], show=show)
class(rval) <- "df.sim"
return(rval)
}
print.df.sim <- function(sim) # print function for df.sim()
{
if(sim$show)
{
cat("\n\tDoriefish simulation\n\n")
cat("Switch point from growth to reproduction:", sim$S, "g.\n\n")
cat("Probability of predation:", sim$p, "per time step.\n\n")
cat("Time to cohort extinction:", length(sim$offspring.t), "time steps.\n\n")
cat("Total adult Doriefish biomass:", sum(sim$mass), "g.\n\n")
cat("Total offspring produced:", sim$offspring, "\n\n")
plot(sim)
}
}
plot.df.sim <- function(sim, sstep=FALSE, s=0.75, ...) # plot function for df.sim()
{
opar <- par(no.readonly=TRUE) # save graphics parameters
txt <- paste("Total offspring:", sim$offspring,
" Time to cohort extinction:", length(sim$offspring.t), "time steps.")
step.through <- function(sim, s=0.5, txt=txt)
{
len <- length(sim$offspring.t)
par(fig=c(0,1,0.1,0.9), xpd=NA)
plot(sim$offspring.t, type="n", xlab="Time steps", ylab="Cumulative Doriefish offspring",
xlim=c(1, max(len, length(sim$mass))))
mtext("Dorriefish living per time step (green = alive, red = dead):", side=3, at=(max(len)/2)+0.5,
line=4.2)
for(i in 1:nrow(sim$mhistory))
{
z <- rep("green", length(sim$mass))
z[sim$mhistory[i,]] <- "red"
points(seq(1,len, length=length(sim$mass)), rep(max(1.18*sim$offspring.t), length(sim$mass)),
pch=21, col="black", bg=z, cex=2.5)
lines(sim$offspring.t[1:i], type="h")
Sys.sleep(s)
}
mtext(txt, side=1, at=0, line=5, adj=0)
}
if(sstep) step.through(sim, s, txt)
else
{
par(fig=c(0,1,0.1,1), xpd=NA)
plot(sim$offspring.t, type="h", xlab="Time steps",
ylab="Cumulative Doriefish offspring", ...)
mtext(txt, side=1, at=0, line=5, adj=0)
}
par(opar)
}
################################################################################
#
# Define the shiny server
#
################################################################################
shinyServer(function(input, output) {
observe({
if(input$runSim == 0) return()
isolate({
sim <- reactive({
switch(input$sim,
dorriefish = {
df.sim(input$S.df, input$p.df, show=FALSE)
} # end case dorriefish
) # end switch(input$sim)
}) # end reactive()
output$modl.plot <- renderPlot({
switch(input$sim,
dorriefish = {
if (input$reps.df == 1)
{
dev.new()
plot(sim(), sstep=TRUE)
}
}, # end case dorriefish
) # end switch(input$sim)
}) # end renderPlot()
}) # end isolate()
}) # end observe()
}) # end shinyServer()
Thanks in advance for any advice you can offer. I'm rather new to Shiny and obviously need more time with it.
I had a look at your code and tried it myself.
I think the main problem is within your ShinyServer method. A "renderPlot" within a "reactive" within an "Isolate" within an "observe" seems like a bad idea. Surely "renderPlot" and "observe" shouldn't be encapsulated within other reactive expressions, they are reactive endpoints. Here you have a really clear overview of how to use the reactive expressions which helped me a lot: http://rstudio.github.io/shiny/tutorial/#reactivity-overview
I tried to rewrite your ShinyServer code to follow that philosophy:
shinyServer(function(input, output) {
sim <- reactive({
switch(input$sim,
dorriefish = {
df.sim(input$S.df, input$p.df, show=FALSE)
} # end case dorriefish
) # end switch(input$sim)
}) # end reactive()
output$modl.plot <- renderPlot({
if(input$runSim == 0) return()
isolate({
switch(input$sim,
dorriefish = {
if (input$reps.df == 1)
{
plot(sim(), sstep=TRUE)
}
}, # end case dorriefish
) # end switch(input$sim)
}) # end isolate
}) # end renderPlot()
}) # end shinyServer()
This seems to work fine.
Notice I also removed the
dev.new()
I don't think there's a need to open a plotting device within shiny, that's what the renderPlot function takes care of.
And as a final tip I suggest you have a look at a visual debugger for shiny (and R in general) so you can trace every bit of code: there's one within RStudio or you could install the StatET plugin for eclipse. It helps a bunch to see what gets called when, to see the reactive expressions at work.
I would recommend using actionButton with observeEvent. The minimal example would be as follow:
In your server.R file you would need the following:
shinyServer(
function(input, output) {
observeEvent(input$run, {
# simulation code
})
})
The observeEvent is then triggered by actionButton in ui.R:
actionButton("run", label = "Run Simulation")
The button is decorated with the id run. After it's hit the input$run on the server side is registered with observeEvent. Once it's triggered the function in the observeEvent is performed (# simulation code in the example).
The above is, I hope, a solution to your problem 2). It doesn't explain the problem 1) but it should solve it.
Related
I am currently working on a shiny app that runs a series of calculations on a dataset on the fly when someone presses "Calculate". The dataset is very large and a lot of calculations are made via a lapply, which allows the user to track the progress with a progress bar.
This means the generation of the output data frame can be quite slow even when there are potentially results already sitting there just waiting to be displayed. The problem I'm having is that the data is potentially quite time sensitive when something is found and therefore if the calculations take, say, 15 minutes to run, there may have been something to display on the first calculation that is 15 minutes out of date.
Is there a way that after each iteration of the lapply (or feel free to suggest another method) the app can look to see whether there is data there and immediately show it, essentially refreshing the output table after each iteration? Essentially updating the reactive value during the observe rather than after.
I've put below a short example app that may help visualise the problem I'm having:
library(shiny)
testDF <- data.frame(number = c(1:10),
letter = letters[1:10])
ui <- fluidPage(
# UI Input
numericInput(inputId = "attemptDivide", label = "Number to divide by",
value = 1, min = 1, max = 10),
actionButton(inputId = "calculate", label = "Calculate"),
# UI Output
dataTableOutput("dividedTable")
)
# Define server logic
server <- function(input, output) {
# Create a bucket for results
results <- reactiveVal()
# Observe when "Calculate" is pushed and we should work out whether the
# number is perfectly divisible by the input given
observeEvent(input$calculate, {
divisibleDF <- lapply(testDF$number, function(x) {
# Set to sleep for 1 second to simulate for the the lapply being
# bigger and taking more time than this example
Sys.sleep(1)
# Find the row we're interested in
interest <- subset(testDF, number == x)
# Find whether the number is perfectly divisible by the one given
divisible <- (x / input$attemptDivide) %% 1 == 0
# If divisible is TRUE we keep, else we return an empty data frame
if (divisible) {
return(interest)
} else {
return(data.frame(number = integer(), letter = character()))
}
}) %>%
do.call(rbind, .)
# Save the results to bucket
results(divisibleDF)
})
# Create the table
output$dividedTable <- renderDataTable({
results()
})
}
# Run the app
shinyApp(ui = ui, server = server)
Thanks in advance for any help.
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 hope I can explain this succinctly. I have an R function that runs an ecological simulation and returns a list of output (time steps, population size, mortality history, number of offspring, stuff like that). I want to slow down the graphics output under certain circumstances so that students can see population dynamics as they happen. The plot axes and title should be displayed, then in 0.5 s the first line representing cumulative offspring should appear, then 0.5 s later the second line of cumulative offspring appears, then the third 0.5 s later, until the entire cohort of new offspring has been plotted at the end of the "simulation."
The problem is that the app waits until the whole image has been rendered before displaying it, so I can't show students "slowed down" population dynamics. The output plot is blank until all of those 0.5 s delays have finished, then it renders all at once. Even the graphics calls that precede the for() loop are suppressed until after the loop finishes. The code examples below are incomplete, work in progress versions.
Here is my server code-- if you uncomment the dev.new() call in the renderPlot command the app works exactly as desired, except that output is in a new graphics device, rather than in the Shiny app plot window:
shinyServer(function(input, output) {
observe({
if(input$runSim == 0) return()
isolate({
sim <- reactive({
switch(input$sim,
dorriefish = {
df.sim(input$S.df, input$p.df, show=FALSE)
} # end case dorriefish
) # end switch(input$sim)
}) # end reactive()
output$modl.plot <- renderPlot({
switch(input$sim,
dorriefish = {
if (input$reps.df == 1)
{
# dev.new()
opar <- par(no.readonly=TRUE)
len <- length(sim()$offspring.t)
par(fig=c(0,1,0.1,0.9), xpd=NA)
plot(sim()$offspring.t, type="n", xlab="Time steps", ylab="Cumulative Doriefish offspring",
xlim=c(1, max(len, length(sim()$mass))))
mtext("Dorriefish living per time step (green = alive, red = dead):", side=3, at=(max(len)/2)+0.5,
line=4.2)
for(i in 1:nrow(sim()$mhistory))
{
z <- rep("green", length(sim()$mass))
z[sim()$mhistory[i,]] <- "red"
points(seq(1,len, length=length(sim()$mass)), rep(max(1.18*sim()$offspring.t), length(sim()$mass)),
pch=21, col="black", bg=z, cex=2.5)
lines(sim()$offspring.t[1:i], type="h")
Sys.sleep(0.5)
}
txt <- paste("Total offspring:", sim()$offspring,
" Time to cohort extinction:", length(sim()$offspring.t), "time steps.")
mtext(txt, side=1, at=0, line=5, adj=0)
par(opar)
} # end if(input$reps.df == 1)
} # end case dorriefish
) # end switch(input$sim)
}) # end renderPlot()
}) # end isolate()
}) # end observe()
}) # end shinyServer()
Here is the UI code:
library(shiny)
shinyUI(
fluidPage(
titlePanel("BIOL 330 ecological simulations"),
sidebarLayout(
sidebarPanel("",
helpText(HTML("<h3 style='text-align:center;'>Control Panel</h3>"), align='center'),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
# choose a simulation from a drop down menu
selectInput("sim", HTML("<b>Select a simulation to explore:</b>"),
# list the simulations available
c("No simulation selected (Introduction)" = "none",
"Dorriefish growth/reproduction trade-offs" = "dorriefish",
"Optimal foraging" = "optfor")
),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
conditionalPanel(condition="input.sim=='dorriefish'",
helpText(HTML("<b>Simulation model parameters:</b>")),
sliderInput("S.df", label=div(HTML("Specify <em>S</em>, the switch point mass (g) for
transition from somatic growth to reproduction:")),
min = 1, max = 50, value = 10, step=5),
sliderInput("p.df", label=div(HTML("Specify <em>p</em>, the probability of mortality
by predation in any given time step:")),
min = 0, max = 1, value = 0.12, step=0.01),
sliderInput("reps.df", label=div(HTML("Specify the number of full simulations to
run:")), min = 1, max = 100, value = 1, step=1)
),
# bottom controls common to all panels
conditionalPanel(condition="input.sim!='none'",
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray'),
fluidRow(column(4, actionButton("runSim", "Run simulation")),
column(4, actionButton("saveSim", "Save output file")),
column(4, actionButton("printSim", "Save/print plot"))),
tags$hr(style='height:2px; border-width:0; color:gray; background-color:gray')
)
),
mainPanel("",
# no model selected-- show information page
conditionalPanel(condition="input.sim=='none'",
includeHTML("www/simNoneText.html")
),
tabsetPanel(id="outTabs", type="tabs",
tabPanel("Plot", plotOutput("modl.plot")
),
tabPanel("Summary"
),
tabPanel("R Code"
)
)
)
)
)
)
So to repeat my question, how can I get the server to display the cumulative number of offspring in each time step, pause 0.5 s, than show the next until all the simulation time steps have been displayed?
Thanks, and best regards,
--Mike C.
I've tried to read your code and this is what I saw:
I suggest you to use the uiOutput() function to display output, so that the ui.R gets clearer, while putting computation in server.R
To simulate the steps while the population is growing (or anything else) you could calculate all at once in server.R, and then displaying inside a for() loop only some data, like this inside the server.R
output$plot <- renderUI({
### here calculate the results all at once and save it to a variable
result <- calculations
for(i in 1:dim(result)[1]){
Sys.sleep(0.5) # time to wait to perform each plot
### here you put the code to only get some results of
### the calculation you've perfomed earlier
ggplot(results[i,...], ...)
}
})
I've been developing a Shiny app that showcases a plot function, accepts inbuilt data or user-input CSV, produces custom plot and can output this to user as a PDF. All modules have worked fine independently of each other in development, but as a whole the app becomes unstable and regularly refuses to react to inputs. Sometimes it needs refreshing a few times just to start. All the functionality does work intermittently, so I think any bugs must relate to the complexities of the Shiny/browser interface. But as there's no feedback from Shiny (to R) or in the browser console it's almost impossible to diagnose, and it's starting to feel like a serious disincentive to using this otherwise very promising platform.
I've made the situation reproducible with a reduced script, which is also executable with runGist('db479811c6237a0741fe', launch.browser=F). I'd be really grateful for assistance from anyone who has experience of this type of issue or who understands Shiny under the hood. Advice also appreciated on ways to streamline or rework the code structure. Any comments/discussion not suitable for SO please post to reddit.
server.R
require(shiny)
# inbuilt dataset
diamonds = ggplot2::diamonds[,c(1,5,7)]
# csv datasets to input via front-end
for(i in 1:3){
dat <- diamonds[sample(1:nrow(diamonds), 200),]
write.table(dat, paste0('dat',i,'.csv'), sep=',',row.names=F, col.names=T)
}
diamonds = diamonds[sample(1:nrow(diamonds),200),]
# global variables
inbuilt = FALSE # whether currently using inbuilt data or not
datapath = '' # to chech current against previous to see if new dataset input
pagereset = FALSE # to reset when inbuilt de-selected
# function to 'plot' welcome instructions
welcome <- function(){
plot.new(); plot.window(xlim=c(0,100), ylim=c(0,100))
text(10,80,"Please input CSV file data with 3 numerical columns", cex=2, pos=4)
text(10,65,"Use the inbuilt dataset and the csv files in the app folder..", cex=1.5, pos=4)
text(10,50,"check app's reliability and how often commands fail", cex=1.5, pos=4)
text(10,35,"output to PDF", cex=1.5, pos=4)
text(10,20,"how stable is the app for you?", cex=1.5, pos=4)
}
shinyServer(function(input, output, session) {
# REACTIVE FUNCTION
plotInput = reactive({
# import data from inbuilt (internal) or a user-input CSV
# first must check if reactive is triggered by new data or not:
newdata = FALSE # initialised
if(input$inbuilt != inbuilt){ # inbuilt data option toggled
if(input$inbuilt) { # inbuilt selected
inbuilt <<- TRUE # update global
d <<- diamonds
newdata = TRUE
} else{ # inbuilt de-selected.
inbuilt <<- FALSE # update global
d <<- NULL # return splashscreen
pagereset <<- TRUE # would now crash so refresh app instead
}
} else { # input doesn't relate to inbuilt dataset
if(!input$inbuilt){ # inbuilt unselected
if(is.null(input$file1)) { # if null no input received yet
d = NULL # so reactive will return splash-screen
} else { # data has been input before
if(input$file1$datapath != datapath){ # new dataset just received
datapath <<- input$file1$datapath # update global
d <<- read.csv(datapath, header=TRUE, sep = ',') # update global
newdata = TRUE
#Sys.sleep(2) # allow file-upload aanimation to finish
# reset file handler in page
session$sendCustomMessage(type = "resetFileInputHandler", "file1")
} else NULL # new input not dataset-related
}
}
}
# reset/null javascript command - to reset app after inbuilt
# dataset is de-selected, as the script crashes otherwise..
reset_js = ifelse(pagereset, "window.location.reload()", '')
reset_js = paste("<script>", reset_js,";</script>")
if(pagereset) {
pagereset <<- FALSE
return(list(resetpage = reset_js, plot = plot.new())) # reset and null plot
}
# no data input so return splash-screen
if(is.null(d)) return(list(resetpage = reset_js, plot = welcome()))
# NORMAL PLOT
# # stroke around polygons
if(input$border != 'none') border = input$border else border = NA
# PDF handling (save file locally to be passed forward)
if(input$returnpdf){
pdf("plot.pdf", width=as.numeric(input$w), height=as.numeric(input$h))
symbols(d[[1]], d[[2]], circles=sqrt(d[[3]]), inches=as.numeric(input$inches),
bg='#ff000020', fg=border)
dev.off()
}
# return plot and reset instruction in list
list(
resetpage = reset_js,
plot = symbols(d$carat, d$depth, circles=sqrt(d$price), inches=as.numeric(input$inches),
bg='#ff000020', fg=border)
)
}) # end reactive
# OUTPUT ELEMENTS
# PDF file
output$pdflink = downloadHandler(
filename <- "shiny_plot.pdf", # default browser save filename
content <- function(file) file.copy("plot.pdf", file) # call pre-saved pdf
)
# plot
output$plot = renderPlot({ plotInput()$plot })
# reset instruction
output$reset = renderText({ plotInput()$resetpage })
})
ui.R
require(shiny)
fluidPage(
titlePanel("Stability testing"),
sidebarLayout(
sidebarPanel(
# this css just resets the CSV upload function
tags$head(
tags$script('
Shiny.addCustomMessageHandler("resetFileInputHandler", function(x) {
var id = "#" + x + "_progress";
var idBar = id + " .bar";
$(id).css("visibility", "hidden");
$(idBar).css("width", "0%");
});
')
),
# inputs
h4('Input options'),
p("Chose inbuilt dataset or upload a CSV:"),
checkboxInput('inbuilt', 'Inbuilt dataset (app resets when de-selected)', FALSE),
fileInput('file1', '', accept = 'text/comma-separated-values'),
# PDF output
h4('PDF output'),
p("Buggy: plot disappears, but link still downloads last plot. Sometimes after download app crashes"),
checkboxInput('returnpdf', 'Save plot to PDF?', FALSE),
conditionalPanel(
condition = "input.returnpdf == true",
strong("PDF size (inches):"),
sliderInput(inputId="w", label = "width:", min=3, max=20, value=12, width=100, ticks=F),
sliderInput(inputId="h", label = "height:", min=3, max=20, value=9, width=100, ticks=F),
downloadLink('pdflink')
),
# plot layout
h4('Plot options'),
selectInput(inputId="border", label="Outline colour:", choices=list(black='black', white='white', none='none'), width=150, selected='black'),
sliderInput(inputId="inches", label = "Circle size (higher values can crash the app)", min=0.05, max=.5, value=.2, width=150)
),
mainPanel(
htmlOutput('reset'), # reset command (when inbuild dataset de-selected)
imageOutput('plot')
)
)
)
I am trying to automate the interaction of a Shiny app so it displays a series of results while incrementing through a predetermined range of inputs, without having to repetitiously count and change input values. This automation will provide a systematic view of a set of inputs, such as displays of refreshed price charts for selected stocks, or plots of current performance indicators for real-time processes that are being monitored.
This is similar to question [Update graph/plot with fixed interval of time] (Update graph/plot with fixed interval of time) which runs a loop with a timer. Extending that approach, my objective is to:
a) Automatically set the invalidateLater pause high (1 hour) to effectively stop the cycle after a fixed (5) set of displays, pending new user input to restart it.
b) [When I can do that, I will add a counter-based control to cycle through a set of input$obs before it stops. For simplicity, that step, which has the same error and presumably same solution, is omitted here.]
Using the above referenced toy example, the following script does repeatedly cycle through its 5 displays, but it yields this error rather than changing the pause interval.
Listening on port 8100
Error in hist.default(dist, main = paste("Last Histogram count =", as.numeric(updater()), :
'x' must be numeric
as.numeric(autoControl())
Error: could not find function "autoControl"
I can not find the reactive conductor, reactiveValues or other methods that this task requires. Thank you for your help.
library(shiny)
updates <- 0
updater <- function(){ updates + 1 }
runApp(list(
ui = pageWithSidebar(
headerPanel("Hello Shiny!"),
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
max = 1000,
value = 50)
,
selectInput(inputId = "secPause",
label = "Seconds between displays:",
choices = c(1, 2, 3, 60*60),
selected = 2)
),
mainPanel(
plotOutput("distPlot")
)
),
server =function(input, output, session) {
updateTracker <- reactive( {
invalidateLater(as.numeric(input$secPause) * 1000, session)
updates <<- as.numeric(updater())
})
autoControl <- reactive( {
if(updateTracker() <= 5)
secPause <<- input$secPause
else
secPause <<- 60*60
return(secPause)
})
output$distPlot <- renderPlot( {
if(updateTracker() <= 5) {
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist, main = paste("Histogram count =" , updateTracker()))
}
else {
updates <<- 0
hist(dist, main = paste("Last Histogram count =",
as.numeric(updater()), "with secPause =",
as.numeric(autoControl())))
}
})
}
))
You are getting the error because the hist distribution is defined inside the if clause, but you are using it (after 5 intervals) inside the else clause, where it is not defined. That's why it works for the first 5 intervals.
if(updateTracker() <= 5) {
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist, main = paste("Histogram count =" , updateTracker()))
}
else {
updates <<- 0
hist(dist, main = paste("Last Histogram count =",
as.numeric(updater()), "with secPause =",
as.numeric(autoControl())))
}
After I moved the dist to before the if condition, I got your cycling to work. (I also split your code into UI.R and server.R to make it more manageable.) Not pasting here since it is essentially the same code, but you can find the working version of code in this gist.