shiny
shiny copied to clipboard
Edit reactiveTimer timer interval
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.
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 }, {
...
})
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>)
?