Callback on Point Drag using on_event(Pan, callback) vs on_change('indices', callback)

Hello bokeh Comunity,

This is my first post, I’m very new to Python and Bokeh. This is an amazing tools for making an interactive dashboard using python. However I need a very specific tools for interaction that I can’t find anywhere in internet.

I need to have a callback action that occur at the end PointDrawTools.

I made script that execute callback using PointDrawTool using two methods that resulting similar result, but not that I want:

  1. Callback using on_change('indices', callback)
    This method allow me to callback on the beginning data source change. In relation with Point Drag, it occur on start of Point Drag.

  2. Callback using on_event(Pan, callback)
    This method allow me to callback upon Pan on figure and at the end Point Drag. But i need the callback executed at the end of Point Drag only, not during Panning of Figure.

1st method have a little bit of problem, I need to data synchronization at the end of Point Drag.

2nd methods work just fine, it callbacks at the end Point Drag and figure panning. I need to have data synchronize only at the end Point Drag only (or during Point Drag only), not during Panning on figure.

Is there any way for me to have Callback at the end of Point Drag?

here is the minimal code that I write:

from bokeh.models import ColumnDataSource, Column, Row, PointDrawTool, Div, CustomJS
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.events import PanEnd

def callback_source_change(attr, old, new):
    data3.data = data12.data


def callback_pan():
    data3.data = data12.data


xa = [1, 3, 3, 4, 5, 1]
ya = [2, 6, 4, 5, 8, 4]

ca = ['red'] * 3 + ['green'] * 3
data12 = ColumnDataSource({'xa': xa, 'ya': ya, 'ca': ca})
data3 = ColumnDataSource({'xa': xa, 'ya': ya, 'ca': ca})

p1 = figure(title="Callback using source.selected.on_change('indices', callback)")
r1 = p1.circle(x='xa', y='ya', color='ca', size=10, source=data12)

# 1 st method
p1.add_tools(PointDrawTool(renderers=[r1], add=False))
data12.selected.on_change('indices', callback_source_change)

# 2 st method
p2 = figure(title="Callback using figure.on_event(Pan, callback)")
r2 = p2.circle(x='xa', y='ya', color='ca', size=10, source=data12)
p2.add_tools(PointDrawTool(renderers=[r2], add=False))
p2.on_event(PanEnd, callback_pan)

# Target Result
p3 = figure(title="Callback Result")
p3.circle(x='xa', y='ya', color='ca', source=data3)

p1.x_range = p2.x_range = p3.x_range
p1.y_range = p2.y_range = p3.y_range

columns = [TableColumn(field="xa", title="xa"),
           TableColumn(field="ya", title="ya"),
           TableColumn(field="ca", title="ca")]

data_table12 = DataTable(source=data12, columns=columns)
data_table3 = DataTable(source=data3, columns=columns)

p = Row(Column(p1, data_table12),
        Column(p2, data_table12),
        Column(p3, data_table3))

curdoc().add_root(p)

Note:
on 1st method, the target data source not immediately updated. It updated on on second trial on start of dragging.

on 2 method, call back also occur during figure Panning (see terminal output)

Best regards,

I don’t know if there’s a better way to do it, but you can create a custom draw tool by extend the PointDrawTool and overriding its _pan_end method in TypeScript.

What about an actual PanEnd event callback?

yes it did work very well, but the callback also executed when im panning on the figure without draging my circle object.

Perhaps you could condition on the active tool in the callback, and do nothing when it is the plain PanTool (or, only do anything when it is the point drag tool)

I was thinking the same way too,
is there any way to get which tools is being active at the time?

active_drag on the toolbar:

The issue here is that even if you have the tool activated and just try to drag the plot itself (i.e. something that’s a no-op), you will still get PanEnd. There seems to be no way to check if an actual action has been done.

Ah, fair enough. It may just be a case of something that would require new development, then. E.g. perhaps gesture tools specifically could fire a new GestureDone event that could include some extra context about what happened. Seems a GitHub issue would be an appropriate next step.