☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

How to cause the screen to redraw from within an event handler?

Community Support
gpn
gpn

Hi

Many thanks for Bokeh!

I have successfully drawn a screen within a vscode notebook (using a Panel pane.Bokeh(column) where column is a Bokeh column). I have a python event handler there, which also works. It’s code is below, essentially every time the plot is tapped it generates an event and I do actually get the event!

However the screen does not refresh, so when I change data based on the tap - there is no change to the screen.

As you can see below, I tried several things, but they do not have the desired effect:

  1. I attempted to add a change.emit, however the Glyph and the ColumnDataSource do not have a change property. Is there some other place, accessible from within an event handler, where I can use change.emit() ? Or perhaps there is a new way to emit a redraw directive?
  2. I tried to use Panel’s pn.io.push_notebook(pane) but it simply has no effect.
  3. I tried to use Panel’s pane.param.trigger('object'), again to no effect

How do I get Bokeh to refresh the plot from inside the event handler? Thanks!

        def tap_callback(event):
            feature_index = floor(event.y)
            feature_name = event.model.y_range.factors[feature_index]
            new_value = ... # [details removed to avoid confusion]
            
            # making the change show up in the plot
            for r in event.model.renderers:
                if hasattr(r, 'glyph'):
                    r.data_source.data['most important'][-1-feature_index] = new_value
                    if hasattr(r.data_source, 'change'):
                        print('here I am')
                        r.data_source.change.emit()
                    if hasattr(r.glyph, 'change'):
                        print('here I am')
                        r.glyph.change.emit()
            
            print('will this work?')
            self.pane.param.trigger('object')
            pn.io.push_notebook(self.pane)

Versions:
Bokeh 3.1.1
vscode 1.79.2
Panel 1.1.1
jupyter_bokeh 3.0.7

⌁   Link to post
gpn
gpn

Thank you @Bryan - I have done so, here.

One follow up question though: can you confirm that the code above for r.data_source.change.emit() and/or r.glyph.change.emit() should normally work? That is pure Bokeh code so I assume you are best placed to answer that bit of my question.

Thank you
GPN

⌁   Link to post
Bryan
Bryan

@gpn change.emit() etc are only useful on the JS side (e.g. in a CustomJS callback). In fact, AFAIK they don’t exist at all on the Python side. The very best way to signal an update for most common situations, on either the Python or the JS side, is to do a full new-dict assignment to a source.data:

source.data = entirely_new_data_dict

Bokeh will always automatically notice this and trigger redraws. No change.emit is needed either, that is only required for “in-place” updates where you reach inside and modify the contents of individual columns directly.

Edit: clarifying “entirely new data dict” I mean a new, different object, which might contain all the same data or columns as the old dict object, that’s fine.

⌁   Link to post
gpn
gpn

@Bryan Thanks for that advice. The solution, as you said, is by doing a complete recreation of the ColumnDataSource and then setting the source, like so:

        def tap_callback(event):
            feature_index = floor(event.y)
            feature_name = event.model.y_range.factors[feature_index]
            new_value = ... # ommitted irrelevant details

            # create new dict of all the data
            new_data = {}
            for r in event.model.renderers:
                if hasattr(r, 'glyph'):
                    for c in r.data_source.data:
                        new_data[c] = r.data_source.data[c]
                    new_data['relevant column name'][-1-feature_index] = new_value
                    r.data_source.data = new_data
                    break

By the way, a copy also works, like so:

        def tap_callback(event):
            feature_index = floor(event.y)
            feature_name = event.model.y_range.factors[feature_index]
            new_value = ... # ommitted irrelevant details

            # create new dict of all the data
            new_data = {}
            for r in event.model.renderers:
                if hasattr(r, 'glyph'):
                    new_data = r.data_source.data.copy()
                    new_data['relevant column name'][-1-feature_index] = new_value
                    r.data_source.data = new_data
                    break

I hope someone else benefits from this solution.
Many thanks
GPN

⌁   Link to post
Bryan
Bryan

FYI I think new_data = dict(r.data_source.data) would accomplish the same thing without a loop.

a deep copy also works, like so

dict.copy is a shallow copy, i.e. identical to dict(other_dict)

In [5]: dict.copy?
Docstring: D.copy() -> a shallow copy of D
Type:      method_descriptor

which is good, a shallow copy is all that is required.

⌁   Link to post

End of archived discussion · Replies are disabled