Chained Bokeh callbacks in a notebook

I have some unusual behavior when updating Bokeh models in a notebook, in the case that a callback updates a data source which itself has an on_change callback registered.

The example case:

  • One button and two plots, with one CDS each
  • The button callback updates the first CDS data
  • The first CDS data has a Bokeh on_change callback which updates the second CDS data

The behaviour: if I add a to the button callback, the first plot updates (only); if I skip the in the button callback the second plot updates (only), I think due to the wrapped callbacks on pn.pane.Bokeh. If I disable pn.pane.Bokeh(autodispatch=False), nothing updates (unsurprising).

The example is intended as a notebook cell, but runs as intended as a script by adding a .show() at the end.

import panel as pn

import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting.figure import figure
from bokeh.models.glyphs import Scatter

def new_data(n_pts=10):
    return {
        'x': np.random.randint(0, 10, size=(n_pts,)),
        'y': np.random.randint(0, 10, size=(n_pts,)),

def get_scatter():
    fig = figure(width=400, height=300)
    glyph = Scatter(marker='circle', size=10, line_color=None, fill_color='red')
    cds = ColumnDataSource(new_data())
    renderer = fig.add_glyph(cds, glyph)
    pane = pn.pane.Bokeh(fig)
    return pane, cds

pane0, cds0 = get_scatter()
pane1, cds1 = get_scatter()

new_scatter_btn = pn.widgets.Button(name='New data', width=200)

def update_first(*e):
#     if uncommented, first plot is updated instead of second

new_watcher = new_scatter_btn.on_click(update_first)

def update_second(attr, old, new):

cds0.on_change('data', update_second)

lo = pn.Column(new_scatter_btn, pn.Row(pane0, pane1))

I may be trying to do something which is unsupported here, but it’s a pattern I have come to rely on in the panel serve context, it would be great if anyone has any ideas of how to do this in a notebook also.

Panel 0.14.3 / Bokeh 2.4.3 / Python 3.10.9