Update content in second multiselect widget

Hi,

Added the code below with test source.
Is it possible to update the second multiselect widget to only show values that have data points for what the user selects on the first multiselect widget.
So in the below code if the user selects ‘a’ the second multiselect tool updates and only showes ‘a-2’, ‘a-5’.


from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJSTransform, MultiSelect, CustomJS
from bokeh.plotting import figure
from bokeh.transform import transform
import numpy as np

p = figure()
ds = ColumnDataSource(dict(x=list(range(10)), 
                           y=list(range(10)), 
                           names = ['a', 'b', 'c', 'c', 'd', 'd', 'a', 'a', 'c', 'c'], 
                           name_id = ['a-5', 'b-1', 'c-1', 'c-1', 'd-1', 'd-1', 'a-2', 'a-5', 'c-2', 'c-2']))

b = BooleanFilter([True if x == 'a' else False for x in ds.data['names']])
selectoroptions = [str(x) for x in sorted(np.unique(ds.data['names']))]
ms = MultiSelect(title= 'MC Experiment:', value = ['a'],
                                    options=selectoroptions)

b_id = BooleanFilter([True if x == 'a-5' else False for x in ds.data['name_id']])
selectoroptions = [str(x) for x in sorted(np.unique(ds.data['name_id']))]
ms_id = MultiSelect(title= 'ID:', value = ['a-5'],
                                    options=selectoroptions)

ms.js_on_change('value', CustomJS(args = dict(f=b, source=ds, columnName = 'names'),
                                      code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      source.change.emit();                                      
                                      """))

ms_id.js_on_change('value', CustomJS(args = dict(f=b_id, source=ds, columnName = 'name_id'),
                                      code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      source.change.emit();                                      
                                      """))


view = CDSView(source=ds, filters=[b, b_id])
tr = CustomJSTransform(v_func="return xs.map(x => 10 * x);")
p.scatter('x', transform('y', tr), view=view, source=ds, color='red')

show(column(p, ms, ms_id))

Here’s how I would do it. The main change is in the first CustomJS.

from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJSTransform, MultiSelect, CustomJS
from bokeh.plotting import figure
from bokeh.transform import transform

p = figure()
ds = ColumnDataSource(dict(x=list(range(10)),
                           y=list(range(10)),
                           names=['a', 'b', 'c', 'c', 'd', 'd', 'a', 'a', 'c', 'c'],
                           name_id=['a-5', 'b-1', 'c-1', 'c-1', 'd-1', 'd-1', 'a-2', 'a-5', 'c-2', 'c-2']))

options = sorted(set(ds.data['names']))
initial_value = [options[0]]
b = BooleanFilter([x in initial_value for x in ds.data['names']])
ms = MultiSelect(title='MC Experiment:', value=initial_value, options=options)

id_options = sorted(set(i for n, i in zip(ds.data['names'], ds.data['name_id'])
                        if n in initial_value))
initial_id_value = [id_options[0]]
b_id = BooleanFilter([x in initial_id_value for x in ds.data['name_id']])
ms_id = MultiSelect(title='ID:', value=initial_id_value, options=id_options)

ms.js_on_change('value', CustomJS(args=dict(f=b, source=ds, columnName='names', ms_id=ms_id),
                                  code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      const id_options = [...new Set(source.data.name_id.filter((_, idx) => f.booleans[idx]))];
                                      id_options.sort();
                                      
                                      // New lines.
                                      ms_id.options = id_options;
                                      ms_id.value = [id_options[0]];
                                      
                                      source.change.emit();                                      
                                      """))

ms_id.js_on_change('value', CustomJS(args=dict(f=b_id, source=ds, columnName='name_id'),
                                     code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      source.change.emit();                                      
                                      """))

view = CDSView(source=ds, filters=[b, b_id])
tr = CustomJSTransform(v_func="return xs.map(x => 10 * x);")
p.scatter('x', transform('y', tr), view=view, source=ds, color='red')

show(column(p, ms, ms_id))

Thanks! That worked perfectly!
Do you know if its possible to have the tool select all available ids?
In the plot above it finds a-2 and a-5 and plots a-2. Is it possible for it to plot all the ids for the specified letter. If not I can just do CTRL-A in the multiselect, just wondering if its possible:D

Yes, just replace ms_id.value = [id_options[0]]; with ms_id.value = id_options;.

Awesome. Thanks!

Last question btw! :stuck_out_tongue:
I tried adding a third multiselect. But now its not plotting anything. Even if I select everything. You have an idea why?

from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJSTransform, MultiSelect, CustomJS
from bokeh.plotting import figure
from bokeh.transform import transform

p = figure()
ds = ColumnDataSource(dict(x=list(range(10)),
                           y=list(range(10)),
                           tc=list(range(10)),
                           temp_int=['(20, 30]', '(40, 50]', '(20, 30]', '(20, 30]', '(40, 50]', '(20, 30]', '(20, 30]', '(20, 30]', '(20, 30]', '(40, 50]'],
                           matrix_names=['ras', 'das', 'ras', 'das', 'das', 'bas', 'ras', 'ras', 'ras', 'bas'],
                           names=['a', 'b', 'c', 'c', 'd', 'd', 'a', 'a', 'c', 'c'],
                           name_id=['a-5', 'b-1', 'c-1', 'c-1', 'd-1', 'd-1', 'a-2', 'a-5', 'c-2', 'c-2']))

options = sorted(set(ds.data['names']))
initial_value = [options[0]]
b = BooleanFilter([x in initial_value for x in ds.data['names']])
ms = MultiSelect(title='MC Experiment:', value=initial_value, options=options)

id_options = sorted(set(i for n, i in zip(ds.data['names'], ds.data['name_id'])
                        if n in initial_value))
initial_id_value = id_options#[id_options[0]]
b_id = BooleanFilter([x in initial_id_value for x in ds.data['name_id']])
ms_id = MultiSelect(title='ID:', value=initial_id_value, options=id_options)

mn_options = sorted(set(ds.data['matrix_names']))
mn_initial_value = [mn_options[0]]
b_mn = BooleanFilter([x in initial_value for x in ds.data['matrix_names']])
ms_mn = MultiSelect(title='Matrix Names:', value=mn_initial_value, options = mn_options)

ms.js_on_change('value', CustomJS(args=dict(f=b, source=ds, columnName='names', ms_id=ms_id, ms_mn = ms_mn),
                                  code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      
                                      // New lines.

                                      const id_options = [...new Set(source.data.name_id.filter((_, idx) => f.booleans[idx]))];
                                      id_options.sort();
                                      
                                      ms_id.options = id_options;
                                      ms_id.value = id_options;

                                      const mn_options = [...new Set(source.data.matrix_names.filter((_, idx) => f.booleans[idx]))];
                                      mn_options.sort();
                                      
                                      ms_mn.options = mn_options;
                                      ms_mn.value = mn_options;

                                      source.change.emit();                                      
                                      """))

ms_id.js_on_change('value', CustomJS(args=dict(f=b_id, source=ds, columnName='name_id'),
                                     code="""\
                                      const val = cb_obj.value;
                                      f.booleans = Array.from(source.data[columnName]).map(d =>  val.includes(d != null && d.toString()));
                                      source.change.emit();                                      
                                      """))

view = CDSView(source=ds, filters=[b, b_id, b_mn])
tr = CustomJSTransform(v_func="return xs.map(x => 10 * x);")
p.scatter('x', transform('y', tr), view=view, source=ds, color='red')

show(column(p, ms, ms_id, ms_mn))

Add print(b_mn.booleans) somewhere and see what it prints.

Haha! Thanks man:D