Hi, I experience the following problem, with example code in this gist: custom-draw-lines · GitHub
The first part is a plot with built-in PolyDrawTool shown with bokeh server. After drawing with PolyDrawTool and clicking on ‘copy’ button, I can show the coordinates of drawn lines in python by running the callback_data
cell. Clicking the button copies the data source data into a dict which is referenced in python code, using python callback.
Here is the code for that part:
callback_data = {}
def plots_server(doc):
source = ColumnDataSource(data=dict(xs=[], ys=[]))
plot = figure(x_range=(0, 10), y_range=(0, 10))
plot.title.text = "Draw with built-in tool"
renderer = plot.multi_line('xs', 'ys', line_width=4, line_color='#000000', source=source, line_cap='round')
plot.add_tools(PolyDrawTool(renderers=[renderer]))
def button_callback():
callback_data['data'] = source.data
button = Button(label="copy data")
button.on_click(button_callback)
doc.add_root(column(plot, widgetbox([button,])))
show(plots_server)
The second part is a similar plot where lines are drawn using custom javascript callbacks (tap to start drawing, tap to finish drawing). But data source is not updated on the python side. When I run callback_data
cell, it always show {'data': {'xs': [], 'ys': []}}
.
I wonder if I missed calling some additional sync function on javascript or python side to make it work similar to the built-in line editor. Or maybe there is a better approach on making a custom drawing tool that would synchronize data back to python side when running in bokeh server.
Here is the code for the second part:
ADD_LINE_JS = """
var tool_name = 'test-line-plugin';
// Init globals.
if (window[tool_name] === undefined) {
window[tool_name] = {'state': 'none'};
}
var tool = window[tool_name];
// Shorthands.
var cx = cb_obj.x; // current mouse x
var cy = cb_obj.y; // current mouse y
var xs = data.data.xs; // x of lines
var ys = data.data.ys; // y of lines
// React to mouse events.
if (cb_obj.event_name == 'tap') {
if (tool.state == 'none') {
// First tap: add start and end point of line.
tool.state = 'adding';
// Draw line.
xs.push([cx, cx]);
ys.push([cy, cy]);
// Update the plot.
data.change.emit();
} else if (tool.state == 'adding') {
// Second tap: end point of line.
tool.state = 'none';
xs[xs.length-1][1] = cx;
ys[ys.length-1][1] = cy;
data.change.emit();
}
} else if (cb_obj.event_name == 'mousemove') {
if (tool.state == 'adding') {
// Between first and second tap: intermediate end point.
xs[xs.length-1][1] = cx;
ys[ys.length-1][1] = cy;
data.change.emit();
}
}
"""
callback_data = {}
def plots_server(doc):
source = ColumnDataSource(data=dict(xs=[], ys=[]))
plot = figure(x_range=(0, 10), y_range=(0, 10))
plot.title.text = "Draw with custom js callbacks"
renderer = plot.multi_line('xs', 'ys', line_width=4, line_color='#000000', source=source, line_cap='round')
plot.js_on_event(events.Tap, CustomJS(args=dict(data=source), code=ADD_LINE_JS))
plot.js_on_event(events.MouseMove, CustomJS(args=dict(data=source), code=ADD_LINE_JS))
def button_callback():
callback_data['data'] = source.data
button = Button(label="copy data")
button.on_click(button_callback)
doc.add_root(column(plot, widgetbox([button,])))
show(plots_server)
This example uses has custom line editor solely to show the problem with data source sync. My original goal is to draw Bezier curves, and since there is no built-in editor, I made a custom one using similar callbacks.
I used bokeh 1.4.0 for testing.