3

I have a minimal sample below to support my question. I have a collection of dataframes in a dictionary that I used to generate DataTable(s) and storage them in an array with the def fDP, then storage in dictionaries the panels and tabs in order to filter them later with the Select widget as in the example. It works fine still I would like to use another Select widget to filter the tabs even further, in that case by product. I tried few different approaches and none worked out, if someone could help me out please.

I added the second Select widget at the end of the code, to give a better idea what I have in mind.

d1 = {'table1' : [['Product1', 0], ['Product2',0]],
     'table2': [['Product1', 1], ['Product2',1]],
     'table3': [['Product1', 0], ['Product2',3]]}
 
dfs = {k:pd.DataFrame(v, columns=['Product', 'value']) for k, v in zip(d1.keys(), [d1[t] for t in d1.keys()])}
 
 
def fDP(df):
    tables = []
    for s in df.keys():
        src = ColumnDataSource(df[s])
        cls = [TableColumn(field=c, title=c) for c in df[s].columns]
        dt = DataTable(source=src,columns=cls, width=600, height=200,editable=True)
 
        tables.append(dt)
    
    return tables
 
plist = list(dfs['table1']['Product'].unique())
 
tables1 = fDP(dfs)
panels1 = {t: Panel(child=p, title='') for t, p in zip(dfs.keys(), tables1)}
tabs1 = Tabs(tabs=[x for x in panels1.values()], align='start', width=10)
 
 
ls = [x for x in dfs.keys()]
 
sel1 = Select(title='Select Check:', align='start', value=ls[0], options=ls, width=195, margin = (15, 5, 0, 0))
 
colT = column([sel1, tabs1], spacing=-26)
 
 
sel1.js_on_change('value', CustomJS(args={'sel':sel1, 'tab':tabs1, 'diPanel':panels1}
        ,code='''
        var sv = sel.value
        tab.tabs = [diPanel[sv]]
        '''))
show(colT)
 
selP = Select(title='Select Product:', align='start', value=plist[0], options=plist, width=195, margin = (15, 5, 0, 0))
ReinholdN
  • 526
  • 5
  • 22

1 Answers1

0

The code below is an example how you could solve it (made for bokeh v2.1.1) The basic idea is to pass the original dataframe to the callback, then filter that data based on current dropdown values and create a new data dictionary that you then assign to the table data source.

For more practical cases (more data) I would recommend using the dataframe-js JS library. In that case you need to use Bokeh preamble or postamble template approach like explained in this post to be able to attach this library to your generated HTML. Having dataframe-js in your code allows you to use many basic Python Pandas functions transferred to JS domain and this is more than enough for what you need.

import pandas as pd

from bokeh.models import ColumnDataSource, DataTable, TableColumn, Tabs, Select, Panel, Row, Column, CustomJS
from bokeh.plotting import show

d1 = {'table1' : [['Product1', 11], ['Product2',12]],
     'table2': [['Product1', 21], ['Product2',22]],
     'table3': [['Product1', 31], ['Product2',32]]}
 
dfs = {k:pd.DataFrame(v, columns=['Product', 'value']) for k, v in zip(d1.keys(), [d1[t] for t in d1.keys()])}

def fDP(df):
    tables = []
    for s in df.keys():
        src = ColumnDataSource(df[s])
        cls = [TableColumn(field=c, title=c) for c in df[s].columns]
        dt = DataTable(source=src,columns=cls, width=600, height=200,editable=True)
 
        tables.append(dt)
    
    return tables
 
plist = list(dfs['table1']['Product'].unique())
plist.insert(0, ' ')
tables1 = fDP(dfs)
panels1 = {t: Panel(child=p, title='') for t, p in zip(dfs.keys(), tables1)}
tabs1 = Tabs(tabs=[x for x in panels1.values()], align='start', width=10)

ls = [x for x in dfs.keys()]
 
sel1 = Select(title='Select Check:', align='start', value=ls[0], options=ls, width=195, margin = (15, 5, 0, 0))
selP = Select(title='Select Product:', align='start', value=plist[0], options=plist, width=195, margin = (15, 5, 0, 0))

colT = Column(Row(sel1, selP), tabs1, spacing=-26)
 
sel1.js_on_change('value', CustomJS(args={'sel':sel1, 'selP':selP, 'tab':tabs1, 'diPanel':panels1}
        ,code='''
        var sv = sel.value
        tab.tabs = [diPanel[sv]]
        selP.value = ' '
        '''))

ds = ColumnDataSource(d1)
selP.js_on_change('value', CustomJS(args={'selT':sel1, 'selP':selP, 'tab':tabs1, 'diPanel':panels1, 'ds': ds}
        ,code='''
        var tb_name = selT.value
        var p_name = selP.value         
        var c_tb_ds = diPanel[tb_name].child.source // table datasource to update
        var c_tb_ds_data = ds.data[tb_name] // original data to work with

        var new_data = {}
        var p_index = null
        var keys = Object.keys(c_tb_ds.data)

        if (index > -1) { 
            keys.splice(index, 1); // 1 means remove one item only => index
        }
        
        for (var k in keys){
            new_data[keys[k]] = []
        }

        for (var i in c_tb_ds_data) {          
            if(p_index == null) {
                for (var j in keys) {
                    if(c_tb_ds_data[i][j] == p_name) {
                        if(c_tb_ds_data[i][j].includes('Product')) {
                            p_index = i
                        }
                    }
                }
            }
        }

        if(p_index != null) {
            for (var j in keys) {
                new_data[keys[j]].push(c_tb_ds_data[p_index][j])              
            }            
        }

        c_tb_ds.data = new_data // update table data source'''))

show(colT)
 

Result: enter image description here

Tony
  • 7,767
  • 2
  • 22
  • 51
  • thanks a lot. I will run the code on the data I got and as soon as possible I will mark it as answered. Nicely done. – ReinholdN Aug 12 '22 at 07:35
  • When running this I get 'cannot import name 'Panel' from 'bokeh.models''. Is there an issue with this code, or is it something my end? – Beavis Dec 23 '22 at 14:14
  • Then you are probably not using the Bokeh version 2.1.1 for which this code was made. Check the current Bokeh documentation if Panel is still supported. – Tony Dec 23 '22 at 15:12
  • It looks like the [Panel](https://docs.bokeh.org/en/2.4.3/docs/reference/models/layouts.html?highlight=panel#bokeh.models.Panel) has been renamed to [TabPanel](https://docs.bokeh.org/en/3.0.3/docs/reference/models/layouts.html#bokeh.models.TabPanel) – Tony Jan 10 '23 at 12:28