Filter Dataframe Using Bokeh Dropdown Widget/CustomJS

Viewed 3278

I have to make a standalone html dashboard so I'm trying to figure out how to add a callback to a bokeh dropdown widget using CustomJS. Problem is even after consulting other posts on variations on the subject I still can't figure it out. Any help would be appreciated! Ultimately, I would use the dropdown to filter a stacked bar chart, but I want to take a stab at figuring that out on my own after messing with filtering the datatable first.

I've consulted Filtering dataframe using Bokeh/Widget/Callback, Bokeh datatable filtering inconsistency, and Filtering dataframe using Bokeh/Widget/Callback, and Python bokeh CustomJS callback update DataTable widget. Additionally, I've been going through the docs at https://docs.bokeh.org/en/1.3.4/docs/user_guide/interaction/callbacks.html#userguide-interaction-jscallbacks,

import pandas as pd
from bokeh.models.widgets import Dropdown
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource, DataTable, TableColumn, CustomJS
from bokeh.io import show, output_file, output_notebook, reset_output


raw_data = {'ORG': ['APPLE', 'ORANGE', 'MELON'],
            'APPROVED': [5, 10, 15],
            'CREATED': [1, 3, 5],
            'INPROCESS': [4,2,16]}

df = pd.DataFrame(raw_data)

# create list of orgs to use later
org_l = list(df['ORG'].unique())

# create CDS for source
src = ColumnDataSource(df)

# create cols
table_columns = [TableColumn(field = Ci, title = Ci) for Ci in df.columns] 


# create filtered table
filtered_df = df.loc[df['ORG']=='f']

# create CDS for filtered source
new_src = ColumnDataSource(filtered_df)

# create dropdown
dropdown = Dropdown(label="Dropdown button", button_type="warning", menu = org_l)

callback_code = """"
                var data = src.data;
                var new_data = new_src.data;
                var f = cb_obj.value;
                var list = org_l;
                if var i = org_list[i] {
                new_src.data = src.data
                }

              """

callback=CustomJS(args=dict(dropdown = dropdown,source=src),
             code=callback_code)

# create table
member_table = DataTable(source = new_src, columns = table_columns)

dropdown.js_on_change('value', callback)

show(widgetbox(dropdown, member_table))

''''

2 Answers

The only Bokeh object accessible as named variables in the JS code, are those which you explicitly include in the args dict. That is the purpose of the args dict, to automagically make the JavaScript counterparts of Python Bokeh objects easily accessible. Browsers do not know anything about Python or the Python variables in your script. You reference src, new_src in your JS code, but have not passed any of these in the args dict. Also the plain Python value org_l will need to be included literally in the JS code text, with string text formatting, e.g. with the % operator.

You also have a syntax error in the JS code, which is reported in the Browser's JS console (which you should become familiar with as it is the best tool for debugging these issues). This is not valid JS code:

if var i = org_list[i] {
    new_src.data = src.data
}

Ok with a lot of trial and error and with pointers from bigreddot I got the below to work.

import pandas as pd
from bokeh.models.widgets import Select
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource, DataTable, TableColumn, CustomJS
from bokeh.io import show, output_file, output_notebook, reset_output
from bokeh.layouts import row, column, layout


raw_data = {'ORG': ['APPLE', 'ORANGE', 'MELON'],
        'APPROVED': [5, 10, 15],
        'CREATED': [1, 3, 5],
        'INPROCESS': [4,2,16]}

df = pd.DataFrame(raw_data)

# create CDS for source
src1 = ColumnDataSource(df)

# create cols
table_columns1 = [TableColumn(field = Ci, title = Ci) for Ci in df.columns]

# original data table
data_table1 = DataTable(source=src1, 
                   columns=table_columns, width=400, height=280)

# create empty dataframe to hold variables based on selected ORG value
df2 = pd.DataFrame({'status':['APPROVED', 'CREATED', 'INPROCESS'],
               'count':[float('nan'), float('nan'), float('nan')]})

# create CDS for empty dataframe
src2 = ColumnDataSource(df2)

# create cols
table_columns2 = [TableColumn(field = Ci, title = Ci) for Ci in    df2.columns] 

callback = CustomJS(args=dict(src1=src1, src2=src2), code='''
var count = ['APPROVED', 'CREATED', 'INPROCESS'];
if (cb_obj.value != 'Please choose...') {
    var org = src1.data['ORG'];
    var ind = org.indexOf(cb_obj.value);
    for (var i = 0; i < count.length; i++) {
        src2.data['count'][i] = src1.data[count[i]][ind];
    }
}
else {
    for (var i = 0; i < status.length; i++) {
        src2.data['status'][i] = undefined;
    }

}
src2.change.emit();
''')

options = ['Please choose...'] + list(src1.data['ORG'])
select = Select(title='Test', value=options[0], options=options)
select.js_on_change('value', callback2)

show(column(select, data_table2))
Related