CustomJS modifying BooleanFilter

Hi all,

I have a multiline plot (say columns ‘x’ and ‘y’) and I’d like to filter values based on other columns of the ColumnDataSource (say ‘units’ and ‘depth’). I can achieve this statically with:

zmin = 20.
zmax = 40.
bools2a = [True if u == '2a' else False for u in source.data['units']]
boolsZ = [True if z > zmin and z < zmax else False for z in source.data['depths']]
bfZ = BooleanFilter(boolsZ)
view2a = CDSView(source = source, filters=[BooleanFilter(bools2a),bfZ])

now, I’d like to use a RangeSlider to act on zmin and zmax to interactively filter the plotted data:

range_slider = RangeSlider(
    title="depth range",
    start=0,
    end=70,
    step=1,
    value=(zmin, zmax),
)

but even after studying the docs for one entire day I’m not able to achieve this with CustomJS, can anyone provide an example?

What version of bokeh are you using? CDSView (as of 3.x I believe) no longer takes “filters” but rather a single filter (and you use IntersectionFilter/UnionFilter etc. to blend them)…

Anyway, here’s a snippet. Not using BooleanFilter because I don’t think that’s actually what you need → You want to collect the indices from src meeting a certain condition (i.e. is depth_range within the bounds of the range slider?) and then use those indices to update an IndexFilter.

from bokeh.plotting import figure, show
from bokeh.models import CustomJS, RangeSlider, CustomJS, IndexFilter, CDSView, ColumnDataSource
from bokeh.layouts import column

src = ColumnDataSource(data={'x':[[1,2,3],[1,2,3],[1,2,3]]
                             ,'y':[[3,1,2],[2,2,1],[2,3,4]]
                             ,'depth_range':[5,4,3]
                             ,'c':['blue','red','green']
                             })

sl = RangeSlider(start=0,end=6, step=1,value=(0,6),title='depth_range')

f = figure()

#initalize filter containing ALL indices --> because starting the slider at either end of its start/end
filt = IndexFilter(indices=list(range(len(src.data['depth_range']))))
view = CDSView(filter=filt)
r = f.multi_line(xs='x',ys='y',line_color='c',source=src,view=view)

#the callback
cb = CustomJS(args=dict(src=src, sl=sl, filt=filt)
              ,code='''
              var inds = []
              //for every item in the src, if its depth range value lies within the slider bounds, append it to the inds list
              for (var i = 0; i<src.data['depth_range'].length; i++){
                      if (src.data['depth_range'][i] >= sl.value[0] && src.data['depth_range'][i] <= sl.value[1]){
                              inds.push(i)}
                      }
              //update the filter with the new indices and emit the change to the src
              filt.indices = inds
              src.change.emit()
              ''')
#want this code to trigger when value of slider changes             
sl.js_on_change('value',cb)

show(column([f,sl]))
2 Likes

Thanks for your thorough reply. I didn’t manage to try it yet, I’ll do it now. Just one thing:
shouldn’t it be:
filt.change.emit()
rather than
src.change.emit()
?

PS: yep I was using Bokeh 2.something by mistake, in fact the inconsistency with the documentation was sort of puzzling me. Now I’m using 3.1.0

shouldn’t it be:
filt.change.emit()
rather than
src.change.emit()

To this day I’m a little bit shaky on the internals as to why src.change.emit() actually works… but see here for when I first asked this exact question → Understanding change.emit() with filters/sources/glyphs

Lots of things trigger off of data source change updates. it is often the best way to ask for a full redraw. TBH none of this internal eventing should be user-visible at all, it would be much better to define a small stable API surface that could be used in JS callbacks, but no-one has has the chance to work on such yet.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.