0

To illustrate my issue, I adapted the example from here.

Let's say I have several datasets of different dimensions which I want to plot in the same figure and then make selections, how would I then store the selected data in e.g. a dictionary (or any other structure as numpy array, pandas dataframe,...)? So the data selection could look like this

enter image description here

Is there now a way to store the selected data in the form:

{'x': ['x_1_selected', 'x_2_selected', '...', 'x_n_selected'],
 'x1': ['x1_1_selected', 'x1_2_selected', '...', 'x1_n_selected'],
 'y': ['y_1_selected', 'y_2_selected', '...', 'y_n_selected'],
 'y1': ['y1_1_selected', 'y1_2_selected', '...', 'y1_n_selected']}

?

To give a bit of background: The user should be allowed to load different datasets, visualize them, make the selections using the lasso and the selected data should then be used in other functions, so they need to be stored in a structure that can easily be passed to other functions I defined.

Is there, e.g. a way to globally update s2 and s2b (see code below)? When I now check s2.data and s2b.data they still show the empty lists.

This is the code:

from random import random

from bokeh.layouts import row
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.plotting import figure, output_file, show

output_file("callback.html")

x = [random() for x in range(500)]
y = [random() for y in range(500)]
x2 = [random() for x2 in range(100)]
y2 = [random() for y2 in range(100)]

# the two different data sources of different length
s1 = ColumnDataSource(data=dict(x=x, y=y))
s1b = ColumnDataSource(data=dict(x2=x2, y2=y2))

# the figure with all source data where we make selections
p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")
p1.circle('x', 'y', source=s1, alpha=0.6, color='red')
p1.circle('x2', 'y2', source=s1b, alpha=0.6, color='black')

# second figure which is empty initially where we show the selected datapoints
s2 = ColumnDataSource(data=dict(x=[], y=[]))
s2b = ColumnDataSource(data=dict(x2=[], y2=[]))
p2 = figure(plot_width=400, plot_height=400, x_range=(0, 1), y_range=(0, 1),
            tools="", title="Watch Here")
p2.circle('x', 'y', source=s2, alpha=0.6, color='red')
p2.circle('x2', 'y2', source=s2b, alpha=0.6, color='black')

# individual callback for different datasets
s1.callback = CustomJS(args=dict(s2=s2), code="""
        var inds = cb_obj.selected['1d'].indices;
        var d1 = cb_obj.data;
        var d2 = s2.data;
        d2['x'] = []
        d2['y'] = []
        for (i = 0; i < inds.length; i++) {
            d2['x'].push(d1['x'][inds[i]])
            d2['y'].push(d1['y'][inds[i]])
        }
        s2.change.emit();
    """)

s1b.callback = CustomJS(args=dict(s2b=s2b), code="""
        var inds = cb_obj.selected['1d'].indices;
        var d1 = cb_obj.data;
        var d2 = s2b.data;
        d2['x2'] = []
        d2['y2'] = []
        for (i = 0; i < inds.length; i++) {
            d2['x2'].push(d1['x2'][inds[i]])
            d2['y2'].push(d1['y2'][inds[i]])
        }
        s2b.change.emit();
    """)

layout = row(p1, p2)

show(layout)
Cleb
  • 25,102
  • 20
  • 116
  • 151

1 Answers1

1

Given that you mentioned storing the selected data in a numpy array or a pandas dataframe, I assume that you plan to actually use bokeh serve. In this case, you don't need to write any JS code since all Bokeh DataSources have selected attribute, and you can attach a Python callback to it. Just replace your callbacks code with this:

def attach_selection_callback(main_ds, selection_ds):
    def cb(attr, old, new):
        new_data = {c: [] for c in main_ds.data}
        for idx in new['1d']['indices']:
            for column, values in main_ds.data.items():
                new_data[column].append(values[idx])
        # Setting at the very end to make sure that we don't trigger multiple events
        selection_ds.data = new_data

    main_ds.on_change('selected', cb)


attach_selection_callback(s1, s2)
attach_selection_callback(s1b, s2b)
Eugene Pakhomov
  • 9,309
  • 3
  • 27
  • 53
  • That runs with `bokeh serve` (only need the `curdoc().add_root(layout)` instead of show). I'll play a bit and get back to you... – Cleb Feb 16 '18 at 15:28
  • Ешё раз спасибочки! :) Seems that all my problems arise from the fact that I still do not fully understand how these callbacks work, when to use `new`, `cb_obj`, what those variables actually return etc. – Cleb Feb 16 '18 at 23:21