Reactive expressions in Shiny propagate changes where they need to go. We can suppress some of this behaviour with isolate
, but can we suppress changes being propagated based on our own logical expression?
The example I give is a simple scatterplot, and we draw a crosshair with abline
where the user clicks. Unfortunately, Shiny considers the result to be a new plot, and our click value is reset to NULL
... which in turn is treated as an update to the value to be propagated as usual. The plot is redrawn, and NULL
is passed to both arguments of abline
.
My hack (commented out below) is to place a condition in the renderPlot
call which updates some non-reactive variables for the plotting coordinates, only when the click values are non-NULL
. This works fine for trivial plots, but it actually results in the plot being drawn twice.
What's a better way to do this? Is there a correct way?
Server file:
library(shiny)
shinyServer(function (input, output)
{
xclick <- yclick <- NULL
output$plot <- renderPlot({
#if (!is.null(input$click$x)){
xclick <<- input$click$x
yclick <<- input$click$y
#}
plot(1, 1)
abline(v = xclick, h = yclick)
})
})
UI file:
library(shiny)
shinyUI(
basicPage(
plotOutput("plot", click = "click", height = "400px", width = "400px")
)
)
A reactive expression is an R expression that uses widget input and returns a value. The reactive expression will update this value whenever the original widget changes. To create a reactive expression use the reactive function, which takes an R expression surrounded by braces (just like the render* functions).
renderPlot is an reactive function that can take input data from the ui. R script and feed it into the server. R script. It then actively updates the information within its function.
Reactive functions are functions that can read reactive values and call other reactive functions. Whenever a reactive value changes, any reactive functions that depended on it are marked as "invalidated" and will automatically re-execute if necessary.
Winston calls this problem "state" accumulation - you want to display not only the current data, but something generated by the previous plot (the best place to learn about this is at https://www.rstudio.com/resources/videos/coordinated-multiple-views-linked-brushing/)
The basic idea is to create your own set of reactive values, and update them when the user clicks on the plot. They won't be invalidated until the next click, so you don't get circular behaviour.
library(shiny)
shinyApp(
shinyUI(basicPage(plotOutput("plot", click = "click"))),
function(input, output) {
click <- reactiveValues(x = NULL, y = NULL)
observeEvent(input$click, {
click$x <- input$click$x
click$y <- input$click$y
})
output$plot <- renderPlot({
plot(1, 1)
abline(v = click$x, h = click$y)
})
}
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With