shiny icon indicating copy to clipboard operation
shiny copied to clipboard

Edit reactiveTimer timer interval

Open alecjwild opened this issue 4 years ago • 2 comments

I am trying to create an R shiny dashboard which has a play and pause button for updating displaying a graph data along a sequence of time. To do this I was using a reactiveTimer, but it does not allow me to dynamically edit the reactiveTimer interval.

Error in .getReactiveEnvironment()$currentContext() : 
  Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)

My attempted code is

library(shiny)
library(ggplot2)
library(tidyr)

input.df <- read.csv(file = 'InputData.csv', header = TRUE, sep=",")

ui<-fluidPage(
  titlePanel("Auckland Volcanic Simulation"),
  hr(style="border-color: grey;"),
  sidebarLayout(
    # panel with all inputs
    sidebarPanel(
      fluidRow(
        column(7,actionButton("stop","Pause")),
        column(5,actionButton("play","Play"))
      ),
      fluidRow(
        column(7,actionButton("skip","Skip")),
        column(3,actionButton("reset","Reset"))
      )
    ),
    # plot panel
    mainPanel(
      # visual data on same row
      fluidRow(
        span(textOutput("Date"), style="font-size: 24px;font-style: italic;")
      ),
       fluidRow(
         column(12,plotOutput('defGraph'))
       )
    )
  )
  
)

server<-function(input,output){

  control<-reactiveValues() # reactive to store all reactive variables
  control$resetindicator<-0   # used to change button labels
  control$count<-0   # day number in sequence
  control$min<-0
  control$max<-0
  control$timer<-Inf

  forward<-function(){
    print("in forward")
    control$resetindicator<-1 # change button label

    step <- 12
    if (step >= control$count) {
      min <- 0
      max <- control$count
    } else {
      min <- control$count - step
      max <- control$count
    }
    control$min <- min
    control$max <- max
    control$count<-control$count+1
  }

  observeEvent(input$skip,{
    forward()
  })

  session<-reactiveValues()
  session$timer<-reactiveTimer(intervalMs = control$timer, session = getDefaultReactiveDomain())

  observeEvent(input$play,{
    print("play")
    control$timer<-1000
    #session$timer<-reactiveTimer(intervalMs = 1000, session = getDefaultReactiveDomain())# Time interval
    observeEvent(session$timer(),{
      print("calling forward")
      forward()
    })
  })

  observeEvent(input$stop,{
    print("stop")
    control$timer<-Inf
    #session$timer<-reactiveTimer(intervalMs = Inf, session = getDefaultReactiveDomain())
  })

  ## when reset button is pressed (set everything to original values, plus set seed)
  observeEvent(input$reset,{
    control$resetindicator<-0
    control$count= 0
  })

  # ## depth plot output
  output$DepthGraph <- renderPlot({
    eqdepthdata.df <- input.df[(input.df$DayTimeID <= control$max & input.df$DayTimeID >= control$min), ]
    ggplot(data.df, aes(x = DateTime, y = -1*AverageDepth_km)) + 
      geom_point() +
      scale_color_manual(values = c("darkorange")) +
      labs(title = "Average depth of earthquakes", x = "", y = "Depth (km)") +
      ylim(-40, 0) +
      theme_light() +
      theme(text = element_text(size = 14)) +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  ## visual data outputs
  output$Date<-renderText({
    paste("Date:", input.df$DateTime[input.df$DayTimeID == control$count-1])
  })

}

shinyApp(ui = ui, server = server)

Example CSV is

DayTimeID,Date,      Time,  DateTime,        AverageDepth_km
0,        20/08/20,  0:00,  20/08/20 0:00,   17
1,        20/08/20,  4:00,  20/08/20 4:00,   8
2,        20/08/20,  8:00,  20/08/20 8:00,   14
3,        20/08/20,  12:00, 20/08/20 12:00,  3
4,        20/08/20,  16:00, 20/08/20 16:00,  5
5,        20/08/20,  20:00, 20/08/20 20:00,  9

I was trying to update a parameter for the interval by updating the parameter. I was following code presented here https://nhsrcommunity.com/blog/animating-a-graph-over-time-in-shiny/ but if press play->pause->play but the time interval goes twice as fast as if I understand you create another reactiveTimer.

Any assistance would be appreciated.

alecjwild avatar Aug 03 '20 09:08 alecjwild

Consider using invalidateLater (plus a control$isRunning I guess) instead of reactiveTimer; you get a lot more control and to me it's also more intuitive. Your observeEvent(session$timer(), {...}) would become something like

observeEvent(if (control$isRunning) { invalidateLater(interval); TRUE }, {
  ...
})

jcheng5 avatar Aug 03 '20 16:08 jcheng5

Hummm....

So, the real issue here is that once the reactiveTimer is "regenerated" through the update, it looses it's connection to the previous reactive function calls which establish the intervariable dependency map. Is that right? Basically, you can instantiate the reactiveTimer and then create a dependency by calling it in a reactive context.... but then when you update the timer by stepping on the original name, it kills the original, which is what the reactive context was looking for which means that the timer will not change (as viewed by the end user).

While I understand the concept of the invalidateLater, it seems as though reactiveTimer should be easily adjusted. If I am correct with the above, can't a conduit into the existing timer be created. Something like update_reactive_timer(<timer>, <new interval>)?

justacec avatar Jul 19 '22 09:07 justacec