Hi,
I’m working on interactive plots in Jupyter Notebook. I have a scatter plot figure where traces may be added that I want to remove then. I would like to do so by clicking on them.
I am trying to use the on_click
method : when a trace is added to the figure fig
, a click_remove
callback is consequently added for this trace by using fig.data[-1].on_click(click_remove)
. Inside click_remove
the fig.data
tuple is updated, and the “can only assign to data a permutation of a subset of data” condition is verified.
However, it ends up with IndexError
or KeyError
. It seems that it is because all traces, even those not clicked, are “refreshed” (or something like that), and at some point the indexation is broken because a trace was removed.
I have found a workaround by setting visible=False
for wanted traces, but it’s not really satisfying. I also tried using clickmode='select'
and fig.data[-1]._select_callbacks.append(click_remove)
but the callback seems to never be called then.
Below is a simple Jupyter Notebook code to replicate the behavior (first post here, let me know if there is a better way to provide code). For this exemple I got following errors:
-
IndexError: tuple index out of range
, if clicking on trace 0 to 3. -
KeyError: 4
, if clicking on trace 4 (the last added).
Code:
import numpy as np
import plotly.graph_objects as go
from IPython.display import display
def click_remove(trace, points, state):
print(points.trace_name)
# skip if clicking on another trace
# because when clicking on a trace
# all traces are activating this callback
if not points.point_inds:
print(' Skip.')
return
print(' Remove.')
idx = points.trace_index
new_data = list(fig.data)
new_data.pop(idx)
# can only assign to data a permutation of a subset of data
assert np.all(
np.array([id(t) for i, t in enumerate(fig.data)
if not i == idx ])
== np.array([id(t) for t in new_data])
)
fig.data = new_data
# Workaround by setting `visible=False`
# fig_spectrum.data[points.trace_index].visible = False
nlines, npoints = 5, 100
lines = [np.random.randn(npoints)+5*i for i in range(nlines)]
fig = go.FigureWidget(layout=dict(hovermode='closest'))
for line in lines:
fig.add_trace(go.Scatter(y=line))
# What I would like to do
fig.data[-1].on_click(click_remove)
# I also tried using clickmode='select' and following line:
#fig.data[-1]._select_callbacks.append(click_remove)
# Even setting the callback on only one trace will throw same errors
#fig.data[2].on_click(click_remove)
display(fig)