Panel is not updated in jupyter notebook

In a jupyterlab cell, I am trying to update a plot figure from an external data source which does not know about the figure.
But I cannot manage to have the figure updated during the cell execution as the data source is updated. Instead, my figure is updated only once at the end.

Here is the test code sample:

import matplotlib as mpl
import panel as pn
from IPython import display
import holoviews as hv
import time
pn.extension()

val = 0
stream = hv.streams.Stream.define('I', index = 0)()

# This is the callback that is called whenever I push data in my `stream`
# This writes "HELLO {i}" in a matplotlib figure where `i` is the data updated
def _cb(index):
    mpl.pyplot.ioff()
    fig = mpl.figure.Figure(figsize=(1, 1))

    string = f"HELLO {index}"
    print(string)
    fig.suptitle(string)
    
    mpl.pyplot.ion()
    panel = pn.panel(fig)
    return panel

# Create the panel widget
col = pn.Column(pn.bind(_cb, index=stream.param.index))
display.display(col)

# Regularly publish data in my stream
for i in range(10):
    stream.event(index=i)
    time.sleep(1)
print("end of loop")

What I expect

  1. When creating the figure and calling display.display, I shall see the figure created.
  2. When calling stream.event in my for loop, I should see the above figure updated every 1s, at the same time I should see “HELLO {i}” in the logs

What I observe

  1. My figure is correctly created and displayed
  2. The logs appear below the cell, but the figure is not updated (keeps on “HELLO 0”)
  3. When the cell finishes, I see the log “end of loop” and the figure is updated with “HELLO 9”

Note 1

When running only the for loop in a second cell, the figure of the first cell is correctly updated every second.
This does not match my final use case as all the code should be executed in one cell, but I’d like to understand what happens.

Note 2

When running this cell in vscode with Jupyterlab extension, vscode first complains about not having jupyter_bokeh and after installation and kernel restarts, it behaves as expected (regular updates of the figure).
Running in jupyterlab with a kernel containing jupyter_bokeh does not work though.
Do you now if jupyter_bokeh might be related to this issue?

My configuration

holoviews                     1.18.3
ipython                       8.18.1
ipython-genutils              0.2.0
ipywidgets                    8.0.6
jupyter_bokeh                 4.0.5
jupyter_client                8.6.0
jupyter_core                  5.7.1
jupyter-events                0.9.0
jupyter-lsp                   2.2.4
jupyter_server                2.13.0
jupyter_server_terminals      0.5.2
jupyterlab                    4.1.3
jupyterlab_pygments           0.3.0
jupyterlab_server             2.25.3
jupyterlab_widgets            3.0.10
jupytext                      1.16.2
matplotlib                    3.7.5
matplotlib-inline             0.1.6
panel                         1.3.8

Thanks for your help!

This seems to work for me:

import matplotlib.pyplot as plt
import panel as pn
from IPython import display
import holoviews as hv
import time

pn.extension()

val = 0
stream = hv.streams.Stream.define("I", index=0)()


# This is the callback that is called whenever I push data in my `stream`
# This writes "HELLO {i}" in a matplotlib figure where `i` is the data updated
def _cb(index):
    fig = plt.Figure(figsize=(1, 1))
    string = f"HELLO {index}"
    print(string)
    fig.suptitle(string)
    return fig


# Create the panel widget
mpl_pane = pn.pane.Matplotlib(object=pn.bind(_cb, index=stream.param.index))
col = pn.Column(mpl_pane)
display.display(col)

Thanks for the quick response, but your suggestion still does not work for my in Jupyter.

Try separating the calls in separate cells like mine.

You might also need pyviz-comms in your base env.

Yes this is the purpose of my “Note 1”.
Do you know why running the data publishing in a separate cell work?

Not sure, but maybe you can try adding a sleep in between.

Alternatively, you can wrap it in pn.state.onload()
https://panel.holoviz.org/how_to/callbacks/defer_load.html
https://panel.holoviz.org/how_to/callbacks/load.html

In all cases the figure is only updated once at the end.
Would you have suggestions to update a figure in live from a data stream that changes over time?

Is there a specific reason why you don’t want it to run in a single cell?

An alternative is using pn.state.add_periodic_callback.

for my target use case I’ll be running several computations in sequence and I want to update my figure as it is computed. So pn.state.add_periodic_callback is not really what I want. Is it a problem about async programming?

I still don’t get why it works in vscode but not in jupyter. I tried again with a vanilla docker image and get the same result.

# Use the official Python base image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

# Install required packages
RUN pip install matplotlib holoviews panel ipython jupyterlab hvplot

# Expose the port JupyterLab will run on
EXPOSE 8888

# Set the default command to run JupyterLab
CMD ["jupyter", "lab", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root"]

Add periodic callback could run immediately for a set number of times.

For me the below versions worked after

  1. Upgrading Panel, Bokeh, Param and HoloViews to latest versions.
  2. Cleaning the notebook.
  3. Doing a “hard refresh” of the browser with Jupyterlab several times.

HoloViews Streams

Reactive Expressions (pn.rx)

import matplotlib as mpl
import panel as pn
import holoviews as hv
from asyncio import sleep
import datetime as dt
pn.extension()

async def _update_value():
    for i in range(100):
        yield str(dt.datetime.now())
        await sleep(1)

index = pn.rx(_update_value)

def _create_figure(index):
    fig = mpl.figure.Figure(figsize=(4, 1))

    string = f"Matplotlib Title {index}"
    fig.suptitle(string)
    
    return fig

figure = index.rx.pipe(_create_figure)

col = pn.pane.Matplotlib(figure, width=400)
col.servable()