The code below attempts to register a callback on Plotly traces in 3 different ways.
- Method 'a', registers a callback on the trace object directly, before adding it to the figure.
- Method 'b', registers the callback after adding it to the figure by getting the object from the figure data list after it has been added.
- Method 'c' is just a variation of 'a' where the callback is local (ie not a member function)
Here is the code showing the 3 attempts I have made to add the callback. This should be self contained and if you run it in a Jupyter Notebook you should see 3 plots and clicking on a any point on any plot should print a message, but the only plot that seems to work is the one using callback method 'b'
import ipywidgets as widgets
import plotly.graph_objects as go
line = {'name': 'line','data': ((1,1), (2,2), (3,3)), 'color':'red', 'dash':'solid'}
squared = {'name': 'squared','data': ((1,1), (2,2**2), (3,3**2)), 'color':'blue', 'dash':'4,4'}
cubed = {'name': 'cubed','data': ((1,1), (2,2**3), (3,3**3)), 'color':'green', 'dash':'solid'}
n4 = {'name': 'n4','data': ((1,1), (2,2**4), (3,3**4)), 'color':'purple', 'dash':'solid'}
traces = (line, squared, cubed, n4)
class MyPlot:
def __init__(self, traces, use_callback):
self.traces = traces
self.use_callback = use_callback
def get_values(self, func, index):
return [e[index] for e in func['data']]
def callback_a(self, trace, points, state):
print(f"in callback_a with trace = {trace}, points = {points}, state = {state}")
def callback_b(self, trace, points, state):
if len(points.point_inds) < 1:
return
print(f"in callback_b with trace = {trace}, points = {points}, state = {state}")
def display(self):
def callback_c(trace, points, state):
print(f"in callback_c with trace = {trace}, points = {points}, state = {state}")
fig = go.FigureWidget()
for t in traces:
s = go.Scatter(mode="lines", name=t['name'], x=self.get_values(t, 0), y=self.get_values(t, 1), line=dict(width=2, color=t['color'], dash=t['dash']))
if self.use_callback == 'a': s.on_click(self.callback_a)
if self.use_callback == 'c': s.on_click(callback_c)
fig.add_trace(s)
if self.use_callback == 'b': fig.data[-1].on_click(self.callback_b)
fig.layout.title = f"Plot using callback {self.use_callback}"
display(fig)
my_plot_a = MyPlot(traces, 'a')
my_plot_b = MyPlot(traces, 'b')
my_plot_c = MyPlot(traces, 'c')
my_plot_a.display()
my_plot_b.display()
my_plot_c.display()
As I said above, the only method that seems to work is method 'b'. This however has an undesirable "property". When I click on any point on any trace, the callbacks for each trace are called, even if I did not click on that trace. I workaround this by checking length of the points.point_inds property which is only > 0 in the desired callback. While this works, its seems unnecessary, and the desired method, 'a', seems to be the documented way to do this, however I can't seem to get it to work.
Here is the example from the Plotly doc I think I am following this example in both 'a' and 'c'. Am I doing something wrong?
