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)
