6
votes

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")
  )
)
1
Since your if statement seems to do what you want with this example it might be good to provide an example of when it doesn't work - Hack-R
renderPlot is somewhat static, to do what you want you should use more advanced libraries like rCharts or highcharter only to invoke those changes - Pork Chop
Like this? - alistaire
@Hack-R I consider the fact that my fix results in plotting the same thing twice to be pretty bad! - Mark O'Connell
@Pork Chop Thanks for the suggestions. I am implementing my default interactive graphics in other ways... I'm just trying to provide a Shiny alternative. - Mark O'Connell

1 Answers

2
votes

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)
    })
  }
)