2

I hope everyone is doing well.

I am trying to develop a Bokeh interaction whereby selecting a part of a scatter plot will update a table.

I am using a lot of the sample code from the Bokeh documentation. My workplace is running an older version of Bokeh (0.12.5) so I had to change the last line in the Custom JS (from s2.change.emit() to s2.trigger('change). I then added in a few lines to create a DataTable.

I naively thought that since sourcing 's1' in the Datatable works, sourcing 's2' will allow me to link the table to the lasso select. I even tried adding in an extra trigger to the table widget in the JS callback.

Does anyone know how to create a table from a lasso select in a graph?

Code

Thanks in advance.

from random import random

from bokeh.layouts import row
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.plotting import figure, output_file, show
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn

output_file("callback.html")

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

s1 = ColumnDataSource(data=dict(x=x, y=y))
p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")
p1.circle('x', 'y', source=s1, alpha=0.6)

s2 = ColumnDataSource(data=dict(x=[], y=[]))
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)

###New code##
columns = [TableColumn(field ="x",  title = "X axis"),
           TableColumn(field ="y",  title = "Y axis")]

table = DataTable(source =s2, columns = columns, width =400, height =  280)


##Added in table.trigger('change') hoping this would link to the lasso select.
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.trigger('change');
        table.trigger('change');
    """)


##having 'table' in the layout, stops the callback from working, deleting table from the layout makes it work.
layout = row(p1, p2, table)

show(layout)
Kah
  • 492
  • 1
  • 5
  • 17

2 Answers2

4

Here's a working version for bokeh 1.0.4

from random import random

import bokeh.io
from bokeh.io import output_notebook, show

from bokeh.layouts import row
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn

from bokeh.resources import INLINE
bokeh.io.output_notebook(INLINE)

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

s1 = ColumnDataSource(data=dict(x=x, y=y))
p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")
p1.circle('x', 'y', source=s1, alpha=0.6)

s2 = ColumnDataSource(data=dict(x=[], y=[]))
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)

columns = [TableColumn(field ="x",  title = "X axis"),
           TableColumn(field ="y",  title = "Y axis")]

table = DataTable(source =s2, columns = columns, width =155, height = 380)


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

layout = row(p1, p2, table)

show(layout)

You can even select rows/values in the table and the points in the second plot will change opacity (and you can sort the table by clicking the headers)

working table callback in bokeh, screen shot

iolsmit
  • 383
  • 2
  • 13
2

Right now, your callback doesn't know, what "table" is. You need to pass it as an argument to your CustomJS function:

s1.callback = CustomJS(args=dict(s2=s2, table=table), 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.trigger('change');
    table.trigger('change');
""")
Saguara
  • 96
  • 3
  • Hi. Thanks for your help on this Saguara. Much appreciated. – Kah Feb 28 '18 at 01:22
  • I am still having issues with the code. Perhaps it is the version of Python I am using at work. I will try to running the code tonight on my home laptop. Cheers – Kah Feb 28 '18 at 01:28