Change when PointDrawTool syncs

I’m trying to get custom js code to run during move events for PointDrawTool.

The PointDrawTool docs has a note that mentions:

The data source updates will trigger data change events continuously throughout the edit operations on the BokehJS side. In Bokeh server apps, the data source will only be synced once, when the edit operation finishes.

From a Bokeh server app is it possible to enable continuous syncing?

There is not any direct way at present. Your CustomJS callback could set values of other properties/models that will sync.

@Bryan thanks for the response. Here is an example app:

# file is main.py within a "simple-pointdraw" directory.
from bokeh.models import ColumnDataSource, PointDrawTool, CustomJS
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.layouts import row, layout

left = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
           title='Point Draw Tool')
left.background_fill_color = 'lightgrey'
right = figure(x_range=(0, 10), y_range=(0, 10), tools=[],
           title='Synced Plot')
right.background_fill_color = 'lightgrey'

source = ColumnDataSource({
    'x': [1, 5, 9],
    'y': [1, 5, 9],
    'y2': [2, 6, 10],
    'color': ['red', 'green', 'yellow'],
})

js = """
for (var i = 0; i<cb_obj.data['y'].length; i++) {
	var y = cb_obj.data['y'][i];
	cb_obj.data['y2'][i] = y + 1;
}
cb_obj.change.emit();
"""
source.js_on_change('data', CustomJS(code=js))

left_renderer = left.scatter(x='x', y='y', source=source, color='color', size=10)
right_renderer = right.scatter(x='x', y='y2', source=source, color='color', size=10)

draw_tool = PointDrawTool(renderers=[left_renderer], empty_value='black')

left.add_tools(draw_tool)
left.toolbar.active_tap = draw_tool

curdoc().add_root(row(left, right))
curdoc().title = "Synced Plots"

Run using bokeh serve:

bokeh serve simple-pointdraw

The right plot gets updated y positions only after the point movement is complete. Is there a change.emit() I need to set to see changes during point movement?

change.emit is purely a JS-side notification, to sync a property back to Python, and actual assignment to the property must be made (i.e. with an “=”)

obj.prop = new_value

I should probably also clarify I was suggesting passing the data on a property of some other model, not the same data source itself. In the past a dummy invisible glyph might have been used for this. In recent versions, a custom DataModel is a better option.

I can easily imagine that updating the same data source as the tool might interfere with the tool. Maybe you will get lucky and it will “work”, but I definitely would not presume that that would be the case.

I might be misunderstanding you, I’m also a javascript noob :sweat_smile:. I edited my custom js code to:

var tmp_obj = {};
Object.assign(tmp_obj, cb_obj.data)
for (var i = 0; i<cb_obj.data['y'].length; i++) {
	var y = cb_obj.data['y'][i];
	tmp_obj['y2'][i] = y + 1;
}
cb_obj.data = tmp_obj;

but still have the same issue. I’m not quite sure how I would make DataModel work for me in this case. Is there an example you can point me to?

The callback is never getting called. I had to go look at the source code, but the tool does not result in any observable events being triggered mid-interaction. The CDS changes and events that trigger re-draws are all internal. I don’t think this approach is workable.

Is the “y+1” actually indicative of your real need? If so, then a different approach coudl be a CustomJSTransform for that transforms the to "y" column to provide computed values for the second glyph.

The actual operation/js-code is more complicated than the example I gave. I took a look at CustomJSTransform and don’t think it quite does what I need. But thanks for the reference, I was unaware of that model, it will come in handy elsewhere.

Is there anyway to set callbacks during that mid-interaction, while moving a point?

I think the only current way would be to create your own custom extension version of the edit tool. You can also submit a github issue for a feature request, perhaps the current behavior can be made configurable. I don’t have any immediate-term simple suggestions, however.

@Bryan , I ended up creating an issue. I tried creating a custom extension, but I’m more than a little out of my element working with typescript. Thanks again for the help!

1 Like

Right, I probably should have prefaced by saying that custom extensions are not trivial to begin with, and on even on that scale, a custom edit tool is probably at the extreme end. I think it’s probably feasible to make the sync behavior configurable, though I can’t speculate on exact time frames for the issue to be addressed.

1 Like