9

I'm trying to do something seemingly simple: when the user clicks on a data point or select multiple points with lasso selection, I want to draw these points in a different colour. In order to do that, I look at what points are selected, and add a col variable to the dataframe, and I tell the ggplot to colour the points according to that column.

It does work for the first selection. But whenever there are already selected points, selecting the next set of points doesn't work. I've added debug statements to see what data is returned from plotly, and it seems like it returns different pointNumber and curveNumber after the initial selection. I couldn't find any documentation about how these variables work and I'm not sure how to fix this issue.

Here's a GIF showing the issue

And here's code to reproduce:

library(plotly)
library(shiny)

ui <- fluidPage(
  plotlyOutput("plot")
)

server <- function(input, output, session) {
  output$plot <- renderPlotly({
    click_data <- event_data("plotly_click", source = "select")
    select_data <- event_data("plotly_selected", source = "select")
    data <- mtcars
    data$col <- "black"
    if (!is.null(select_data)) {
      cat(str(select_data))
      idx <- select_data$pointNumber + 1
      data[idx, "col"] <- "blue"
    }
    if (!is.null(click_data)) {
      cat(str(click_data))
      idx <- click_data$pointNumber + 1
      data[idx, "col"] <- "red"
    }
    p <- ggplot(data, aes(mpg, wt, col = I(col))) + geom_point()
    ggplotly(p, source = "select")
  })
}

shinyApp(ui, server)

I've also been told that perhaps what I need to do is create my own row identifier and pass it to the key aesthetic. I'm not sure what that means I tried defining key <- row.names(data) and then passing key=key to ggplot's aes(), but that didn't seem to change anything.

3 Answers 3

7

Carson Sievert answered my question on a gist

Here's the answer:

I know it seems counter-intuitive, but pointNumber isn't a reliable row identifier. Use a key variable like this:

library(plotly)
library(shiny)

mtcars$key <- row.names(mtcars)
mtcars$col <- "black"

ui <- fluidPage(
  plotlyOutput("plot")
)

server <- function(input, output, session) {
  output$plot <- renderPlotly({
    click_data <- event_data("plotly_click")
    select_data <- event_data("plotly_selected")
    if (!is.null(select_data)) {
      mtcars[mtcars$key %in% select_data$key, "col"] <- "blue"
    }
    if (!is.null(click_data)) {
      mtcars[mtcars$key %in% click_data$key, "col"] <- "red"
    }
    p <- ggplot(mtcars, aes(mpg, wt, col = I(col), key = key)) + 
      geom_point()
    ggplotly(p) %>% layout(dragmode = "lasso")
  })
}

shinyApp(ui, server)
Sign up to request clarification or add additional context in comments.

2 Comments

thank you for this - any idea how we can pass the key identifier directly to plot_ly without using ggplotly wrapper ? also, is there any logic to the curveNumber, pointNumber that could possibly be exploited ?
@asifzuba I added an answer showing an approach directly using plot_ly.
3

Here is the plot_ly way, using the customdata attribute:

library(plotly)
library(shiny)

mtcars$key <- row.names(mtcars)
mtcars$col <- "black"

ui <- fluidPage(
  plotlyOutput("plot")
)

server <- function(input, output, session) {
  output$plot <- renderPlotly({
    click_data <- event_data("plotly_click", priority   = "event")
    select_data <- event_data("plotly_selected", priority   = "event")
    if (!is.null(select_data)) {
      mtcars[mtcars$key %in% select_data$customdata, "col"] <- "blue"
    }
    if (!is.null(click_data)) {
      mtcars[mtcars$key %in% click_data$customdata, "col"] <- "red"
    }
    p <- plot_ly(mtcars, x = ~mpg, y=~wt, colors = ~sort(unique(col)), color = ~col, customdata = ~key, type = "scatter", mode = "markers") %>% layout(dragmode = "lasso")
  })
}

shinyApp(ui, server)

plot_ly can also handle the key argument in the same way. However, customdata is the more official way:

The key attribute is only supported in shiny, but customdata is officially supported by plotly.js, and thus can also be used to attach meta-information to event

The documentation can be seen via:

library(listviewer)
schema(jsonedit = interactive())

Navigate: object ► traces ► scatter ► attributes ► customdata

Comments

0

You can maybe map it against the variables your are plotting:

library(plotly)
library(shiny)

ui <- fluidPage(
  plotlyOutput("plot")
)

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

  output$plot <- renderPlotly({
    click_data <- event_data("plotly_click", source = "select")
    select_data <- event_data("plotly_selected", source = "select")

    data <- mtcars

    data$col <- "black"
    if (!is.null(select_data)) {
      idx <- which(data$mpg %in% c(select_data[3]$x) | data$wt %in% c(select_data[4]$y))
      data[idx, "col"] <- "blue"
    }
    if (!is.null(click_data)) {
      idx <- click_data$pointNumber + 1
      data[idx, "col"] <- "red"
    }
    p <- ggplot(data, aes(mpg, wt, col = I(col))) + geom_point()
    ggplotly(p, source = "select")
  })
}

shinyApp(ui, server)

enter image description here

1 Comment

I purposely stayed away from that because I'm afraid the x/y returned might not always be precisely identical to the values in the original data. I'm trying to understand how the pointNumber/curveNumber work, it seems very strange to me but obviously there's got to be some logic behind it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.