How to create a callback with a datatable?

I’m trying to get a data table to show the data that’s presented in the figure, not the original source. I don’t know how to do this with any kind of Javascript code, which it looks like I’ll need.

I’ve found this Stackoverflow thread, and I tried doing this but it didn’t work and it said no bueno because I’m trying to mix Python and Java callbacks, or something along those lines.

I had originally thought doing it all in Python would be better but this talk of servers (which I know nothing about) scares me, and I also want to create something that’s highly transferrable, so perhaps Java is the way to go. I’m trying to create something that can be emailed, I don’t know how to write Javascript but I can sorta adapt it.

Here’s my simplified code which demonstrates the issue. When looking at the datatable and filtering down I want there to be fewer rows showing:

from bokeh.plotting import figure, show
from bokeh.models import Slider, Panel, CustomJSFilter, CDSView, ColumnDataSource, CustomJS, Tabs
from bokeh.models.widgets import TableColumn, DataTable
from bokeh.layouts import column, layout
data = dict(Apples=[97, 34, 23, 6, 26, 97, 21, 92, 73, 10, 92, 14, 77, 4, 25, 48, 26, 39, 93],
            Not_Cancelled=[87, 63, 56, 38, 57, 63, 73, 56, 30, 23, 66, 47, 76, 15, 80, 78, 69, 87, 28],
            OnTime_Arrivals=[21, 65, 86, 39, 32, 62, 46, 51, 17, 79, 64, 43, 54, 50, 47, 63, 54, 84, 79])
source = ColumnDataSource(data=data)
MinApples = Slider(start=0, value=50, end=100, step=1)
MinApples.js_on_change('value', CustomJS(args=dict(source=source), code="""source.change.emit()"""))
custom_filter = CustomJSFilter(args=dict(source=source, MinApples=MinApples), code='''
    var indices = [];
    for (var i = 0; i < source.get_length(); i++){
        if (source.data['Apples'][i] > MinApples.value){
            indices.push(true);
        } else {
            indices.push(false);
        }
    }
    return indices;
''')
view = CDSView(source=source, filters=[custom_filter])
p = figure()
p.circle('OnTime_Arrivals', 'Not_Cancelled', source=source, view=view, size=20)
-- what do I put in here and how do I create a callback? some kind of .js_on_change event like with the slider?
table = DataTable(source= source, editable =True, reorderable =True, columns= [
    TableColumn(field="Apples", title="Apples"),
    TableColumn(field="Not_Cancelled", title="Not_Cancelled"),], sizing_mode = "stretch_both")
tab1 = Panel(child=p, title="Plot")
tab2 = Panel(child=table, title="Data")
tabs = Tabs(tabs = [tab1, tab2])
controls = [MinApples]
inputs = column(*controls, width = 300)
inputs.sizing_mode = "stretch_height"
l = layout([[inputs, tabs]], sizing_mode="stretch_both")
show(l)

Sorry that this is such a basic question but I asked yesterday about where to ask and carolyn’s response was supportive, recommending to ask it here.

Could someone please point me in the right direction?

Hi tvkyq,

The good news is, you’ve got most of what you need already here! The only thing I added to make the DataTable rows change with the slider is to add view=view as an argument when the DataTable is created:

table = DataTable(source=source, view=view, editable=True, reorderable=True, columns=[
TableColumn(field="Apples", title="Apples"),
TableColumn(field="Not_Cancelled", title="Not_Cancelled")
], sizing_mode = "stretch_both")

Just like in your circle glyph, this tells the DataTable to use your ColumnDataSource called source for the full data source, but only show the rows that pass the filter on your CDSView called view. With that added, when I switch over to the Data tab, the slider controls what rows I see.

Your comment in the code asked about adding an additional callback. Probably the easiest way to think about it is that you would have, in most cases, one callback per widget. So the callback belongs to the widget (your slider), and acts upon the data source, which the other models (your circle, your datatable) are listening to for changes. … DataTables muddy this a little bit because they are themselves clickable widgets, but that’s not what we’re doing here. :slight_smile:

Ok wow, that was super simple. All I needed was view = view, something I had already figured out for the figure! How embarrassing!

And thank you for clarifying what a callback is, and thank you for the help!

2 Likes