Plot animation in Shiny with rgl - r
I just started using Shiny and I'm trying to plot an "animation" using lapply or a for loop in Shiny, but I can't seem to get the correct output. When using base R, my code works.
My data is not set as a time series, but each row represents an observation in time.
Also, I'm willing to use another package (other than rgl), if necessary.
And, I'm making use of some of the code described here, including the javascript file rglwidgetaux.js .
global.R
library(rgl)
# MAIN FUNCTION
movement.points<-function(DATA,time.point,CONNECTOR){
DATA.time<-DATA[time.point,]
DATA.time<-matrix(DATA.time,c(3,4),byrow = TRUE)
x<-unlist(DATA.time[,1])
y<-unlist(DATA.time[,2])
z<-unlist(DATA.time[,3])
next3d(reuse=FALSE)
points3d(x=x,y=y,z=z,size=6,col="blue")
segments3d(x=c(x,x[CONNECTOR]),y=c(y,y[CONNECTOR]),z=c(z,z[CONNECTOR]),col="red")
Sys.sleep(0.05)
}
############################################################################
Using the function above, this works:
# INITIAL POSITION
rgl.viewpoint(userMatrix=rotationMatrix(0,2,0,0))
U <- par3d("userMatrix")
par3d(userMatrix = rotate3d(U, pi, 1,1,2))
movement.points(DATA=DATA.position,time.point=1,CONNECTOR=CONNECTOR)
# # ANIMATION (THIS IS WHAT I WANT TO RUN IN SHINY)
lapply(1:dim(DATA.position),movement.points,DATA=DATA.position,CONNECTOR=CONNECTOR)
But I can't get the "animation" (the lapply) to work in Shiny. This is what I've done:
ui.R
library(shiny)
library(rgl)
library(htmlwidgets)
library(jsonlite)
rglwgtctrl <- function(inputId, value="", nrows, ncols) {
# This code includes the javascript that we need and defines the html
tagList(
singleton(tags$head(tags$script(src = "rglwidgetaux.js"))),
tags$div(id = inputId,class = "rglWidgetAux",as.character(value))
)
}
ui <- fluidPage(
rglwgtctrl('ctrlplot3d'),
rglwidgetOutput("plot3d"),
actionButton("queryumat", "Select initial position"),
tableOutput("usermatrix"),
actionButton("regen", "Visualize sequence with new position")
,rglwidgetOutput("plot3d2")
)
server.R
source('global.R', local=TRUE)
library(shiny)
library(rgl)
library(jsonlite)
library(htmlwidgets)
options(shiny.trace=TRUE)
server <- function(input, output, session)
{
# DATA
DATA.position<-c(0.099731,-0.509277,3.092024,1,0.173340,-0.869629,3.142025,1,0.197632,-0.943848,3.099056,1,
0.099315,-0.509114,3.094403,1,0.173125,-0.868526,3.140778,1,0.196985,-0.943108,3.100157,1,
0.099075,-0.509445,3.094318,1,0.172445,-0.869610,3.138849,1,0.196448,-0.943238,3.100863,1,
0.097668,-0.508197,3.090442,1,0.172319,-0.869749,3.138942,1,0.195357,-0.943346,3.102253,1,
0.096432,-0.507724,3.087681,1,0.172151,-0.870230,3.139060,1,0.193886,-0.943752,3.103878,1,
0.095901,-0.508632,3.086148,1,0.172345,-0.870636,3.139181,1,0.193134,-0.943644,3.107753,1,
0.093076,-0.513129,3.082425,1,0.173721,-0.874329,3.139272,1,0.188041,-0.949220,3.111685,1,
0.092158,-0.513409,3.082376,1,0.173221,-0.876358,3.141781,1,0.188113,-0.949724,3.111405,1,
0.091085,-0.513667,3.082308,1,0.173626,-0.876292,3.140349,1,0.189704,-0.948493,3.108416,1,
0.089314,-0.514493,3.083489,1,0.173133,-0.876019,3.141443,1,0.189653,-0.947757,3.108083,1,
0.087756,-0.515289,3.084332,1,0.172727,-0.875819,3.141264,1,0.189452,-0.947415,3.108107,1,
0.085864,-0.515918,3.085951,1,0.172672,-0.876940,3.141271,1,0.190892,-0.946514,3.104689,1,
0.084173,-0.515356,3.087133,1,0.172681,-0.876866,3.140089,1,0.189969,-0.944275,3.100415,1,
0.065702,-0.518090,3.097703,1,0.172706,-0.876582,3.139876,1,0.189737,-0.944277,3.100796,1,
0.063853,-0.517976,3.099412,1,0.172821,-0.876308,3.139856,1,0.189682,-0.944037,3.100752,1,
0.062551,-0.518264,3.100512,1,0.172848,-0.874960,3.139102,1,0.190059,-0.942105,3.098919,1,
0.065086,-0.517151,3.098104,1,0.172814,-0.875237,3.138775,1,0.190539,-0.942204,3.098439,1,
0.064088,-0.517003,3.098001,1,0.172911,-0.874908,3.137694,1,0.190593,-0.942012,3.097417,1,
0.065648,-0.516077,3.094584,1,0.172581,-0.874648,3.137671,1,0.190480,-0.942432,3.098431,1,
0.068117,-0.516750,3.094343,1,0.172545,-0.874946,3.136352,1,0.190648,-0.942610,3.096850,1)
DATA.position<-matrix(DATA.position,c(20,12),byrow = TRUE)
CONNECTOR<-c(1,2,3)
#############################################
# THIS WORKS
# INITIAL POSITION MATRIX
observe({
input$queryumat
session$sendInputMessage("ctrlplot3d",list("cmd"="getpar3d","rglwidgetId"="plot3d"))
})
# USER POSITION MATRIX
# SELECTION
umat <-reactive({
shiny::validate(need(!is.null(input$ctrlplot3d),"User Matrix not yet queried"))
umat <- matrix(0,4,4)
jsonpar3d <- input$ctrlplot3d
if (jsonlite::validate(jsonpar3d)){
par3dout <- fromJSON(jsonpar3d)
umat <- matrix(unlist(par3dout$userMatrix),4,4) # make list into matrix
}
return(umat)
})
## SHOW POSITION
output$usermatrix <- renderTable({
umat()
})
# INITIAL IMAGE
scenegen <- reactive({
rgl.viewpoint(userMatrix=rotationMatrix(0,2,0,0))
U <- par3d("userMatrix")
par3d(userMatrix = rotate3d(U, pi, 1,1,2))
movement.points(DATA=DATA.position,time.point=1,CONNECTOR=CONNECTOR)
scene1 <- scene3d()
rgl.close() # make the app window go away
return(scene1)
})
output$plot3d <- renderRglwidget({ rglwidget(scenegen()) })
############################################################
# NOT WORKING
# Animation after selecting position
# 1st TRY
# scenegen2 <- eventReactive(input$regen,({
# par3d(userMatrix = umat())
# lapply(1:dim(DATA.position)[1],movement.points,DATA=DATA.position,CONNECTOR=CONNECTOR)
# scene2 <- scene3d()
# rgl.close() # make the app window go away
# return(scene2)
# })
# )
# output$plot3d2 <- renderRglwidget({ rglwidget(scenegen2()) })
# 2nd TRY
# output$plot3d2 <- eventReactive(input$regen,
# renderRglwidget({
# lapply(1:dim(DATA.position)[1],movement.points,DATA=DATA.position,CONNECTOR=CONNECTOR)
# scene2 <- scene3d()
# rgl.close() # make the app window go away
# return(scene2)
# })
# )
# 3rd TRY
# for (i in 1:(dim(DATA.position)[1])){
# scenegen2 <- eventReactive(input$regen,({
# par3d(userMatrix = umat())
# movement.points(DATA=DATA.position,time.point=i,CONNECTOR=CONNECTOR)
# scene2 <- scene3d()
# rgl.close() # make the app window go away
# return(scene2)
# })
# )
# output$plot3d2 <- renderRglwidget({ rglwidget(scenegen2()) })
# }
#4th TRY
observe({
input$regen
isolate({
for (i in 1:(dim(DATA.position)[1])){
par3d(userMatrix = umat())
movement.points(DATA=DATA.position,time.point=1,CONNECTOR=CONNECTOR)
scene2 <- scene3d()
rgl.close()
output$plot3d2 <- renderRglwidget({ rglwidget(scene2) })
}
})
})
}
Thanks.
I've found animations using Shiny are too slow: there's a lot of data passed from R to Javascript to show an rgl scene, and it takes too long for each frame update. You're better off using the techniques shown in the WebGL vignette based on playControl. Unfortunately these require you to precompute data for each animation frame, so aren't always available.
Related
How to make the plot reactive to user input?
In the below super-simple MWE code, I'm trying to make the plot reactive to user slider input 'periods'. I've tried various iterations of observeEvent with no luck. Can someone please help me make this reactive? The plot is supposed to show endBal on the y-axis and periods on the x-axis. Also, in the below line of MWE code, res <- Reduce(f,seq(periods),init=beginBal,accumulate=TRUE) is not picking up the value of periods from the slider input. Instead to make the MWE run for demo I have to manually define the periods variable in the "Define vectors" section at the top of the code (periods <- 5 for example). Any idea why this part isn't working? library(shiny) ### Define vectors ############################################################## beginBal <- 1000 yield_vector <- c(0.30,0.30,0.30,0.30,0.30,0.28,0.26,0.20,0.18,0.20) npr_vector <- c(0.30,0.30,0.30,0.30,0.30,0.30,0.30,0.30,0.30,0.30) mpr_vector <- c(0.20,0.20,0.20,0.20,0.20,0.20,0.20,0.20,0.20,0.20) default_vector <- c(0.10,0.10,0.10,0.10,0.10,0.09,0.08,0.07,0.06,0.05) ### End define vectors ########################################################## ui <- fluidPage( sliderInput(inputId = "periods", label = "Periods:", min = 1, max = 10, value = 5), plotOutput(outputId = "balancePlot") ) # close fluid page server <- function(input,output){ ### Generates data for plotting ############################################### f <- function(x,y){x*(1+npr_vector[y]-mpr_vector[y]-default_vector[y]/12)} res <- Reduce(f,seq(periods),init=beginBal,accumulate=TRUE) b <- head(res,-1) endBal <- res[-1] ### End data for plotting ##################################################### output$balancePlot <- renderPlot({plot(endBal)}) } # close server shinyApp(ui = ui, server = server)
Try this output$balancePlot <- renderPlot({ f <- function(x,y){x*(1+npr_vector[y]-mpr_vector[y]-default_vector[y]/12)} res <- Reduce(f,seq(input$periods),init=beginBal,accumulate=TRUE) b <- head(res,-1) endBal <- res[-1] plot(endBal) })
Shiny - runif function inside reactive loop
hope someone can help with this one: My code below was returning a dinamic table as I intend, but when I created the for loop to assign individually random numbers to each [k,p] position in the table all the calculations stop and I see an empty UI. If instead of rv$MCProbTable[[k,p]] = round(as.numeric(stats::runif(1,0,100)), 3) I use for example rv$MCProbTable[[k,p]] = 2 I see "2" allocated in each position in the table, what is close to what I want and shows that everything is working up to the runif application. So is it an issue with the function? Or most likely it is the reactive for loop that isn't set properly? Thanks and hope this is an easy one! ## LIBRARIES library(tidyverse) library(stats) library(data.table) library(triangle) library(base) library(matrixStats) library(ggplot2) library(ggthemes) library(readxl) library(httr) library(writexl) library(shiny) ExcelTemplate <- tempfile(fileext = ".xlsx") GET(url = "https://www.openmontecarlo.com/SampleS.xlsx",write_disk(ExcelTemplate)) defaultRR <- read_xlsx(ExcelTemplate) ## DEFINE UI ui <- fluidPage( numericInput("ISvModels", label = h3("Select how many scenarios to run in the model:"),value=10,min=5,max=10000), hr(), mainPanel( fluidRow( p("vModels"), textOutput("vModelsText"), p("vtModels"), textOutput("vtModelsText"), p("Scenarios"), textOutput("SnScenarios"), h3(strong("ProbTable")), br(), dataTableOutput("SMCProbTable"), hr() ) ) ) server <- function(input, output, session) { ## CREATE DEFAULT RR AND REACTIVE VARIABLES rv <- reactiveValues( vModels = 10, nScenarios = 10, vtModelScenarios = NULL, RRDT = data.frame(), nRisks = 1, MCProbTable = data.frame(), p = 1, k = 1 ) ## PROCESS MODELS INPUT observe({rv$vModels <- as.numeric(unlist(input$ISvModels))}) output$vModelsText <- renderPrint({unlist(rv$vModels)}) qtModels <- reactive({length(unlist(rv$vModels))}) output$qtModelsText <- renderPrint({unlist(qtModels())}) vtModels <- reactive({paste0("M",1:qtModels()," n = ",rv$vModels," scenarios")}) output$vtModelsText <- renderPrint({unlist(vtModels())}) ## RR TABLE observe({rv$nRisks <- nrow(defaultRR)}) ############################################################################################################### ## CALCULATE SCENARIOS - CREATE DATA TABLES AND TEXT VECTORS FOR EACH SCENARIO ## Vectors for SINGLE scenario observe({rv$nScenarios <- rv$vModels}) observe({rv$vtModelScenarios <- paste0("Scenario ",1:rv$nScenarios,"/",rv$vModels)}) ## CREATE PROBABILITY TABLE AND CALCULATE PROBABILITIES observe({rv$MCProbTable <- data.frame(matrix(nrow=rv$nRisks,ncol=rv$nScenarios))}) observe({rownames(rv$MCProbTable) <- rv$RRDT$ID}) observe({colnames(rv$MCProbTable) <- rv$vtModelScenarios}) observe({ for (p in 1:rv$nScenarios){ for (k in 1:rv$nRisks){ rv$MCProbTable[[k,p]] = round(as.numeric(stats::runif(1,0,100)), 3) } } }) output$SMCProbTable <- renderDataTable({rv$MCProbTable}) } # Run the app ---- shinyApp(ui = ui, server = server)
In your for loop, use isolate() for (p in 1:rv$nScenarios){ for (k in 1:rv$nRisks){ isolate( rv$MCProbTable[k,p] <- round(as.numeric(stats::runif(1,0,100)), 3) ) } }
add another layer to ggplot2/ggtree based on user input Rshiny
The example below is using ggtree in which I can brush the tips in the phylogeny and add an annotation label ("clade"). Steps to get the app going - load the tree - called vert.tree brush over (highlight) tips (test with human and lemur) and press the 'annotate tree' button to add the label in red. What I want to do is add another annotation onto the tree while maintaining the first annotation (human and lemur). For example, a second label for the pig and cow tips. Essentially, I want to be able to add a line onto a phylogenetic tree based on user input and then repeat that based on second input from the user while maintaining the first line on the image. Currently, the label gets reset every time I brush a different pair so only one annotation is displayed at a time. # This is a Shiny web application. You can run the application by clicking # the 'Run App' button above. library(shiny) library(treeio) library(ggtree) library(phytools) library(ape) #make phylogenetic tree text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);" #read in the tree vert.tree<-ape::read.tree(text=text.string) # Define UI for application that draws a histogram ui <- fluidPage( # Application title titlePanel("Test"), actionButton("add_annotation","Add clade annotation"), # Show a plot of the generated distribution mainPanel(plotOutput("treeDisplay", brush ="plot_brush") ) ) # Define server logic required to draw a histogram server <- function(input, output) { #reactive that holds base tree - this is how I am building the base tree make_tree <- reactive({ ggtree::ggtree(vert.tree)+ ggtree::geom_tiplab()+ ggplot2::xlim(NA, 10)}) #render base tree output$treeDisplay <- renderPlot({ make_tree() }) #reactive that holds the brushed points on a plot dataWithSelection <- reactive({ brushedPoints(make_tree()$data, input$plot_brush) }) #add to label to vector if isTip == True dataWithSelection2 <- reactive({ tipVector <- c() for (i in 1:length(dataWithSelection()$label)){ if(dataWithSelection()$isTip[i] == TRUE) tipVector <- c(tipVector,dataWithSelection()$label[i])} return(tipVector) }) # incorporate the tipVector information for adding layer layer <- reactive({ ggtree::geom_cladelabel(node=phytools::findMRCA(ape::as.phylo(make_tree()), dataWithSelection2()), label = "Clade", color = "red") }) #display that layer onto the tree observeEvent(input$add_annotation, { output$treeDisplay <- renderPlot({make_tree() + layer()}) }) } # Run the application shinyApp(ui = ui, server = server) Suggestions greatly appreciated! updated to include a base tree (vert.tree)
Hope you found the solution already, but if not, here is an approach. First it helps to do the problem in a non-shiny setting. What we need is a list that accumulates vectors of tips. Then we cycle over this list to generate annotations: tree_plot <- ggtree::ggtree(vert.tree) + ggtree::geom_tiplab() + ggplot2::xlim(NA, 10) tip_vector <- list(c("human", "lemur"), c("pig", "cow")) make_layer <- function(tree, tips, label, color) { ggtree::geom_cladelabel( node = phytools::findMRCA(ape::as.phylo(tree), tips), label = label, color = color ) } x + lapply(1:2, function(i) make_layer( tree_plot, tips = tip_vector[[i]], label = paste("Clade", i), color = "red" )) The key bit is in the lapply call, where generate the annotation layer for each member of the tip_vector list. Now that this is working, we go to shiny. In your app, every time you click add annotation the brushed points data frame is refreshed and your tip vector is just a vector of the newly brushed tips. Any previously selected clades are forgotten. To remember these, we can introduce two reactive values. One n_annotations is a numeric reactiveVal counting how many times we click add annotation. The other annotations is a reactiveValues list which stores all the brushed clades under the names paste0("ann", n_annotations()). Then, the actual adding of the layer of annotations proceeds as in the non-reactive example with lapply cycling over the reactiveValues. App code: # This is a Shiny web application. You can run the application by clicking # the 'Run App' button above. library(shiny) library(treeio) library(ggtree) library(phytools) library(ape) #make phylogenetic tree text.string <-"(((((((cow, pig),whale),(bat,(lemur,human))),(robin,iguana)),coelacanth),gold_fish),shark);" #read in the tree vert.tree<-ape::read.tree(text=text.string) # Define UI for application that draws a histogram ui <- fluidPage( # Application title titlePanel("Test"), actionButton("add_annotation","Add clade annotation"), # Show a plot of the generated distribution mainPanel(plotOutput("treeDisplay", brush ="plot_brush"), plotOutput("treeDisplay2") ) ) # Define server logic required to draw a histogram server <- function(input, output) { #reactive that holds base tree - this is how I am building the base tree make_tree <- reactive({ ggtree::ggtree(vert.tree) + ggtree::geom_tiplab() + ggplot2::xlim(NA, 10) }) #render base tree output$treeDisplay <- renderPlot({ make_tree() }) # Initialize a reactive value and set to zero n_annotations <- reactiveVal(0) annotations <- reactiveValues() #reactive that holds the brushed points on a plot dataWithSelection <- reactive({ brushedPoints(make_tree()$data, input$plot_brush) }) #add to label to vector if isTip == True dataWithSelection2 <- eventReactive(input$plot_brush, { tipVector <- c() for (i in 1:length(dataWithSelection()$label)) { if (dataWithSelection()$isTip[i] == TRUE) tipVector <- c(tipVector, dataWithSelection()$label[i]) } tipVector }) make_layer <- function(tree, tips, label, color) { ggtree::geom_cladelabel( node = phytools::findMRCA(ape::as.phylo(tree), tips), label = label, color = color ) } #display that layer onto the tree anno_plot <- eventReactive(input$add_annotation, { # update the reactive value new <- n_annotations() + 1 n_annotations(new) annotations[[paste0("ann", n_annotations())]] <- dataWithSelection2() plt <- make_tree() + lapply(1:n_annotations(), function(i) make_layer( make_tree(), tips = annotations[[paste0("ann", i)]], label = paste("Clade", i), color = "red" )) return(plt) }) output$treeDisplay2 <- renderPlot({ anno_plot() }) } # Run the application shinyApp(ui = ui, server = server) Edit: how the reactive values work without the phylo stuff I tried to comment this thoroughly. ui <- basicPage( actionButton("add_anno", "Add annotation"), helpText("n_annotation is counting clicks"), textOutput("n_anno"), helpText("clades is accumulating clades"), verbatimTextOutput("clades") ) server <- function(input, output) { # this initializes a reactive value # and sets the initial state to 0 n_anno <- reactiveVal(0) # makes an empty reactive list # this can be populated and index # like a normal list # e.g., clades[["first"]] <- c("bird", "lizard") clades <- reactiveValues() observeEvent(input$add_anno, { # increment the number of clicks new_count <- n_anno() + 1 # update the reactiveValue # works the same way we initialized it # except instead of zero we set the incremented value n_anno(new_count) # making a name for an element in the clades list # we use the n_anno number of clicks to increment the clades # message just prints it on console message( paste0("clade", n_anno() )) # populate the list of clades for annotations clades[[ paste0("clade", n_anno() ) ]] <- sample(LETTERS, 3) }) output$n_anno <- renderText(n_anno()) output$clades <- renderPrint( str(reactiveValuesToList(clades)) ) } shinyApp(ui, server)
hmmm - okay when I tested your suggestion dataWithSelection2 <- reactive({ tipVector <- c() for (i in 1:length(dataWithSelection()$label)){ if(!is.null(dataWithSelection()$isTip[i])) { tipVector <- c(tipVector,dataWithSelection()$label[i]) } } return(tipVector) }) I get the error: missing value where TRUE/FALSE needed....
R Shiny dataTableOutput don't work if using loop of reactive image
I'm using R software and EBImage package to image analysis. I have an image with several frames that i load locally. I want to show a dataTable output of features (compute.Features.basic). I want to bind each dataframe (each frame) to a total one. But it doen't work properly. If i do the same with a non reactive image, it works ok. So, it's something about the reactive image. Here is an example: ## Only run examples in interactive R sessions if (interactive()) { shinyApp( ui = fluidPage( fileInput('input.image',""), dataTableOutput("tabledata") ), server = function(input, output) { # Load Image img <- reactive({ if (is.null(input$input.image)) return(NULL) x <- readImage(input$input.image$datapath) }) # Modified Image img_bw <- reactive({ req( img() ) x <- img() x <- gblur(x, sigma = 5) x <- thresh(x, w = 15, h = 15, offset = 0.05) x <- bwlabel(x) }) # Create empty datagrame dt <- data.frame() # Calculate cell features for each frame and rbind frame-dataframe to total data <- reactive({ for (i in 1:numberOfFrames(img())){ dt <- rbind(dt, computeFeatures.basic(img_bw()[,,i], img()[,,i])) } }) # Otuput output$tabledata <- renderDataTable(data()) } ) }
Ok, I solved simply introducing the creation of temporal dataframe into the reactive function and calling the dataframe: ## Only run examples in interactive R sessions if (interactive()) { shinyApp( ui = fluidPage( fileInput('input.image',""), dataTableOutput("tabledata") ), server = function(input, output) { # Load Image img <- reactive({ if (is.null(input$input.image)) return(NULL) x <- readImage(input$input.image$datapath) }) # Modified Image img_bw <- reactive({ req( img() ) x <- img() x <- gblur(x, sigma = 5) x <- thresh(x, w = 15, h = 15, offset = 0.05) x <- bwlabel(x) }) # Calculate cell features for each frame and rbind frame-dataframe to total data <- reactive({ # Create empty datagrame dt <- data.frame() # Use dt dataframe for (i in 1:numberOfFrames(img())){ dt <- rbind(dt, computeFeatures.basic(img_bw()[,,i], img()[,,i])) } # Print dt dataframe dt }) # Otuput output$tabledata <- renderDataTable(data()) } ) }
Change reactive time for dygraph's dyRangeSelector in Shiny
I'm building a Shiny application where I want to use the dyRangeSelector from dygraphs to provide the input period. My problem is that I only want the reactive change to fire when the selector receives a "MouseUp"-event, ie., when the user is done with choosing the period. Right now events are dispatched as the selector is moved which results in a lagged app since the computations done for each period take a few seconds. Essentially, Shiny is too reactive for my taste here (I know this it the wrong way round - normally we want the apps to be super reactive). Can I modify when the reactive request is dispatched? Here's a small example that shows the problem. library(quantmod) library(shiny) library(dygraphs) library(magrittr) # Create simple user interface ui <- shinyUI(fluidPage( sidebarLayout( sidebarPanel( dygraphOutput("dygraph") ), mainPanel( plotOutput("complicatedPlot") ) ) )) server <- shinyServer(function(input, output) { ## Read the data once. dataInput <- reactive({ getSymbols("NASDAQ:GOOG", src = "google", from = "2017-01-01", auto.assign = FALSE) }) ## Extract the from and to from the selector values <- reactiveValues() observe({ if (!is.null(input$dygraph_date_window)) { rangewindow <- strftime(input$dygraph_date_window[[1]], "%Y-%m-%d") from <- rangewindow[1] to <- rangewindow[2] } else { from <- "2017-02-01" to <- Sys.Date()+1 } values[["from"]] <- from values[["to"]] <- to }) ## Render the range selector output$dygraph <- renderDygraph({ dygraph(dataInput()[,4]) %>% dyRangeSelector() %>% dyOptions(retainDateWindow = TRUE) }) ## Render the "complicated" plot output$complicatedPlot <- renderPlot({ plot(1,1) text(1,1, values[["from"]]) Sys.sleep(1) ## Inserted to represent computing time }) }) ## run app runApp(list(ui=ui, server=server))
There is a function in shiny called debounce which might pretty much suit your needs. If you rewrite the limits to a reactive expression (as opposed to observe), you can wrap it into debounce with a specification of time in milliseconds to wait before evaluation. Here is an example with 1000ms: library(quantmod) library(shiny) library(dygraphs) library(magrittr) # Create simple user interface ui <- shinyUI(fluidPage( sidebarLayout( sidebarPanel( dygraphOutput("dygraph") ), mainPanel( plotOutput("complicatedPlot") ) ) )) server <- shinyServer(function(input, output) { ## Read the data once. dataInput <- reactive({ getSymbols("NASDAQ:GOOG", src = "google", from = "2017-01-01", auto.assign = FALSE) }) ## Extract the from and to from the selector values <- reactiveValues() limits <- debounce(reactive({ if (!is.null(input$dygraph_date_window)) { rangewindow <- strftime(input$dygraph_date_window[[1]], "%Y-%m-%d") from <- rangewindow[1] to <- rangewindow[2] } else { from <- "2017-02-01" to <- Sys.Date()+1 } list(from = from, to = to) }), 1000) ## Render the range selector output$dygraph <- renderDygraph({ dygraph(dataInput()[,4]) %>% dyRangeSelector() %>% dyOptions(retainDateWindow = TRUE) }) ## Render the "complicated" plot output$complicatedPlot <- renderPlot({ plot(1,1) text(1,1, limits()[["from"]]) Sys.sleep(1) ## Inserted to represent computing time }) }) ## run app runApp(list(ui=ui, server=server)) This basically means that the reactive expression must be returning the same value for at least 1s to be send to its dependencies. You can experiment with the best time.