Hi, Im trying to plot some data and use Select widget to filter the data in jupyter notebook. What is the best approach? I google a lot and found CustomJs, ipywidget ( in bokeh wiki recommend this), also found solutions with CDSView and callbacks.
I would prefer using only python an minimal javascript. Im practising using de iris dataset but I can’t make it work. Also, Im not able to add a ‘All’ value to remove the filter.
from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Select
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook
import seaborn as sns
output_notebook()
df = sns.load_dataset('iris')
source = ColumnDataSource(data=df)
true_source = ColumnDataSource(data=df)
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
Columns = [TableColumn(field=c, title=c) for c in df.columns] # bokeh columns
data_table = DataTable(columns=Columns, source=ColumnDataSource(df), width=800, height=280)
show(data_table)
from bokeh.models import CDSView, ColumnDataSource, IndexFilter
import pandas as pd
callback = CustomJS(args=dict(source=source,ts=true_source), code='''
var d1=ts.data;
var d2=source.data;
var f=cb_obj.value;
var x_2=[];
var y_2=[];
var sepal_lenght_2=[];
var sepal_width_2=[];
for(var i=0; i<d1['species'].length; i++){
if(d1['species'][i]==f){
x_2.push(d1['petal_lenght'][i]);
y_2.push(d1['petal_width'][i]);
sepal_lenght_2.push(d1['sepal_lenght'][i]);
sepal_width_2.push(d1['sepal_width'][i]);
}
}
d2['x']=x_2;
d2['y']=y_2;
d2['sepal_lenght']=sepal_lenght_2;
d2['sepal_width']=sepal_width_2;
source.change.emit();
''')
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in df['species']]
p = figure(title="Iris Morphology")
p.xaxis.axis_label = 'Petal Length'
p.yaxis.axis_label = 'Petal Width'
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in df['species']]
select = Select(title="Option:", value='setosa', options=df.species.unique().tolist())
select.js_on_change("value",callback)
p.scatter(x='petal_length', y='petal_width', source=source)
show(column(select, p))
Using a Select widget equates to only selecting one thing at a time, meaning CDSView + GroupFilter → Providing data — Bokeh 2.4.1 Documentation is your simplest option. If your use case is exactly your MRE, then I would stop reading here honestly.
But…
There are a lot of other options that can be used for more complex use cases. They are mainly all the other filter options listed in the above link. CustomJSFilter IMO is the most robust/universal but like most things robust and universal, they require the most effort to get what you want out of them.
The one other “trick” not explicitly laid out in the docs that I’ve used is IndexFilter+CDSView+CustomJS in some cases when I want to update multiple filters on multiple views for multiple sources in a single callback, kinda like this in rough pseudocode:
src = ColumnDataSource(yourdata)
filt = IndexFilter() #make a filter under an initial state
f=figure(...)
v = CDSView(...filters=[filt]) #make a view holding that index filter
r = f.line(x=....,source=src,view=v) #assign that view to the renderer
cb = CustomJS(args=dict(...widgets/callback object etc..., filt=filt,src=src)
,code='''
//retrieve the state of your widgets etc.
var new_inds = []
//then based on the state of the widgets/what the user did etc (if statement, for loops etc)
// populate new_inds with the indices in src you want to display
new_inds = [2,5,6...] //this would filter the renderer to diplay src indices 2,5,6 etc.
filt.indices = new_inds
src.change.emit()
''')
This setup I find handy because CustomJSFilter is limited to only returning one list of indices to update one filter. With the above, I could make x IndexFilters applied to y CDSViews applied to z Renderers applied to zz ColumnDataSources, pass them all as args to the CustomJS, and I can custom-update them all at once (i.e. in the same callback) as opposed to having to manage multiple CustomJSFilter JS snippets. This (I think) is also more computationally efficient as it minimizes the number of times a CDS needs to be iterated through.
FYI I did try to craft a simple example but was not completely happy about the result, so I started a dev discussion about it. You can ignore the discussion, but there is a complete example there that might be a useful reference:
Some kinds of updates from a Select are very easy. It really depends on what you want to do, and how. Hopefully the linked discussion will lead to user-facing improvements for this specific case in near future, but lease don’t over-generalize from the current API shortcomings of views/filters.