Search code examples
rshinyplotly

Why is plotly_legenddoubleclick becoming out of sync?


I attempt to capture plotly_legenddoubleclick events to trigger a styling event in a second plot. When the user double clicks a legend group, the group is highlighted in the corresponding scatterplot using an opacity 'hack'. In practice, this works for the first series of double-clicks but becomes out of sync and eventually stops working.

MWE and GIF of behaviour below.

library(shiny)
library(plotly)

data = iris

ui <- fluidPage(
    mainPanel(
      plotlyOutput("bp"),
      plotlyOutput("sc")
    )
)

server <- function(input, output) {
  
  serv_data <- reactive({ df <- data })
  
  output$bp <- renderPlotly({
    fig <- plot_ly(
      data,
      source = "boxplot",
      y = ~Sepal.Length,
      color = ~Species,
      type = "box"
    ) %>%
      event_register("plotly_legenddoubleclick")
  })
  
  output$sc <- renderPlotly({
    if(!is.null(legend_data$data)) {
      df <- legend_data$data
    }else{
      df <- data
      df$opacity <- 0.75
    }
    
    fig <- plot_ly(
      df,
      source = "scatter",
      x = ~Sepal.Width,
      y = ~Sepal.Length,
      type = "scatter",
      mode = "markers",
      color = ~Species,
      opacity = ~opacity
    )
  })

  ## Double click stuff
  legend_data <- reactiveValues(data = NULL)
  
  observeEvent(event_data("plotly_legenddoubleclick", source = "boxplot"), {
    clicked_data <- event_data("plotly_legenddoubleclick", source = "boxplot")
    if (is.null(legend_data$data)) {
      clicked_species <- clicked_data$name
      mat <- serv_data()
      mat <- mat %>% mutate(opacity = ifelse(Species == clicked_species, 1.0, 0.2))
      legend_data$data <- mat
    } else {
      legend_data$data <- NULL
    }
  })
  
  
}

shinyApp(ui = ui, server = server)

enter image description here


Solution

  • The wrong display occurs when the second time a group is "un-selected", e.g. in your GIF after the second double-click on versicolor. This is because you have an observeEvent on event_data("plotly_legenddoubleclick", source = "boxplot") and this isn't triggered because in event_data the "resetting" of the plot already has a storage and event_data acts in a lazy-sense (see below).

    What you need to do is to always trigger the re-execution. From ?event_data:

    Arguments

    ...

    priority: the priority of the corresponding shiny input value. If equal to "event", then event_data() always triggers re-execution, instead of re-executing only when the relevant shiny input value changes (the default).

    You need priority = "event" (instead of the default "input"), hence you can change the condition in the observeEvent to the following and it will work:

    observeEvent(event_data(
      "plotly_legenddoubleclick",
      source = "boxplot",
      priority = "event"
    ), {
      # your Code as above 
    }