3

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))

''''

imstuck
  • 191
  • 2
  • 11

2 Answers2

2

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
}
bigreddot
  • 33,642
  • 5
  • 69
  • 122
  • thanks. the trickiest part for me was to plan/create the empty dataframe that will hold the updated values. If for instance, my dataframe was of long format and there was an extra column for "dept" so that for the APPLE org there were 3 departments and so forth, then is the length of the empty dataframe equal to the len of the original source dataframe? – imstuck Oct 28 '19 at 16:06
  • ok. after a lot of trial and error and much searching across various posts, I discovered that I don't have to create a blank dataframe to hold the updated values. The callback and can be written such that when selecting an org from the list, the other rows associated with the org are hidden. – imstuck Nov 04 '19 at 02:58
1

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))
imstuck
  • 191
  • 2
  • 11
  • 3
    Copying/Pasting the above code, I had to add a bit of code to get it running, namely: `# new data table data_table2 = DataTable(source=src2, columns=table_columns2, width=400, height=280)` (Plus a few small edits to mis-labeled things (i.e. no `callback2` to reference at the end of the code, etc.) Hopefully this is helpful to others who come along. – alofgran Nov 07 '19 at 20:17
  • 1
    Working gist with solution by @alofgran https://gist.github.com/bede/7bfdac7f395d1ea4d1a1dcd1e80926ea – Bede Constantinides Jan 28 '21 at 09:16