Hello! I am trying to modify data selections made via a DataTable so that more rows are selected than just the ones the user clicked on (e.g. to select all the rows that also match on a certain column). But I am having trouble with an infinite loop in my customJS. Here is an example that I think almost works:
import numpy as np
import pandas as pd
from bokeh.io import show, push_notebook
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CDSView, CustomJS, CustomJSFilter, Slider, TableColumn, DataTable, SelectEditor
x = np.arange(0, 10, 0.1)
dfs = []
tstep = 1
ts = range(0, 100, tstep)
for t in ts:
y = x**(t/50.)
dfs.append(pd.DataFrame({"x": x, "y": y, "t": t}))
df = pd.concat(dfs)
cds = ColumnDataSource(df)
t_slider = Slider(start=ts[0], end=ts[-1], step=tstep, value=0)
# Callback to notify downstream objects of data change
change_callback = CustomJS(args=dict(source=cds), code="""
source.change.emit();
""")
t_slider.js_on_change('value', change_callback)
# JS filter to select data rows matching t value on slider
js_filter = CustomJSFilter(args=dict(slider=t_slider), code="""
const indices = [];
// iterate through rows of data source and see if each satisfies some constraint
for (let i = 0; i < source.get_length(); i++){
if (source.data['t'][i] == slider.value){
indices.push(true);
} else {
indices.push(false);
}
}
return indices;
""")
# Use the filter in a view
view = CDSView(filter=js_filter)
# Add table to use for selecting data
columns = [
TableColumn(field="x", title="x", editor=SelectEditor()),
TableColumn(field="y", title="y", editor=SelectEditor()),
]
data_table = DataTable(source=cds, columns=columns, editable=True, width=800, view=view)
# Custom JS to select points across all t values that match x,y in the selected row.
custom_js_table = CustomJS(args=dict(source=cds), code="""
const selected_x = [];
const selected_y = [];
for (let i = 0; i < source.selected.indices.length; i++){
let ind = source.selected.indices[i];
selected_x.push(source.data['x'][ind]);
selected_y.push(source.data['y'][ind]);
}
const new_selected_indices = [];
for (let i = 0; i < source.get_length(); i++){
for (let j = 0; j < selected_x.length; j++){
if ( (source.data['x'][i] == selected_x[j])
&& (source.data['y'][i] == selected_y[j]) ){
new_selected_indices.push(i);
}
}
}
source.selected.indices = new_selected_indices;
""")
# cds.selected.js_on_change("indices", custom_js_table)
p = figure(x_range=(0,10), y_range=(0,100))
p.scatter(x='x', y='y', source=cds)
layout = column(p) #, t_slider) #, data_table)
show(layout)
The issue I guess is that cds.selected.js_on_change("indices", custom_js_table)
is triggering on changes to cds.selected.indices
, which I then also change in the callback, which I supposed triggers the callback again, looping forever. Is there a smarter way to do this that doesn’t cause an infinite loop?