Best way to use widgets in Jupyter notebooks in 2022/2023

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

Any help is welcome.

Regards.
Sebastian.

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:

Thanks all for the replies.

I thought that update a plot base on a Select widget was something useful it was a easy one but I see I was wrong.

I’m going to see CDSView + GroupFilter or maybe IndexFilter again.

Thanks

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.