2

In the dummy app below, the user can select / deselect points by dragging a region around 1 or more points. This results in changing the state of these points to flip from T <-> F in the data.table.

What I'm trying to solve at the moment, is how to empty the event_data after processing it,

or at least making sure it is possible for the user to select the same set of points twice in a row.

i.e.: Now, selecting the bottom three points will turn them into crosses, selecting the same three points with the intend to turn them back to circles currently does not work as the event_data is identical to the previous selection.

I thought I had it working, but it turns out I did not.

Plotly allows the event data to be cleared with double click but I want to achieve the same effect to do this with automatic functionality in the code to clear it as soon as it's processed. I also tried to play around with this solution for click events, but I can't get it to work for my select events HERE

  useShinyjs(),

    extendShinyjs(text = "shinyjs.resetSelect = function() { Shiny.onInputChange('.clientValue-plotly_click-A', 'null'); }"),

in the UI and js$resetSelect()in the server block

enter image description here GIF shows the difference between the behavior with and without double clicking between drag select actions.

library(shiny)
library(plotly)
library(dplyr)
library(data.table)

testDF <- data.table( MeanDecreaseAccuracy =  runif(10, min = 0, max = 1), Variables = letters[1:10])
testDF$Selected <- T

ui <- fluidPage(
  plotlyOutput('RFAcc_FP1',  width = 450)
)

server <- function(input, output, session) {

  values <- reactiveValues(RFImp_FP1 = testDF)

  observe({
    if(!is.null( values$RFImp_FP1)) {
      values$Selected <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
    }
  })


  observeEvent(values$Selected, {
    parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
    if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
      data_df <- values$RFImp_FP1
      data_df <- data_df %>% .[, Selected := if_else(Variables %in% parsToChange, !Selected, Selected)]
      values$RFImp_FP1 <- NULL
      values$RFImp_FP1 <- data_df
    }

  })


  output$RFAcc_FP1 <- renderPlotly({

    RFImp_score <- values$RFImp_FP1[order(MeanDecreaseAccuracy)]
    plotheight <- length(RFImp_score$Variables) * 80
    colors <- if(length(unique(RFImp_score$Selected)) > 1) { c('#F0F0F0', '#1b73c1') } else { '#1b73c1' }
    symbols <- if(length(unique(RFImp_score$Selected)) > 1) {  c('x', 'circle') } else { 'circle' }    

    p <- plot_ly(data = RFImp_score,
                 source = 'RFAcc_FP1',
                 height = plotheight,
                 width = 450)  %>%
      add_trace(x = RFImp_score$MeanDecreaseAccuracy,
                y = RFImp_score$Variables,
                type = 'scatter',
                mode = 'markers',
                color = factor(RFImp_score$Selected),
                colors = colors,
                symbol = factor(RFImp_score$Selected),
                symbols = symbols,
                marker = list(size  = 6),
                hoverinfo = "text",
                text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
                               '<br>',  'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
                               sep = '')) %>%
      layout(
        margin = list(l = 160, r= 20, b = 70, t = 50),
        hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
        xaxis =  list(title = 'Mean decrease accuracy index (%)',
                      tickformat = "%",
                      showgrid = F,
                      showline = T,
                      zeroline = F,
                      nticks = 5,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        yaxis =  list(categoryarray = RFImp_score$Variables,
                      autorange = T,
                      showgrid = F,
                      showline = T,
                      autotick = T,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        dragmode =  "select"
      ) %>%  add_annotations(x = 0.5,
                             y = 1.05,
                             textangle = 0,
                             font = list(size = 14,
                                         color = 'black'),
                             text = "Contribution to accuracy",
                             showarrow = F,
                             xref='paper',
                             yref='paper')

    p <- p %>% config(displayModeBar = F)
    p
  })


}
shinyApp(ui, server)
ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
Mark
  • 2,789
  • 1
  • 26
  • 66
  • p.s. i'm using the latest plotly package 4.9.0 which comes with quite some differences compared to the previous version. – Mark Jun 02 '19 at 15:06
  • This example is far away from beeing minimal. Please spend some more time on condensing the code. It makes it much easier to understand your problem. – ismirsehregal Jun 03 '19 at 13:26
  • fair enough ismirsehregal. I probably should have stripped down the plot, but I thought it wouldn't be an issue as the code for the plot was not part of the issue. I found another solution, by inverting the trace assignment so that the 'selected = F' points become trace 1, the original / selected = T are trace 0 color = ~factor(!Selected), colors = colors, symbol = ~factor(!Selected), symbols = symbols, – Mark Jun 03 '19 at 16:38

2 Answers2

2

Please check the following:

library(shiny)
library(plotly)
library(data.table)

testDF <- data.table(MeanDecreaseAccuracy =  runif(10, min = 0, max = 1), Variables = letters[1:10], Selected = TRUE)
setorder(testDF, MeanDecreaseAccuracy)

ui <- fluidPage(
  plotlyOutput('RFAcc_FP1',  width = 450)
)

server <- function(input, output, session) {

  RFImp_score <- reactive({
    eventData <- event_data("plotly_selected", source = 'RFAcc_FP1_source', session)
    parsToChange <- eventData$y
    testDF[Variables %in% parsToChange, Selected := !Selected]
    testDF
  })

  output$RFAcc_FP1 <- renderPlotly({
    req(RFImp_score())
    plotheight <- length(RFImp_score()$Variables) * 80

    colors <- if (length(unique(RFImp_score()$Selected)) > 1) {
      c('#F0F0F0', '#1b73c1')
    } else {
      if (unique(RFImp_score()$Selected)) {
        '#1b73c1'
      } else {
        '#F0F0F0'
      }
    }

    symbols <-
      if (length(unique(RFImp_score()$Selected)) > 1) {
        c('x', 'circle')
      } else {
        if (unique(RFImp_score()$Selected)) {
          'circle'
        } else {
          'x'
        }
      }

    p <- plot_ly(data = RFImp_score(),
                 source = 'RFAcc_FP1_source',
                 height = plotheight,
                 width = 450) %>%
      add_trace(x = ~MeanDecreaseAccuracy,
                y = ~Variables,
                type = 'scatter',
                mode = 'markers',
                color = ~factor(Selected),
                colors = colors,
                symbol = ~factor(Selected),
                symbols = symbols,
                marker = list(size  = 6),
                hoverinfo = "text",
                text = ~paste('<br>', 'Parameter: ', ~Variables,
                              '<br>',  'Mean decrease accuracy: ', format(round(MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
                              sep = '')) %>%
      layout(
        margin = list(l = 160, r= 20, b = 70, t = 50),
        hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
        xaxis =  list(title = 'Mean decrease accuracy index (%)',
                      tickformat = "%",
                      showgrid = F,
                      showline = T,
                      zeroline = F,
                      nticks = 5,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        yaxis =  list(categoryarray = ~Variables,
                      autorange = T,
                      showgrid = F,
                      showline = T,
                      autotick = T,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        dragmode =  "select"
      ) %>%  add_annotations(x = 0.5,
                             y = 1.05,
                             textangle = 0,
                             font = list(size = 14,
                                         color = 'black'),
                             text = "Contribution to accuracy",
                             showarrow = F,
                             xref='paper',
                             yref='paper')

    p <- p %>% config(displayModeBar = F)
    p
  })


}
shinyApp(ui, server)

Result:

Result

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • thanks for the answer ismirsehregal. I found another solution without having to switch to reactive, which I explain below is a bit of a hassle in lapply approach to build this code for several plots in one go. – Mark Jun 03 '19 at 16:57
  • @Mark, using `reactive` over `reactiveValues` was just a try to reduce the complexity of your example (which wasn't necessary to illustrate the problem). For further motivation see [this](https://stackoverflow.com/a/53035952/9841389). – ismirsehregal Jun 03 '19 at 17:24
  • Just updated my answer once again. Concerning the logic of your initial code it seems there simply was an if-statement missing for the colors and symbols (for the sake of completeness). – ismirsehregal Jun 04 '19 at 08:02
1

Normally the reactive approach is probably better, but I chose to stick with observe due to my

 lapply(plotlist, function(THEPLOT) {
values[[paste('RFImp', THEPLOT, sep = '')]]   #..... etc
#......
})

In the end I managed to fix the issue to achieve the desired behaviour by inverting the trace order. By making selected == T curveNumber 0 and selected == F curveNumber 1, each time the identical selection is made, and inverted, the event_data switches between

  curveNumber pointNumber         x y
1           0           0 0.3389429 g
2           0           1 0.3872325 j

and

  curveNumber pointNumber         x y
1           1           0 0.3389429 g
2           1           1 0.3872325 j

This is achieved by ! in front of the color and symbol statements:

                mode = 'markers',
                color = ~factor(!Selected), 
                colors = colors,
                symbol = ~factor(!Selected), 

the if(!is.null( values$RFImp_FP1)) { ...} statement causes the observe({...}) to fire twice, but this has no further implications as values$Selected only changes the first time. Without this statement, the new Plotly version causes apps to throw the following error if the plot is not on the first page you open (i.e. on another tab or dropdownbutton)

Warning: The 'plotly_selected' event tied a source ID of 'RFAcc_FP1' is not registered. In order to obtain this event data, please add event_register(p, 'plotly_selected') to the plot (p) that you wish to obtain event data from.

The working App:

library(shiny)
library(plotly)
library(dplyr)
library(data.table)

testDF <- data.table( MeanDecreaseAccuracy =  runif(10, min = 0, max = 1), Variables = letters[1:10])
testDF$Selected <- T

ui <- fluidPage(
  plotlyOutput('RFAcc_FP1',  width = 450)
)

server <- function(input, output, session) {

  values <- reactiveValues(RFImp_FP1 = testDF)

  observe({

      values$Selected <- event_data("plotly_selected", source = 'RFAcc_FP1')

  })


  observeEvent(values$Selected, {
    parsToChange <- event_data("plotly_selected", source = 'RFAcc_FP1')$y
    if(!is.null(event_data("plotly_selected", source = 'RFAcc_FP1'))){
      data_df <- values$RFImp_FP1
      data_df[Variables %in% parsToChange, Selected := !Selected]
      values$RFImp_FP1 <- NULL
      values$RFImp_FP1 <- data_df
    }

  })


  output$RFAcc_FP1 <- renderPlotly({

    RFImp_score <- values$RFImp_FP1[order(MeanDecreaseAccuracy)]
    plotheight <- length(RFImp_score$Variables) * 80
    colors <- if(length(unique(RFImp_score$Selected)) > 1) { c( '#1b73c1', '#F0F0F0') } else { '#1b73c1' }
    symbols <- if(length(unique(RFImp_score$Selected)) > 1) {  c( 'circle', 'x') } else { 'circle' }    

    p <- plot_ly(data = RFImp_score,
                 source = 'RFAcc_FP1',
                 height = plotheight,
                 width = 450)  %>%
      add_trace(x = RFImp_score$MeanDecreaseAccuracy,
                y = RFImp_score$Variables,
                type = 'scatter',
                mode = 'markers',
                color = ~factor(!Selected), 
                colors = colors,
                symbol = ~factor(!Selected), 
                symbols = symbols,
                marker = list(size  = 6),
                hoverinfo = "text",
                text = ~paste ('<br>', 'Parameter: ', RFImp_score$Variables,
                               '<br>',  'Mean decrease accuracy: ', format(round(RFImp_score$MeanDecreaseAccuracy*100, digits = 2), nsmall = 2),'%',
                               sep = '')) %>%
      layout(
        margin = list(l = 160, r= 20, b = 70, t = 50),
        hoverlabel = list(font=list( color = '#1b73c1'), bgcolor='#f7fbff'),
        xaxis =  list(title = 'Mean decrease accuracy index (%)',
                      tickformat = "%",
                      showgrid = F,
                      showline = T,
                      zeroline = F,
                      nticks = 5,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        yaxis =  list(categoryarray = RFImp_score$Variables,
                      autorange = T,
                      showgrid = F,
                      showline = T,
                      autotick = T,
                      font = list(size = 8),
                      ticks = "outside",
                      ticklen = 5,
                      tickwidth = 2,
                      tickcolor = toRGB("black")
        ),
        dragmode =  "select"
      ) %>%  add_annotations(x = 0.5,
                             y = 1.05,
                             textangle = 0,
                             font = list(size = 14,
                                         color = 'black'),
                             text = "Contribution to accuracy",
                             showarrow = F,
                             xref='paper',
                             yref='paper')

    p <- p %>% config(displayModeBar = F)
    p
  })


}
shinyApp(ui, server)
Mark
  • 2,789
  • 1
  • 26
  • 66
  • Also a good way to solve this! Especially with the lapply-approach as background (I wasn't aware that this is used in your actual app) using `reactiveValues` of course is more convenient. – ismirsehregal Jun 04 '19 at 08:03