Drag and Delete event for Plot

HI All,

I am trying to implement an interactive plot where the user can add points to a
multi-segments line.

So far I managed to do this:

from bokeh.plotting import figure, output_notebook, show
from bokeh.models import ColumnDataSource, PointDrawTool
from bokeh.events import Tap
import numpy as np

output_notebook()

def bkapp(doc):

    p = figure(x_range=(0, 10), y_range=(0, 10), width=400, height=400)

    source = ColumnDataSource({'x': [1, 5, 9], 'y': [1, 5, 9]})

    def add_point(event):
        x = np.array(source.data["x"])
        y = np.array(source.data["y"])
        indices = np.argsort(x)
        x = np.sort(x)
        y = y[indices]
        data = {"x": list(x), "y": list(y)}
        source.data = data

    renderer = p.scatter(x="x", y="y", source=source, size=10)
    draw_tool = PointDrawTool(renderers=[renderer], empty_value='black')
    p.line(x="x", y="y", source=source)
    p.on_event(Tap, add_point)

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

    doc.add_root(p)

show(bkapp)

That seems to work just fine… My issue is that I would like the the python callback to be called when a point is deleted or moved.

Any ideas?

R

Hi @rbeucher lease edit your post to use code formatting so that the code is intelligible (either with the </> icon on the editing toolbar, or triple backtick ``` fences around the code blocks)

1 Like

@rbeucher the main function of edit tools like PointDrawTool is to update a data source, which is how points are added or deleted. So, what you want is a callback on the source, e.g

source.on-change('data', ...)

Hi @Bryan

Thanks. I tried that. The issue is that this results in some recursion that I don’t want…

from bokeh.plotting import figure, output_notebook, show
from bokeh.models import ColumnDataSource, PointDrawTool
from bokeh.events import Tap
import numpy as np

output_notebook()

def bkapp(doc):

    p = figure(x_range=(0, 10), y_range=(0, 10), width=400, height=400)

    source = ColumnDataSource({
        'x': [1, 5, 9], 'y': [1, 5, 9]})

    def add_point(attr, old, new):
        x = np.array(source.data["x"])
        y = np.array(source.data["y"])
        indices = np.argsort(x)
        x = np.sort(x)
        y = y[indices]
        data = {"x": list(x), "y": list(y)}
        source.data = data
        print("OK")

    renderer = p.scatter(x="x", y="y", source=source, size=10)
    draw_tool = PointDrawTool(renderers=[renderer], empty_value='black')
    p.line(x="x", y="y", source=source)
    source.on_change("data", add_point)

    p.add_tools(draw_tool)
    p.toolbar.active_tap = draw_tool
    
    doc.add_root(p)

show(bkapp) 

@rbeucher Right, you are updating the data, in a callback on data updates. Is that actually something you need to do? If you just need be able to add/delete points, then the tool does that for you, including updating the data source as necessary.

Well I want to sort the points and have them available via Python