Filter tabs with Select widget with jscallback

Basically I have several dashboards for each year of a specific subject. It is not feasible to use select to filter an unique year as they have very different aspects/metrics, I thought that it would be possible to use a Select widget to filter these tabs with a JS callback, still I did not find anything as such in my online research and my knowledge of JS is not good enough. Anyone out to give me some guidance would be appreciate.

I don’t really have a clear picture of either your situation, or what you are trying to accomplish, from this description. Please provide much more detail (and ideally some images and code) that relate what interaction you are trying have happen.

def dfTeamsChamp(year):
#Function from 2003 onwards

dfR = pd.read_csv('races.csv', index_col=0, delimiter=',')
dfR = dfR[dfR['year'] == int(year)]
dfR.drop('url', axis=1, inplace=True)
dfR.sort_values('date', inplace=True)

#df contructors teams
dfC = pd.read_csv('constructors.csv', delimiter=',', index_col=0)

#df constructor standings
dfC_S = pd.read_csv('constructor_results.csv', delimiter=',', index_col=0)
dfC_S.set_index('raceId', inplace=True)
#dfC_S = pd.read_csv( 'constructor_standings.csv', index_col=1, delimiter=',')
#filter year qualify
CSF = np.array(dfR.index)
dfC_S1 = dfC_S.index.isin(CSF)
dfC_S1 = dfC_S[dfC_S1]

c_F = np.array(dfC_S1.constructorId.unique())
dfC1 = dfC.index.isin(c_F)
dfC1 = dfC[dfC1]

#dictionary constructorId x constructor name
diC_N = pd.Series(dfC1.name.values, dfC1.index.values).to_dict()

#add constructor name to df constructor standings
dfC_S1['Team'] = dfC_S1.constructorId.map(diC_N)

#df qualis
dfQ = pd.read_csv('qualifying.csv', index_col=1, delimiter=',')
#filter year qualify
qF = np.array(dfR.index)
dfQ1 = dfQ.index.isin(qF)
dfQ1 = dfQ[dfQ1]
dfQ1['Team'] = dfQ1.constructorId.map(diC_N)

#df drivers
dfD = pd.read_csv('drivers.csv', index_col=0, delimiter=',')
dfD.drop('url', axis=1, inplace=True)
#filter drivers year
dfDY = dfQ1.set_index('driverId')
dfDYF = np.array(dfDY.index.unique())
dfDY = dfD.index.isin(dfDYF)
dfDY = dfD[dfDY]
#dict driverId x driverName
diDidxDN = pd.Series(dfDY.forename.values, dfDY.index.values).to_dict()
   
#add driver name to df quali
dfQ1['DN'] = dfQ1.driverId.map(diDidxDN)

#dict DriverName x Team
diDxT = pd.Series(dfQ1.Team.values, dfQ1.DN.values).to_dict()


#dict DriverName x TeamID
diCxD = pd.Series(dfQ1.constructorId.values, dfQ1.DN.values).to_dict()

#df driver standings championship
dfD_C = pd.read_csv('driver_standings.csv', sep=',', index_col='raceId')
dfD_CF = dfD_C.index.isin(qF)
dfD_C = dfD_C[dfD_CF]
dfD_C['DN'] = dfD_C.driverId.map(diDidxDN)
dfD_C['Team'] = dfD_C.DN.map(diDxT)




#costructor cham
dfC_S2 = dfC_S1.sort_values('points', na_position ='first')
dfC_S2['cumsum'] = pd.DataFrame(dfC_S2.groupby('Team')['points'].cumsum())
dfC_S3 = pd.DataFrame(dfC_S2.groupby('Team')['points'].sum())
dfC_S3.sort_values('points', ascending=False, inplace=True)
dfC_S3['c'] = dfC_S3.index.map({'Red Bull': '#121F45', 'Ferrari':'#A6051A', 'Mercedes':'#00A19C',
                                   'Alpine F1 Team': '#005BA9', 'Haas F1 Team': '#F9F2F2', 'Williams': '#005AFF',
                                   'AlphaTauri': '#00293F','McLaren': '#FF8000', 'Alfa Romeo': '#981E32','Aston Martin':'#00352F',
                               'Racing Point':'#F596C8', 'Renault':'#FFF500', 'Toro Rosso':'#469BFF', 'Catherham':'#048646',
                               'Sauber':'#9B0000','Force India':'#F596C8','Lotus F1':'#FFB800', 'Marussia':'#6E0000',
                               'Jaguar':'#08623e', 'Toyota':'#8e0018', 'BAR':'#d5d5d3', 'Jordan':'#ead136',
                               'Prost':'#03033f', 'Super Aguri': '#b61211', 'Benetton':'#72dffe', 'Arrows':'#fc8d2c',
                               'Honda':'#cccfc8', 'BMW Sauber':'#ffffff', 'Brawn':'#cccfc8'})

pos = ['1st', '2nd', '3rd', '4th', '5th','6th','7th','8th','9th','10th','11th','12th','13th','14th']   
dfC_S3['pos'] = pos[:len(dfC_S3.index)]    
wins = dfD_C[dfD_C.wins >= 1].groupby(['raceId', 'Team'])['wins'].sum().reset_index()
dfC_S3['wins'] = wins[wins.raceId == wins.raceId.max()].set_index('Team').drop('raceId', axis=1)
dfC_S3['wins'].fillna(0, inplace=True)
dfC_S3['poles'] = dfQ1[dfQ1.position == 1].groupby('Team')['position'].count()
dfC_S3.poles.fillna(0, inplace=True)

srcC = ColumnDataSource(dfC_S3)

xC = np.array(dfC_S3.index.unique())

pCons = figure(x_range=xC, title='Constructor Championship ' + str(year), plot_height=450, plot_width=700, y_axis_label = 'points',
                 tools='pan, wheel_zoom, box_zoom, reset', toolbar_location='right')
pCons.vbar(top='points', width=.6, x='Team',  source=srcC, line_color="black", line_width = 0.1, fill_color='c')

pCons.grid.grid_line_alpha = .25
pCons.grid.grid_line_dash = 'dotted'
pCons.grid.grid_line_dash_offset = 5
pCons.grid.grid_line_width = 2
pCons.grid.grid_line_color = 'white'
pCons.axis.major_label_text_font_style = 'bold'
pCons.xaxis.major_label_text_font_size = '9px'
pCons.yaxis.major_label_text_font_size = '11px'
pCons.title.text_font_size = '19px'
pCons.title.text_color = 'white'
#pP.title.position = 'below'
pCons.outline_line_color=None
pCons.yaxis.axis_label_text_color= 'white'


pCons.axis.major_label_text_color= 'white'

pCons.legend.border_line_color = None
pCons.legend.label_text_color='white'
pCons.legend.background_fill_color= None
pCons.yaxis.ticker.desired_num_ticks = 12
pCons.toolbar.autohide = True
pCons.y_range.start = 0

pCons.background_fill_color = '#010727'
pCons.background_fill_alpha =.8
pCons.border_fill_color =  '#010727'
pCons.border_fill_alpha = .8
pCons.axis.axis_line_color ='white'
pCons.axis.minor_tick_line_color ='white'
pCons.axis.major_tick_line_color ='white'
hv = HoverTool()
hv.tooltips=[('Team', '@Team'), ('Points', '@points{0.0}'), ('Position', '@pos'),
             ('Wins','@wins'), ('Poles', '@poles{0}')]
pCons.add_tools(hv)

t = Panel(child=pCons, title=str(year))

return t
def tabs():

t03 = dfTeamsChamp(2003)
t04 = dfTeamsChamp(2004)
t05 = dfTeamsChamp(2005)
t06 = dfTeamsChamp(2006)
t07 = dfTeamsChamp(2007)
t08 = dfTeamsChamp(2008)
t09 = dfTeamsChamp(2009)
t10 = dfTeamsChamp(2010)
t11 = dfTeamsChamp(2011)
t12 = dfTeamsChamp(2012)
t13 = dfTeamsChamp(2013)
t14 = dfTeamsChamp(2014)
t15 = dfTeamsChamp(2015)
t16 = dfTeamsChamp(2016)
t17 = dfTeamsChamp(2017)
t18 = dfTeamsChamp(2018)
t19 = dfTeamsChamp(2019)
t20 = dfTeamsChamp(2020)
t21 = dfTeamsChamp(2021)
tall = [t03,t04,t05,t06,t07,t08,t09,t10,t11,t12,t13,t14,t15,t16,t17,t18,t19,t20,t21]

tabs = Tabs(tabs= tall[::-1])
return show(tabs)

I know that if I build each year as CDS I could use Select widget to select them and filter it. But I was wondering if there is a way to use Select to filter through the tabs instead. Mostly because set the x_range would be a kinda pain with CDS for year instead the full plot as it is. I will implement it further with other plots, but it would be nice to be able to filter the tabs with Select, that would facilitate significantly the work…

I think you’ve framed your question well in the last paragraph, but the code example you’ve provided is very very far from a “minimal reproducible example” for us to work with. Please consider what “minimal” actually means → it does not mean dumping your entire pandas preprocessing set up into a codeblock for us to parse out. It does mean distilling down to the absolute bare bokeh bones what you’re asking about, using dummy data that can be initialized directly in the MRE.

Based on your last paragraph, I think you’re asking for something like this:

from bokeh.models import Panel, Tabs, Toggle, TextInput, CheckboxGroup, CustomJS
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.layouts import column, row  

#make two figures with stuff in them
p1 = figure(plot_width=300, plot_height=300)
p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)

p2 = figure(plot_width=300, plot_height=300)
p2.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=3, color="navy", alpha=0.5)

toggle1 = Toggle(label="Foo", button_type="success")
toggle2 = Toggle(label="Foo", button_type="warning")
text_input = TextInput(value="default", title="Label:")

tab1 = Panel(child=row(p1,toggle2), title="circle")
tab2 = Panel(child=column(p2,toggle1, text_input), title="line")

#collect ALL panels that could be displayed into a big list
all_tabs = [tab1,tab2]

#Initialize tabs being "currently" displayed
display_tabs = Tabs(tabs=all_tabs)

#checkbox group, initialized with all checked off
cbg = CheckboxGroup(labels=[x.title for x in all_tabs],active=[0,1])
#make a layout of the checkbox group and the display_tabs
lo = row([cbg,display_tabs])

#JS callback --> everytime the user checks a box, we want:
        #to go through every active checkbox index and append the corresponding tab (from all_tabs) to a new list
        #update the display_tabs tabs property with that new list
cb = CustomJS(args=dict(cbg=cbg,all_tabs=all_tabs,display_tabs=display_tabs)
              ,code='''
              var upd_tabs = []
              for (var i=0;i<cbg.active.length;i++){                     
                      upd_tabs.push(all_tabs[cbg.active[i]])
                      }
              display_tabs.tabs=upd_tabs
              display_tabs.change.emit()
              ''')
              
cbg.js_on_change('active',cb) #tells this callback to execute when the "active" property on the checkbox group changes
             
show(lo)

The idea is:

  • Assemble all tabs into a list
  • Initialize the “display_tabs”, i.e. the tabs that are currently being displayed (my example only has two tabs so might as well start with showing all of them)
  • Initialize a checkbox group containing labels for all possible tabs, and index them in the same order as all_tabs
    -Write a CustomJS callback to trigger whenever a checkbox value changes (i.e. the ‘active’ property. That CustomJS looks at the currently active checkboxes, and appends the corresponding tab (in all_tabs) to a list, which is then used to update the tabs property of display_tabs.

tabber

I know this isn’t using a Select widget but a Select widget can only select one thing at a time and I felt the checkbox group is actually more what you’re after based on your initial problem statement. Regardless of the widget you choose for the filtering you can probably apply the same kind of approach though.

1 Like

Thanks for the reply and help. Yeah my bad, I could have done something much simpler to share. I would rather the Select widget, but it is a very good start your example, now I have an idea where to start and I got the logic on it.
I am a bit busy at moment with other things, as soon as I get it done I will share it here. Thanks again.

2 Likes