DataTable doesn't update when source changes

I’m working with a DataTable and I want to have some triggers that change the values in the table.
I’m able to change the source value without problem, however, I can’t get the displayed table to update when I do.

Here is a very simplified version of my code. When I click on the button, I expect the first value of col1 to change to ‘z’. When I check by sending the value to the log, it seems to have worked, however, the Table still show an ‘a’ there.

Can you help me find what I’m missing ?

from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, Button
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.layouts import column

import logging
import bokeh
import pandas as pd

df = pd.DataFrame({'col1': ['a', 'a', 'b', 'a', 'c', 'b', 'a'],
                   'col2': [1, 3, 3, 4, 2, 1, 3]
                 })

source = ColumnDataSource(df)

columns = [TableColumn(field = col, title=col) for col in df.columns]

data_table = DataTable(columns=columns, 
                       source=source
                       )

but = Button(label = 'Change')

def change_value():
    global source 
    source.data['col1'][0] = 'z'
    logging.warning(source.data['col1'][0])
    
but.on_click(change_value)

lay = column(data_table, but)

curdoc().add_root(lay)

Bokeh can only implement so much auto-magic. You need to assign a whole new column, or a whole new .data dict. “In-place” item assignments are not detected.

Alternatively you can use the .patch method on the CDS.

2 Likes

Thank you for the quick reply. Do you mean I should assign a new dictionary to source ?

I tried replacing

    source.data['col1'][0] = 'z'

by

    df2 = {'col1': ['z', 'a', 'b', 'a', 'c', 'b', 'a'],
           'col2': [1, 3, 3, 4, 2, 1, 3]
          }
    source = ColumnDataSource(df2)

But nothing changes. Did I misunderstand something ?

EDIT : I just tried with the patch method, it works fine. I’m still curious as to why the above doesn’t work, as it would suit my actual use case better.

The issue now is you’re instantiating a new columndatasource, one that isn’t assigned to drive your renderer.

See my edit below:

df2 = {'col1': ['z', 'a', 'b', 'a', 'c', 'b', 'a'],
           'col2': [1, 3, 3, 4, 2, 1, 3]
          }

#source = ColumnDataSource(df2)
#instead:
source.data = df2
#my own curiousity also give this a go---> the "single column replace only" option Bryan mentioned
# source.data['col1'] = df2['col1']
#and also the patch thing Bryan mentioned:
#never used this before, but the idea I think is you pass a dict with columns for keys, and a 2 item tuple as each key value for that dict like this: (index,value to fill it with) 
# source.patch({'col1':(0,'z')})

Best explainer for this that helped me understand why some things “magically auto-update” while others don’t: python - Bokeh: Whats the differences between certain methods of editing data in a ColumnDataSource - Stack Overflow

2 Likes

Thanks a lot, that works. Seems like I’ve got some reading to do :wink:

FWIW The TLDR reason that “in place” item assignments are not auto-magically handled is that wrapping arrays to intercept every single item assignment would be detrimental to performance of the common usage, and would implicitly promote usage patterns that could never be made efficient (e.g looping over and assigning every item individually would always be terrible, even if it “worked”).

1 Like