2
votes

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.

2
Sounds like you want a submitButton rather then an actionButonjdharrison
jdharrison-- that's what I thought too, but the UI doesn't display properly when I use a submitButton, and I think I understand that an actionButton is more "robust" and flexible than a submitButton. Certainly the UI works when I use an actionButton.Mike C.

2 Answers

3
votes

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.

1
votes

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.