Why doesn't multiselect work in the way checkboxgroup does? I expected this code to work but don't understand what's missing / what I'm doing wrong

I’m trying to figure out how to filter data by both a checkbox group and a multiselect, and I can’t get the multiselect to work and don’t understand why. I think it’s because of how the multiselect handles the data, perhaps it somehow doesn’t give a list of active fields like the checkbox group does. I’ve poured over the documentation and it all looks fine and am seriously starting to question my sanity.

Here’s the simplified code with the JS code which I expected to work but didn’t commented out:

from bokeh.plotting import figure, show
from bokeh.models import Slider, CustomJSFilter, CDSView, ColumnDataSource, CustomJS
from bokeh.models.widgets import CheckboxGroup, MultiSelect
from bokeh.layouts import layout
from bokeh.transform import factor_cmap
data = dict(Apples=[97, 34, 23, 6, 26, 97, 21, 92, 73, 10, 92, 14, 77, 4, 25, 48, 26, 39, 93],
            Bananas=[87, 63, 56, 38, 57, 63, 73, 56, 30, 23, 66, 47, 76, 15, 80, 78, 69, 87, 28],
            Oranges=[21, 65, 86, 39, 32, 62, 46, 51, 17, 79, 64, 43, 54, 50, 47, 63, 54, 84, 79],
            Category = ['A', 'B', 'B', 'C', 'A', 'C', 'B', 'C', 'C', 'B', 'A', 'A', 'B', 'B', 'A', 'C', 'C', 'C', 'C'],
            Multi_Category = ['X', 'X', 'Y', 'Z', 'Z', 'X', 'Z', 'Y', 'X', 'Y', 'Y', 'Y', 'Y', 'X', 'Z', 'X', 'Y', 'Y', 'Y'])
colordict = dict(Colors = ['red', 'green', 'blue'],
                 Categories = ['A', 'B', 'C'],
                 Multi_Categories = ['X', 'Y', 'Z'])
source = ColumnDataSource(data=data)
checkbox_group = CheckboxGroup(labels= colordict['Categories'], active = [1])
checkbox_group.js_on_change("active", CustomJS(code="source.change.emit();", args=dict(source=source)))
multiselect = MultiSelect(value = colordict['Multi_Categories'], options = colordict['Multi_Categories'])
custom_filter = CustomJSFilter(args=dict(source=source, checkbox_group = checkbox_group, multiselect = multiselect), code=
'''var indices = [];
    selected = checkbox_group.active.map(i=>checkbox_group.labels[i]);
    column = source.data['Category'];
//    multselected = multiselect.active.map(i=>multiselect.labels[i]);
//    mult_column = source.data['Multi_Category'];
for (var i = 0; i < source.get_length(); i++){
    if ( selected.includes(column[i])
//    &&
//    multiselected.includes(mult_column[i))
   ){ indices.push(true);
    } else {indices.push(false);}}return indices;''')
view = CDSView(source=source, filters=[custom_filter])
p = figure()
p.circle('Oranges', 'Bananas', source=source, view=view, size=20, fill_color = factor_cmap('Category', palette = colordict['Colors'], factors = colordict['Categories']))
controls = [checkbox_group, multiselect]
l = layout([[controls, p]], sizing_mode="stretch_both")
show(l)

Could someone please help me understand how I can get this multiselect to work?

Again, sorry for all these stupid questions…

edit: fixed formatting for code

HI @tvkyq the MultiSelect does not have an active property, it has a value property that lists the actual selected values (i.e. strings, not numerical indices). You can always find basic information about what properties a Bokeh model has in the reference guide, e.g.

And the fastest way to get to one of these entries in the reference guide is to type the name of the model in the docs search box.

Edit: and FYI note that that link shows the deprecated callback property. You should avoid that and use the standard mselect.js_on_change('value', ...), in case you want a CustomJS callback on this widget.

Ok, great! Yeah I thought it was something along those lines! I had a look at that page but I wasn’t familiar enough with most of the terminology in the reference guide to understand that was the important difference.

I’m embarrassed to say I still don’t know how to get the Javascript code to work. I don’t know anything about JS and have only got this far through trying to adapt code from other examples until it somehow works… I tried changing the active to values and playing around with the variables that preceded the for loop but couldn’t quite get it right. Here’s what I tried to use which didn’t work:

var indices = []
selected = checkbox_group.active.map(i=>checkbox_group.labels[i])
column = source.data['Category']
for (var i = 0; i < source.get_length(); i++) {
  if (selected.includes(column[i]) && multiselect.value.includes(mult_column[i)) { 
     indices.push(true)
  } else {
     indices.push(false)
  }
}
return indices

Could I bother you once again to please point out what I might be doing wrong? Sorry mate… I feel like a real moron

Nevermind, I’m an idiot! Figured it out:

var indices = []
for (var i = 0; i < source.get_length(); i++) {
  if (checkbox_group.active.map(i=>checkbox_group.labels[i]).includes(source.data['Category'][i]) 
&& multiselect.value.includes(source.data['Multi_Categories'][i)) { 
         indices.push(true)
      } else {
         indices.push(false)
      }
    }
    return indices

Hope this helps someone else.

2 Likes

Glad you figured out a solution! FYI no one is born knowing any of this, so it’s only a matter of needing to learn over time, not being an idiot!