The web page https://shiny.rstudio.com/articles/communicating-with-js.html discusses communicating between R and Javascript within a Shiny app. It is fairly simple: R can send messages to Javascript (but not receive replies to them), and Javascript can change reactive values within R.
I can't quite see how to get these mechanisms to do what I want:
I would like to have a server output calculation (e.g. in response to a checkbox change) query the existing Javascript state before doing more calculations. The application is in rgl, where a user may have interacted with an existing plot to change the view before asking for a new one; I'd like the new plot to be generated using the same view as the old one.
The idea I had was to have the output calculation send a message to Javascript to ask for the current view, then generate the new display based on the answer. The problem is that the response from Javascript arrives after the output calculation is complete: Shiny sees that an input has changed, and calls the output function again.
I could maintain a flag to say whether the returned view is current or not, and only act on it when it is current, but that seems clunky. Isn't there a way to send a query from R to Javascript and get an immediate response, or equivalently a way for R to send a message to Javascript and wait for a return value?
In response to the checkbox change, I don't want to do the new plot, I want to retrieve the current view data. This makes that data a "conductor", in the notation of that page. I want the new plot to depend on that conductor.
So my code ends up something like this:
save <- options(rgl.useNULL = TRUE)
xyz <- matrix(rnorm(300), ncol = 3)
app = shiny::shinyApp(
ui = shiny::bootstrapPage(
shiny::actionButton("redraw", "Redraw"),
rglwidgetOutput("rglPlot")
),
server = function(input, output, session) {
# This waits until the user to click on the "redraw"
# button, then sends a request for the current userMatrix
shiny::observeEvent(input$redraw, {
shinyGetPar3d("userMatrix", session)
})
# This draws the plot whenever input$par3d changes,
# i.e. whenever a response to the request above is
# received.
output$rglPlot <- renderRglwidget({
if (length(rgl.dev.list())) rgl.close()
col <- sample(colors(), 1)
plot3d(xyz, col = col, type = "s", main = col)
par3d(userMatrix = input$par3d$userMatrix)
rglwidget()
})
})
shiny::runApp(app)
options(save)
(The shinyGetPar3d function is now in the R-forge version of rgl.)
I'm glad you found a solution that works for you. In answer to your underlying question:
Isn't there a way to send a query from R to Javascript and get an immediate response, or equivalently a way for R to send a message to Javascript and wait for a return value?
No--the communication between R and the browser is non-blocking. There isn't a way to block the R thread while you wait for an answer to come. However, you could cancel the current call while you wait for a response--maybe that's what you're doing now with shinyGetPar3d? Anyway, there's a little known option to req called cancelOutput (e.g. req(FALSE, cancelOutput=TRUE)), which throws a special error that stops the currently executing render function but tells it to leave the existing output in place (rather than clearing it, as req(FALSE) does. You could use that as the building block for something perhaps.
I have now committed the new functions on R-forge. I did it slightly differently and a little more simply; I have edited my solution to show the new version.