☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

How to maintain selection when filtering?

Community Supportserver
grantjenks
grantjenks

I’m trying to support three things: scatter plot, table with only lasso selection displayed, and range slider for filtering. Here’s my attempt:

from random import random

from bokeh.layouts import layout
from bokeh.models import ColumnDataSource, CustomJS, RangeSlider
from bokeh.plotting import figure, curdoc

x = [random() for x in range(1_000)]
y = [random() for y in range(1_000)]

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=[]))

from bokeh.models import DataTable, TableColumn

columns = [
    TableColumn(field="x", title='x'),
    TableColumn(field="y", title='y'),
]
data_table = DataTable(source=s2, columns=columns)

s1.selected.js_on_change('indices', CustomJS(args=dict(s1=s1, s2=s2), 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();
    """)
)

x_slider = RangeSlider(title='X', start=0, end=1, value=(0, 1), step=0.01)

def update():
    min_x, max_x = x_slider.value
    indices = [i for i, e in enumerate(x) if min_x <= e <= max_x]
    s1.data = dict(
        x=[x[i] for i in indices],
        y=[y[i] for i in indices],
    )

x_slider.on_change('value', lambda attr, old, new: update())

layout = layout([p1, data_table], x_slider)
doc = curdoc()
doc.add_root(layout)

The lasso selection/table and range slider work well independently but not together. If I select data points with lasso then change the slider, the selected points in the scatter plot change. I’m replacing s1.data in update() but I probably also need to update the selection? I’m not really sure how selections work. Help appreciated!

⌁   Link to post
p-himik
p-himik

The jumping selection is because you change the set of points but the s1.selected.indices stays the same, I think.

In any case, this is how I’d do it:

from random import random

from bokeh.layouts import layout
from bokeh.models import ColumnDataSource, CustomJS, RangeSlider, CDSView, CustomJSFilter
from bokeh.plotting import figure, curdoc

p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")

from bokeh.models import DataTable, TableColumn

ds = ColumnDataSource(data=dict(x=[random() for x in range(1_000)],
                                y=[random() for y in range(1_000)]))

x_slider = RangeSlider(title='X', start=0, end=1, value=(0, 1), step=0.01)
slider_filter = CustomJSFilter(args=dict(slider=x_slider),
                               # language=JavaScript
                               code="""
                                  const [min_x, max_x] = slider.value;
                                  return source.data['x'].reduce((acc, x, idx) => {
                                      if (x >= min_x && x <= max_x) acc.push(idx);
                                      return acc
                                  }, [])
                              """)
x_slider.js_on_change('value', CustomJS(args=dict(ds=ds), code="ds.change.emit();"))

selection_filter = CustomJSFilter(code="return source.selected.indices;")
ds.selected.js_on_change('indices', CustomJS(args=dict(ds=ds), code="ds.change.emit();"))

circle_view = CDSView(source=ds, filters=[slider_filter])
p1.circle('x', 'y', source=ds, view=circle_view, alpha=0.6)

table_view = CDSView(source=ds, filters=[slider_filter, selection_filter])
columns = [
    TableColumn(field="x", title='x'),
    TableColumn(field="y", title='y'),
]
data_table = DataTable(source=ds, columns=columns, view=table_view)

layout = layout([p1, data_table], x_slider)
doc = curdoc()
doc.add_root(layout)

⌁   Link to post

End of archived discussion · Replies are disabled