Chad Palmer
Chad Palmer

Reputation: 11

Keep a running value in Shiny

I am an environmental scientist trying to create a harvest simulation for oysters. I want the simulation to display two maps, one for showing the current oyster populations, and one for showing no-take zones (sanctuaries). Clicking on a plot on the map (each one has an invisible marker) should do something different depending on which map is showing. Clicking with the oyster population map should cause the population to update with oyster harvest occurring in the clicked plot. Clicking on the sanctuary map should cause the clicked plot to change is designation of open or closed.

The issue, as far as I can tell, is that all of these values reset every time an input is clicked. For example, it does not matter which map was showing previously, the "showSanctuary" variable, which is supposed to display F is the population map is up and T if the sanctuary map is up, is always set to False, its starting value, whenever a new input is clicked. The oyster population vector and sanctuary vector appear to do the same thing. How can I prevent these variables from resetting back to their starting values?

Also, I am very new to this forum, and I am not sure of the etiquette. I am going to post all of my code below absent the two scripts where I store my functions (I am certain they are not the issue), but it is a fairly lengthy program. This question relates to code between lines 70 and 152. I will post only those lines first, with the full script below it. Sorry again if that is not typical etiquette.

Lines where problem is occurring (this is inside the Server function):

#Make Reactive Values
  offLim <- reactiveValues()
  offLim = 0
  
  showSanctuary <- reactiveValues()
  showSanctuary = F
  
  myOutputs <- reactiveValues()
  myOutputs$outHarvTime = 0
  myOutputs$outSacksTaken = 0
  myOutputs$outAvgSize = 0
  
  #React to click event
  observeEvent(input$map_marker_click, {
    click<-input$map_marker_click
    if(is.null(click))
      return()
    
    xClk=trunc(click$lng*1000)/1000
    yClk=trunc(click$lat*1000)/1000
    
    xCor = which(coord$x == xClk)
    yCor = which(coord$y == yClk)
    myPlot = intersect(xCor, yCor)
    
    if(showSanctuary == T){
      if(offLim[myPlot]==1){
        offLim[myPlot]=0
      }else if(offLim[myPlot]==0){
        offLim[myPlot]=1
      }
      myMap <- makeSanctuaryMap(offLim)
      myOutputs$finalMap = myMap
    }
    else{
      myHarvLim = input$amount
      myMaxTime = input$effort
      myMinSize = input$size
      returnShells = input$shell
      param = c(myHarvLim, myMaxTime, myMinSize, myPlot, returnShells)
    
      newOysters = oysters
      newDens = myDens
      newShell = myShell
    
      outVar<-localUpdate(param, newOysters, myMaxDens, newDens, newShell, nplts)
      oysters = outVar$oysters
      myDens = outVar$myDens
      myShell = outVar$myShell
    
      myOutputs$outAvgSize = outVar$avgSize
      myOutputs$outHarvTime = outVar$harvTime
      myOutputs$outSacksTaken = outVar$sacksTaken
      myOutputs$finalMap = outVar$myMap
    }
  })
  
  observeEvent(input$update,{
    #Make sanctuary variables. Needs to be reactive to be global
    showSanctuary = F #Whether the Map Currently Displays Sanctuary Areas
    offLim = vector(length=nplts) #1 if plot is a Sanctuary or 0 if not
    offLim[] = 0
    offLim[!cond]<-NA
    
    myOutputs$outAvgSize = NA
    myOutputs$outHarvTime = NA
    myOutputs$outSacksTaken = NA
    myOutputs$finalMap = myMap
  })
  
  observeEvent(input$sanctuaryMap,{
    print(showSanctuary)
    showSanctuary = T
    myMap <- makeSanctuaryMap(offLim)
    myOutputs$finalMap = myMap
  })
  
  observeEvent(input$harvestMap,{
    print(showSanctuary)
    showSanctuary = F
    myMap <- updateMap(heatVec)
    myOutputs$finalMap = myMap
  })

Full Script:

library(shiny)
library('leaflet')
library(raster)
library('sf')
library(rgdal)
source('updateFunctions.R')

effortLbl = "What is the maximum number of hours that you are willing to spend harvesting each day?"
amountLbl = "What is the maximum number of sacks of oysters you would harvest in one day?"
sizeLbl = "Select a minimum size for legal harvest (inches)"
shellLbl="Check to require culling on site"

ui<-fluidPage(
  numericInput(inputId="effort", label=effortLbl, value=8, min=1, max=16, step=1),
  numericInput(inputId="amount", label=amountLbl, value=4, min=1, max=40, step=1),
  numericInput(inputId="size", label=sizeLbl, value=3, min=1, max=6, step=1),
  checkboxInput(inputId="shell", label=shellLbl, value = FALSE),
  actionButton(inputId="update", label="Begin"),
  actionButton(inputId="sanctuaryMap", label="Set Sanctuaries"),
  actionButton(inputId="harvestMap", label="Choose Harvest Area"),
  leafletOutput("map"),
  textOutput("time"),
  textOutput("sacks"),
  textOutput("size")
)

server<-function(input, output, session){
  #Generate list of clickable coordinates
  cedKey <- readOGR(dsn=path.expand("shapefile"), layer="LC_10_Area") #Imports Cedar Key shape file
  ckCrd <- spTransform(cedKey, CRSobj = CRS("+init=epsg:4326")) #Converts shape file coordinate to longitude/latitude
  matCrd=expand.grid(x=seq(from=-83.1164,to=-83.06251,length.out=moveRow), #Generates a series of coordinates within range
                     y=seq(from=29.2169,to=29.26528,length.out=moveRow))
  df = data.frame(x = matCrd$x, y = matCrd$y)
  s = SpatialPixelsDataFrame(df[,c('x', 'y')], data = df, proj4string = crs(ckCrd))
  clp <- over(s[,c("x", "y")], ckCrd)
  cond <- !is.na(clp$Id)
  spNew<-s[cond,]
  spDf = as.data.frame(spNew)
  coord = data.frame(x=(trunc(df$x*1000)/1000), y=(trunc(df$y*1000)/1000))
  
  #Initialize oyster population variables
  nplts = 1600 #Total number of plots
  nsize = 7 #Number of size classes (including larva)
  oysters = matrix(0, nplts,nsize)
  myDens = vector(length=nplts) #Total number of oysters weighted by size
  myShell = vector(length=nplts) #The amount of dead shell (or other non-living hard substrate)
  myMaxDens = 1000 #The maximum capacity of every plot
  moveRow = sqrt(nplts) #The number of plots in a row
  
  #Initialize oyster population with randomization
  for(i in 1:nplts){
    initMin = c(20,20,5,5,0,0) #Minimum number of oysters of each size at game start
    initMax = c(60,40,30,20,10,5) #Maximum number of oysters of each size at game start
    oysters[i,1:6]=runif(6, initMin, initMax)
    oysters[i,7]=sum(oysters[i,1:6]*4)
  }
  oysters[!cond,]<-NA
  for(i in 1:nplts){
    myDens[i]=sum(oysters[i,1:6]*c(1:6))
    myShell[i]=0.2*myDens[i]
  }
  
  #Set values of outputs before initial update
  heatVec = vector(length=nplts)
  for(i in 1:nplts){
    heatVec[i] = (100*myDens[i])/myMaxDens
  }
  myMap = updateMap(heatVec)
  
  #Make Reactive Values
  offLim <- reactiveValues()
  offLim = 0
  
  showSanctuary <- reactiveValues()
  showSanctuary = F
  
  myOutputs <- reactiveValues()
  myOutputs$outHarvTime = 0
  myOutputs$outSacksTaken = 0
  myOutputs$outAvgSize = 0
  
  #React to click event
  observeEvent(input$map_marker_click, {
    click<-input$map_marker_click
    if(is.null(click))
      return()
    
    xClk=trunc(click$lng*1000)/1000
    yClk=trunc(click$lat*1000)/1000
    
    xCor = which(coord$x == xClk)
    yCor = which(coord$y == yClk)
    myPlot = intersect(xCor, yCor)
    
    if(showSanctuary == T){
      if(offLim[myPlot]==1){
        offLim[myPlot]=0
      }else if(offLim[myPlot]==0){
        offLim[myPlot]=1
      }
      myMap <- makeSanctuaryMap(offLim)
      myOutputs$finalMap = myMap
    }
    else{
      myHarvLim = input$amount
      myMaxTime = input$effort
      myMinSize = input$size
      returnShells = input$shell
      param = c(myHarvLim, myMaxTime, myMinSize, myPlot, returnShells)
    
      newOysters = oysters
      newDens = myDens
      newShell = myShell
    
      outVar<-localUpdate(param, newOysters, myMaxDens, newDens, newShell, nplts)
      oysters = outVar$oysters
      myDens = outVar$myDens
      myShell = outVar$myShell
    
      myOutputs$outAvgSize = outVar$avgSize
      myOutputs$outHarvTime = outVar$harvTime
      myOutputs$outSacksTaken = outVar$sacksTaken
      myOutputs$finalMap = outVar$myMap
    }
  })
  
  observeEvent(input$update,{
    #Make sanctuary variables. Needs to be reactive to be global
    showSanctuary = F #Whether the Map Currently Displays Sanctuary Areas
    offLim = vector(length=nplts) #1 if plot is a Sanctuary or 0 if not
    offLim[] = 0
    offLim[!cond]<-NA
    
    myOutputs$outAvgSize = NA
    myOutputs$outHarvTime = NA
    myOutputs$outSacksTaken = NA
    myOutputs$finalMap = myMap
  })
  
  observeEvent(input$sanctuaryMap,{
    print(showSanctuary)
    showSanctuary = T
    myMap <- makeSanctuaryMap(offLim)
    myOutputs$finalMap = myMap
  })
  
  observeEvent(input$harvestMap,{
    print(showSanctuary)
    showSanctuary = F
    myMap <- updateMap(heatVec)
    myOutputs$finalMap = myMap
  })
  
  localUpdate <- function(param, locOyster, myMaxDens, locDens, locShell, nplts){
    myUpdate<-updateFunction(locOyster, myMaxDens, locDens, locShell, param, nplts) #All updates done in separate script
    
    #Set oyster pop, dens, and dead shell according to updates
    oysters =  myUpdate$oysters
    for(i in 1:nplts){
      myDens[i] = sum(oysters[i,1:6]*c(1:6))
    }
    myShell = myUpdate$shell
    
    #Calculate heatmap values based on density (biomass)
    for(i in 1:nplts){
      heatVec[i] = (100*myDens[i])/myMaxDens
    }
    myMap <- updateMap(heatVec) #Create Map
    
    return(list(avgSize = myUpdate$avgSize, harvTime = myUpdate$harvTime, sacksTaken = myUpdate$sacksTaken, 
                myMap = myMap, myShell = myShell, myDens = myDens, oysters = oysters))
  }
  
  #Assemble and Display Outputs
  output$map <- renderLeaflet({
    input$map_marker_click #Makes output dependent on map or button click (via isolate)
    input$sanctuaryMap
    input$harvestMap
    input$update
    isolate(myOutputs$finalMap)})
  output$time <- renderText({
    input$map_marker_click
    input$update
    timeString <- isolate(c("Time Spent Harvesting Each Day: ", 
                            toString(trunc(myOutputs$outHarvTime*100)/100), " hours"))
    timeString})
  output$sacks <- renderText({
    input$map_marker_click
    input$update
    sacksString<-isolate(c("Average Number of Sacks Harvested per Day: ", 
                           toString(trunc(myOutputs$outSacksTaken*100)/100), " sacks"))
    sacksString})
  output$size <- renderText({
    input$map_marker_click
    input$update
    sizeString<-isolate(c("Average Size of Harvested Oysters this Year: ", 
      toString(trunc(myOutputs$outAvgSize*100)/100), " inches"))
    sizeString})
}

shinyApp(ui = ui, server = server)

Upvotes: 1

Views: 33

Answers (1)

SmokeyShakers
SmokeyShakers

Reputation: 3402

For starters, you aren't using reactiveValues correctly. It would be something like this:

my_reactives <- reactiveValues()
my_reactives$offLim <- 0
my_reactives$showSanctuary <- F

Upvotes: 1

Related Questions