Problems Filtering DataTable Rows Through CustomJS

Update: My working solution based on Bryan’s suggestion:

soc_check_callback=CustomJS(args=dict(full_filter=full_filter, full_list=full_list, datatable=merged_data_table, full_view=full_view, orig_source=true_source, source=merged_source,filt_P90=filt_P90, filt_G90T=filt_G90T , filt_SD855=filt_SD855, filt_E9820=filt_E9820, filt_A13=filt_A13), code='''

    full_filter.indices = [];

    var labels_length = cb_obj.labels.length;
    
    for(i = 0; i < labels_length; i++) {
        if (cb_obj.active.includes(i)) {
            //alert("Active! ".concat(i, "  ", cb_obj.labels[i]),"  indices: ", full_filter.indices);
            
            switch(cb_obj.labels[i]) {
                case "A13":
                    //alert("A13 ".concat(empty_filter.indices.length));
                    for (j=0; j < filt_A13.indices.length; j++) {
                        full_filter.indices.push(filt_A13.indices[j]);
                    }
                    //alert("Post A13 Add ".concat(full_filter.indices));
                    break;
                case "E9820":
                    //alert("E9820")
                    for (j=0; j < filt_E9820.indices.length; j++) {
                        full_filter.indices.push(filt_E9820.indices[j]);
                    }
                    break;
                case "G90T":
                    for (j=0; j < filt_G90T.indices.length; j++) {
                        full_filter.indices.push(filt_G90T.indices[j]);
                    }
                    break;
                case "P90":
                    for (j=0; j < filt_P90.indices.length; j++) {
                        full_filter.indices.push(filt_P90.indices[j]);
                    }
                    break;
                case "SD855":
                    for (j=0; j < filt_SD855.indices.length; j++) {
                        full_filter.indices.push(filt_SD855.indices[j]);
                    }  
            }
        }
    }
    
    full_filter.indices.sort();
    source.change.emit()
    source.selected.indices = full_filter.indices;
''')

I am learning Bokeh 1.3.4 while creating standalone dashboard charts with interactivity.
My latest challenge is to show a datatable where the user can use checkboxes to limit what columns and rows are shown.

I have solved the column issue by carefully aligning my checkboxgroup indices with my table, and using CustomJS. I include this in case it is of use to a future reader.

columns_check_callback=CustomJS(args=dict(datatable=merged_data_table, orig_columns=backup_merged_columns), code='''
    var new_columns = orig_columns.slice()
    
    lab_len=cb_obj.labels.length;
    for (i=0;i<lab_len;i++) {
        if (cb_obj.active.includes(i) === false) {
            new_columns.splice(i,1)
        }
    }

    datatable.columns = new_columns
    datatable.change.emit()
''')

Solving the rows issue seems very difficult. I have tried using filters. Right off the bat, if I use multiple IndexFilter or GroupFilters my datatable will show no data. If I have only one filter, then the datatable shows the data.
full_view = CDSView(source=merged_source, filters=[gfilt_A, gfilt_B])
I wonder why is the field called “filters” if it can only function with one.

Ok, I then try changing the indices and updating, but it gets weird. Only the first update “takes”, and subsequent changes are not propagated. ie. The first time I uncheck a box, the correct rows are removed from the display, but the second time the displayed data remains unchanged.
`

    new_filter.indices.sort();
    new_filter.change.emit()
    full_view.filters = [new_filter];
    full_view.change.emit();
    datatable.change.emit();

`

Right now I am trying to figure out how, in CustomJS, to walk down the rows of the original full datasource and only copy over the rows I currently want ( based on some cell values ) to the source used for display. The tables I am using are small, so even if I have to repeat the walk for each checked box on each callback, I think I will be ok on response time. If someone could help with a simple example showing proper syntax, I would be very grateful!

@Kyle_Marcroft please edit to apply code formatting to your post, either with triple-backticks (```) around blocks of code, or using the </> symbol on the editor menu.

@Bryan Done, thanks!

People start things but run out of time/money, names are made aspirational to provide for growth room for future capability without breaking changes, there are just plain bugs? Any number of reasons.

Views/filters are a powerful concept and have moved to “promising initial implementation” stage. But the person who added this feature got busy with other responsibilities and is no longer active in the project. This code has not seen much attention since.

In any case, you mention that your tables are small. So one option, if they are really small, is simply not to use views at all. Have a “full” CDS that has all the data, and a “view” CDS that drives the table. Whenever there is an update, copy the relevant subset from the full CDS to the view CDS.

Alternatively, best practice in Bokeh is always to make the smallest change possible, and in particular, to update existing object where possible, rather than deleting/re-creating models. In this case that would mean: configuring one IndexFilter once, and never deleting it, but letting the callbacks compute the proper set of indices and updating the configuration of that same IndexFilter as appropriate.

Thank you for your suggestion! I was able to get it working. Part of my problem during my struggle was that I needed to be emitting a change for the source, and not any of the views, filters, or whatnot.

FYI, I played around with setting the selected indices to the same list as my filter, and found that the selections “lagged” behind by one click. In order for the selected.indices to keep up, I had to source.change.emit() before I set the source.selected.indices. However, I did not need to source.change.emit() after I set them. That seems weird, but there is probably a set of things that happen immediately on an emit and another set of things that happen when the callback exits.

I will also put my working code in the original post for more visibility.

soc_check_callback=CustomJS(args=dict(full_filter=full_filter, full_list=full_list, datatable=merged_data_table, full_view=full_view, orig_source=true_source, source=merged_source,filt_P90=filt_P90, filt_G90T=filt_G90T , filt_SD855=filt_SD855, filt_E9820=filt_E9820, filt_A13=filt_A13), code='''

    full_filter.indices = [];

    var labels_length = cb_obj.labels.length;
    
    for(i = 0; i < labels_length; i++) {
        if (cb_obj.active.includes(i)) {
            //alert("Active! ".concat(i, "  ", cb_obj.labels[i]),"  indices: ", full_filter.indices);
            
            switch(cb_obj.labels[i]) {
                case "A13":
                    //alert("A13 ".concat(empty_filter.indices.length));
                    for (j=0; j < filt_A13.indices.length; j++) {
                        full_filter.indices.push(filt_A13.indices[j]);
                    }
                    //alert("Post A13 Add ".concat(full_filter.indices));
                    break;
                case "E9820":
                    //alert("E9820")
                    for (j=0; j < filt_E9820.indices.length; j++) {
                        full_filter.indices.push(filt_E9820.indices[j]);
                    }
                    break;
                case "G90T":
                    for (j=0; j < filt_G90T.indices.length; j++) {
                        full_filter.indices.push(filt_G90T.indices[j]);
                    }
                    break;
                case "P90":
                    for (j=0; j < filt_P90.indices.length; j++) {
                        full_filter.indices.push(filt_P90.indices[j]);
                    }
                    break;
                case "SD855":
                    for (j=0; j < filt_SD855.indices.length; j++) {
                        full_filter.indices.push(filt_SD855.indices[j]);
                    }  
            }
        }
    }
    
    full_filter.indices.sort();
    source.change.emit()
    source.selected.indices = full_filter.indices;
''')
1 Like