1

I am building plotly figures with R. The figures have legends. Each legend has a colored point that represents a level of the data. Here is a minimal example:

library(plotly)
data(iris)
plot_ly(
  x     = ~Petal.Length, y = ~Petal.Width, 
  color = ~Species,
  data  = iris)

[screenshot]

By default, double-clicking on a point in the legend completely hides all unrelated points. For example, double-clicking on the "versicolor" point in the legend hides all "setosa" and "virginica" points in the plot. In plotly argot, it "filters" the data in the plot.

But I would rather that clicking on a point in the legend highlight points in the plot. For example, I would like clicking (or double-clicking) on the versicolor point in the legend to dim the "setosa" and "virginica" points in the plot, perhaps by reducing their opacity. The versicolor points in the plot would then be "highlighted." Can this behavior be implemented?

I've read through the plotly documentation and searched SO and the plotly forums for related questions. That search suggests two potential solutions, but they seem rather complicated:

  • Write a custom "click event" function in JS. https://plotly.com/javascript/plotlyjs-events/#legend-click-events seems to suggest that this approach can work. I don't know whether I can implement this approach from R.

  • Disable the default legend (showlegend = FALSE), then create a new legend by adding traces that have customized click events.

Are these the best approaches? If they are, and if more than one is workable, which one should I pursue?

Other notes: I'm not using Shiny. And I know about the itemclick and itemdoubleclick legend attributes, and about highlight_key(), but they don't seem relevant. (Please correct me if I'm wrong.)

1 Answer 1

1

The key is to disable legend-click events in R, and then to write a custom event handler that changes the figure when plotly_legendclick is triggered. Here is an example:

library(plotly)
data(iris)
myPlot <- plot_ly(
  x     = ~Petal.Length, y = ~Petal.Width, 
  color = ~Species,
  data  = iris)

# Disable default click-on-legend behavior
myPlot <- layout(
  myPlot, 
  legend = list(itemclick = FALSE, itemdoubleclick = FALSE))

# Add an event handler
onRender(
  myPlot,
  "function (el) {
    const OPACITY_START = el._fullData[0].marker.opacity;
    const OPACITY_DIM   = 0.3;
    el.on('plotly_legendclick', function (data) {

      // Get current opacity. The legend bubble on which we click is given by 
      // data.curveNumber: data.curveNumber == 0 means that we clicked on the 
      // first bubble, 1 means that we clicked on the second bubble, and so on.
      var currentOpacity = data.fullData[data.curveNumber].marker.opacity

      if (currentOpacity < OPACITY_START) {                 // if points already dimmed
        var update = { 'marker.opacity' : OPACITY_START };    // could also set to null
      } else {                                              // if points not already dimmed
        var update = { 'marker.opacity' : OPACITY_DIM }; 
      }

      Plotly.restyle(el, update, data.curveNumber);
        } );
    }"
)

When a user clicks on a legend bubble, this code toggles the corresponding bubbles in the plot between "normal" and "dim" states.

To instead toggle the other bubbles in the plot -- that is, to modify the other traces -- a small modification will be needed. In particular, data.curveNumber is always the number of the trace that corresponds to the clicked bubble in the legend. To instead modify the other traces, we need to pass the other trace numbers to Plotly.restyle(), not the trace that is indexed by data.curveNumber. See the Plotly.restyle() documentation for more on this point.

1
  • can you please add how to dim those that are not clicked? Commented Jan 7, 2021 at 1:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.