Bar chart with select widget

Hello!

I’m trying to create a bar chart that is updated by option selected in select widget using customJS.

But it is not updating the plot I don’t know why.

This is my code:

import numpy as np

notif = ['Notificacao1', 'Notificacao2', 'Notificacao3', 'Notificacao4',

'Notificacao5', 'Notificacao6', 'Notificacao7', 'Notificacao8', 'Notificacao9',

'Notificacao10', 'Notificacao11', 'Notificacao12', 'Notificacao13', 

'Notificacao14', 'Notificacao15', 'Notificacao16', 'Notificacao17',

'Notificacao18', 'Notificacao19', 'Notificacao20', 'Notificacao21',

'Notificacao22', 'Notificacao23', 'Notificacao24', 'Notificacao25',

'Notificacao26', 'Notificacao27', 'Notificacao28', 'Notificacao29',

'Notificacao30']

notif = np.array(notif)

group = [1] * 10 + [2] * 10 + [3] * 10

group = np.array(group)

import pandas as pd

df = pd.DataFrame(dict(notif = notif,group = group))

from bokeh.io import show, output_file

from bokeh.plotting import figure

from bokeh.models import Select,ColumnDataSource, CustomJS

from bokeh.layouts import column

source = ColumnDataSource(data = df)

p = figure(y_range = source.data['notif'][source.data['group'] == 1].tolist())

p.hbar(y = 'notif', source = source, height = .9, right = .1)

select = Select(title = 'Grupo', value = '1', options = ['1','2','3'])

callback = CustomJS(args = dict(source = source, select = select), 

                    code = """ var data = source.data;

                               var G = int(select.value);

                               group = data['group']

                               data['notif'] = data['notif'][group == G]

                               data['group'] = data['group'][group == G]                        

                               

                    source.change.emit()""")

select.js_on_change('value', callback)

layout = column(select, p)

show(layout)

Hi lunna.borges!

I think conceptually what you want is actually a CustomJSFilter. I played with your code a little bit to work up an example. The tricky thing ended up being your categorical y-axis, so in this example we have the CustomJSFilter to actually do the work of filtering the dataset, and a CustomJS callback to reset the y-axis values to match your new view. (I would love to see this done a shorter way, but this works!)

import numpy as np
import pandas as pd
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models import Select, ColumnDataSource, CustomJS, CustomJSFilter, CDSView
from bokeh.layouts import column

notif = ['Notificacao1', 'Notificacao2', 'Notificacao3', 'Notificacao4', 'Notificacao5', 'Notificacao6', 'Notificacao7',
         'Notificacao8', 'Notificacao9', 'Notificacao10', 'Notificacao11', 'Notificacao12', 'Notificacao13',
         'Notificacao14', 'Notificacao15', 'Notificacao16', 'Notificacao17', 'Notificacao18', 'Notificacao19',
         'Notificacao20', 'Notificacao21','Notificacao22', 'Notificacao23', 'Notificacao24', 'Notificacao25',
         'Notificacao26', 'Notificacao27', 'Notificacao28', 'Notificacao29', 'Notificacao30']

notif = np.array(notif)

group = [1] * 10 + [2] * 10 + [3] * 10
group = np.array(group)

df = pd.DataFrame(data={'notif': notif, 'group': group})
source = ColumnDataSource(data=df)

select = Select(title='Grupo', value='1', options=['1', '2', '3'])

p = figure(y_range=df['notif'].loc[df['group'] == int(select.value)].tolist())

custom_filter = CustomJSFilter(args=dict(source=source, select=select), code='''
var indices = [];
// iterate through rows of data source and see if each satisfies some constraint
for (var i = 0; i < source.get_length(); i++){
    if (source.data['group'][i] == select.value){
        indices.push(true);
    } else {
        indices.push(false);
    }
}
return indices;
''')

filtered_view = CDSView(source=source, filters=[custom_filter])
p = figure(y_range=df['notif'].loc[df['group'] == int(select.value)].tolist())
bars = p.hbar(y='notif', source=source, height=.9, right=.1, view=filtered_view)

callback = CustomJS(args=dict(source=source, select=select, p=p), code="""
    new_y_range = [];
    for (var i = 0; i < source.get_length(); i++){
      if (source.data['group'][i] == select.value)
          new_y_range.push(source.data['notif'][i]);
    }
    p.y_range.factors = new_y_range;
    source.change.emit()
    """)
select.js_on_change('value', callback)

layout = column(select, p)
show(layout)

Dear carolyn,

thank you so much to answer my question.

I’m excited to run the code and see the final result of it but I’m having problems to import CustomJSFilter from bokeh.models module.

When I try to do it, I get the following error:

ImportError                               Traceback (most recent call last)
Untitled-1 in ()
----> 1 from bokeh.models import CustomJSFilter

ImportError: cannot import name 'CustomJSFilter'

I believe it is because of a difference in bokeh package version but the only version of bokeh that I can install is 0.12.5, I don’t know why :slightly_frowning_face:

I’m using vscode, python 3.5.2 (64-bit) and satisfying the conditions bellow:

Jinja2 >=2.7
numpy >=1.7.1
packaging >=16.8
pillow >=4.0
python-dateutil >=2.1
PyYAML >=3.10
six >=1.5.2
tornado >=4.3

Could you help me understand what is going on?

Hi @lunna.borges unfortunately 0.12.5 is way too old, CustomJSFilter was added some time after that release. So that will not be an option.

There’s two main issues with the code you have already. The first is this:

JavaScript does not support any Pandas-like fancy indexing. You will need to write an explicit loop that loops over the entire data set and pulls out the things you care about. And then the second issue is that you will need to pass two CDS the CustomJS code: a “full” CDS that always has all the data, and a “glyph” CDS that has the subset you want to drive the glyph. If you just send one CDS and then overwrite it as you are now, as soon as you make one update, you are throwing away information you can never get back.

Here is an example that may be a useful reference:

https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-selections