Data table had many blank rows if multiselect has 4 or more values

I am trying to make a table with a multiselect widget so that only rows that match the multiselect appear. This works perfectly well when 1-3 values are selected but, for some reason, when a 4th is selected it shows the complete table size (308 rows) with the unselected rows being blank.
bokeh_blank_rows

output_notebook()

output_file('testy.html')

jobs = orders_df['job'].unique().tolist()

source = ColumnDataSource(aggregate_orders_df)
source_col_names = [column for column in aggregate_orders_df]
# create a ColumnDataSource containing all the necessary data so that I can send it to javascript for filtering
all_source = ColumnDataSource(aggregate_orders_df)

columns = [
    TableColumn(field='job', title='Job'),
    TableColumn(field='picture_day', title='Picture Day', formatter=DateFormatter()),
    TableColumn(field='gross_sales', title='Gross Sales', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='aov', title='AOV', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='pd100_count', title='Orders Over $100')
    ]

myTable = DataTable(source=source, columns=columns)

# create the dropdown menu 
job_select = MultiSelect(title = 'Jobs', 
    value = jobs,
    options = aggregate_orders_df['job'].unique().tolist(),
    width = 200,
    margin = (5, 5, 5, 80)
)


# Javascript code for the callback
callback = CustomJS(
    args = dict(
        source = source, 
        all_source = all_source,
        job_select = job_select,
        source_col_names = source_col_names
    ),
    code = 
    """
        // get the value from the dropdown
        var jobs = job_select.value;
        console.log(source.data);
        for (let key in source_col_names) {
            let col_name = source_col_names[key];
            source.data[col_name] = all_source.data[col_name].filter(function(d,i){return jobs.includes(all_source.data.job[i])});
        }
        source.change.emit();
        
    """
)

#attach the callback 
job_select.js_on_change('value', callback)

show(
    column(
        [
            Div(text = 'Job KPIs'), 
            job_select,
            myTable
        ]
    )
)

Hi @Ross_D It’s not really possible to speculate without a complete Minimal Reproducible Example (i.e. one that can be copy-pasted and actually run) to investigate.

with imports and random data still get the same issue

from bokeh.plotting import *
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, Select, RadioButtonGroup, Div, HoverTool, MultiSelect, TableColumn, DataTable, DateFormatter, NumberFormatter
import numpy as np
import pandas as pd

output_notebook()

orders_df = pd.DataFrame(range(0,300), columns=['job'])
orders_df['job'] = orders_df['job'].astype(str)
jobs = orders_df['job'].unique().tolist()
aggregate_orders_df = pd.DataFrame(np.random.randint(0,100,size=(300, 4)), columns=['picture_day','gross_sales','aov','pd100_count'])
aggregate_orders_df = orders_df.join(aggregate_orders_df)

source = ColumnDataSource(aggregate_orders_df)
source_col_names = [column for column in aggregate_orders_df]

all_source = ColumnDataSource(aggregate_orders_df)

columns = [
    TableColumn(field='job', title='Job'),
    TableColumn(field='picture_day', title='Picture Day', formatter=DateFormatter()),
    TableColumn(field='gross_sales', title='Gross Sales', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='aov', title='AOV', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='pd100_count', title='Orders Over $100')
    ]

myTable = DataTable(source=source, columns=columns)

job_select = MultiSelect(title = 'Jobs', 
    value = jobs,
    options = aggregate_orders_df['job'].unique().tolist(),
    width = 200,
    margin = (5, 5, 5, 80)
)


callback = CustomJS(
    args = dict(
        source = source, 
        all_source = all_source,
        job_select = job_select,
        source_col_names = source_col_names),
    code = 
    """
        // get the value from the dropdown
        var jobs = job_select.value;
        console.log(source.data);
        for (let key in source_col_names) {
            let col_name = source_col_names[key];
            source.data[col_name] = all_source.data[col_name].filter(function(d,i){return jobs.includes(all_source.data.job[i])});
        }
        source.change.emit();
        
    """
)

#attach the callback 
job_select.js_on_change('value', callback)

show(
    column(
        [
            Div(text = 'Job KPIs'), 
            job_select,
            myTable
        ]
    )
)

@Ross_D when I run your code I see JavaScript errors about an undefined name (which does seem to be undefined on the browser side)

copy-pasting code that I’m certain runs without anything else.
I forgot to execute on a new kernel so there were some set variables in the last one but this time that’s resolved. Sorry for the goose-chase.

from bokeh.plotting import *
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS, Select, RadioButtonGroup, Div, HoverTool, MultiSelect, TableColumn, DataTable, DateFormatter, NumberFormatter
import numpy as np
import pandas as pd

output_notebook()
output_file('testy.html')
orders_df = pd.DataFrame(range(0,300), columns=['job'])
orders_df['job'] = orders_df['job'].astype(str)
jobs = orders_df['job'].unique().tolist()
aggregate_orders_df = pd.DataFrame(np.random.randint(0,100,size=(300, 4)), columns=['picture_day','gross_sales','aov','pd100_count'])
aggregate_orders_df = orders_df.join(aggregate_orders_df)

source = ColumnDataSource(aggregate_orders_df)
source_col_names = [column for column in aggregate_orders_df]

all_source = ColumnDataSource(aggregate_orders_df)

columns = [
    TableColumn(field='job', title='Job'),
    TableColumn(field='picture_day', title='Picture Day', formatter=DateFormatter()),
    TableColumn(field='gross_sales', title='Gross Sales', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='aov', title='AOV', formatter=NumberFormatter(format='$0,0.00')),
    TableColumn(field='pd100_count', title='Orders Over $100')
    ]

myTable = DataTable(source=source, columns=columns)

job_select = MultiSelect(title = 'Jobs', 
    value = jobs,
    options = aggregate_orders_df['job'].unique().tolist(),
    width = 200,
    margin = (5, 5, 5, 80)
)


jobs_callback = CustomJS(
    args = dict(
        source = source, 
        all_source = all_source,
        job_select = job_select,
        source_col_names = source_col_names),
    code = 
    """
        // get the value from the dropdown
        var jobs = job_select.value;
        console.log(source.data);
        for (let key in source_col_names) {
            let col_name = source_col_names[key];
            source.data[col_name] = all_source.data[col_name].filter(function(d,i){return jobs.includes(all_source.data.job[i])});
        }
        source.change.emit();
        
    """
)

#attach the callback 
job_select.js_on_change('value', jobs_callback)

show(
    column(
        [
            Div(text = 'Job KPIs'), 
            job_select,
            myTable
        ]
    )
)

@Ross_D I believe your console.log reveals the source of the problem:

Your index column still has 300 rows while the others have only a few rows. Columns in a CDS are assumed to always have identical numbers of rows. Anything else is undefined behavior.

Edit: just to note, the index column is present because you creating a CDS out of a DataFrame, and DataFrames have an index column, even if it is not named (if the index column does have a name, that will be used instead of generic “index”). If you don’t want that column, you could delete it. Otherwise, you will need to handle it along with all the others.

Interesting. That’s the solution but I do wonder why the number of selections matters in this case. I was thrown off by the fact that the table appeared as desired for 1-3 selections.
Regardless my issue is solved. Thanks