Search code examples
pythonbokeh

Python 3.x Bokeh - Sort and Update ColumnDataSource After PointDrawTool


The python code below plots x,y and allows to edit/add new points with PointDrawTool in Bokeh. When adding new points, I would like to connect all points with a line based on x values. Currently, the new points are added at the end and the line is connected from the last point from the original data to the new ones. What would be an optimum way to resort the source data after adding new points and redraw the line? In my real data, the size are hundreds to few thousands points.

Also, is there a way to allow to insert a new cell below selected cell in the table and add new data point then update the figure. Thanks.

from bokeh.plotting import Column, ColumnDataSource, figure, output_file, show
from bokeh.models import DataTable, TableColumn, PointDrawTool, ColumnDataSource

x=[1, 2, 3, 4, 5, 6, 7, 8]
y=[1,2,1,2,1,2,3,1]
data = {'x': x,
        'y': y,
       'color': ['blue']*len(x)}

source = ColumnDataSource(data=data)

p = figure(plot_width=400, plot_height=400)

p.line('x', 'y', line_width=2, source=source)
xyp = p.scatter(x='x', y='y', source=source, color='color', size=10)
columns = [TableColumn(field="x", title="x"),
           TableColumn(field="y", title="y"),
           TableColumn(field='color', title='color')]
table = DataTable(source=source, columns=columns, editable=True, height=200)

draw_tool = PointDrawTool(renderers=[xyp], empty_value='black')
p.add_tools(draw_tool)
p.toolbar.active_tap = draw_tool

show(Column(p, table))

Solution

  • Add this to your code:

    from bokeh.models import CustomJS
    
    
    source.js_on_change('data', CustomJS(code="""\
        let prev_x = null;
        let sorted = true;
        for (const x of cb_obj.data.x) {
            if (prev_x !== null && prev_x > x) {
                sorted = false;
                break;
            }
            prev_x = x;
        }
        if (!sorted) {
            const acc = (cb_obj.data.x
                         .map((x, idx) => [x, cb_obj.data.y[idx]])
                         .sort(([x1], [x2]) => x1 - x2));
            cb_obj.data.x = acc.map(([x]) => x);
            cb_obj.data.y = acc.map(([, y]) => y);
            cb_obj.change.emit();
        }
    """))