solara icon indicating copy to clipboard operation
solara copied to clipboard

Failing to display Matplotlib plot with ipympl backend after a task completes

Open lballabio opened this issue 6 months ago • 0 comments

In a multi-page Solara application, I'm retrieving some data from a long-running task and plotting them when the task completes. However, when using the ipympl backend, an empty figure is displayed.

Expected Behavior

The figure should be displayed and interactive features from ipympl should be enabled.

Current Behavior

The figure is empty. However, after navigating to another page in the same app and going back to the initial page, the figure is displayed correctly.

Steps to Reproduce the Problem

  1. Create an activate a Python virtual environment with the following requirements:
solara
matplotlib
ipympl
mplcursors
  1. Add the two following files in a src directory:

01-working.py (no task used)

import matplotlib
from matplotlib import pyplot as plt
import mplcursors
import solara
import solara.lab
import time

matplotlib.use("widget")

ids = ["Choose one", "a", "b"]
choice = solara.reactive(ids[0])


@solara.component
def controls():
    solara.Select("choice", value=choice, values=ids)


def retrieve_data(*args):
    if choice.value == "Choose one":
        return

    time.sleep(1.0)

    return (
        [1, 2, 3, 4],
        [2, 4, 3, 1],
    )


@solara.component
def view():
    data = retrieve_data()
    if data:
        xs, ys = retrieve_data()

        fig = plt.figure(figsize=(10, 6))
        ax1 = fig.add_subplot(1, 1, 1)

        ax1.set_title("My data")
        points = ax1.plot(xs, ys, "o")
        cursor = mplcursors.cursor(points, hover=mplcursors.HoverMode.Transient)
        cursor.connect("add", lambda sel: sel.annotation.set_text(str(ys[sel.index])))

        plt.show()
        plt.close(fig)


@solara.component
def Page():
    with solara.AppBarTitle():
        solara.Text("Sample page")
    with solara.Sidebar():
        controls()
    view()


Page()

02-not-working.py (using a Solara task)

import matplotlib
from matplotlib import pyplot as plt
import mplcursors
import solara
import solara.lab
import time

matplotlib.use("widget")

ids = ["Choose one", "a", "b"]
choice = solara.reactive(ids[0])


@solara.component
def controls():
    solara.Select("choice", value=choice, values=ids, on_value=retrieve_data)


@solara.lab.task
def retrieve_data(*args):
    if choice.value == "Choose one":
        return

    for i in range(10):
        if not retrieve_data.is_current():
            return

        time.sleep(0.1)

        retrieve_data.progress = (i + 1) * 100.0 / 10

    return (
        [1, 2, 3, 4],
        [2, 4, 3, 1],
    )


@solara.component
def view():
    solara.ProgressLinear(retrieve_data.progress if retrieve_data.pending else 100.0)

    if retrieve_data.finished and retrieve_data.value:
        xs, ys = retrieve_data.value

        fig = plt.figure(figsize=(10, 6))
        ax1 = fig.add_subplot(1, 1, 1)

        ax1.set_title("My data")
        points = ax1.plot(xs, ys, "o")
        cursor = mplcursors.cursor(points, hover=mplcursors.HoverMode.Transient)
        cursor.connect("add", lambda sel: sel.annotation.set_text(str(ys[sel.index])))

        plt.show()
        plt.close(fig)


@solara.component
def Page():
    with solara.AppBarTitle():
        solara.Text("Sample page")
    with solara.Sidebar():
        controls()
    view()


Page()
  1. Start the app with solara run src/
  2. The app will open in the browser. In the home page, choose either "a" or "b" in the sidebar control. After a second, the page will display a Matplotlib figure and you should be able to get tooltips when if you hover on a point.
  3. Switch to the "not-working" page and choose either "a" or "b". The progress bar will fill, and at the end an empty figure will be displayed. The terminal where you launched the app will log a few errors like:
ERROR:    Uncaught exception: Traceback (most recent call last):
  File "/Users/lballabio/Downloads/solara/.venv/lib/python3.13/site-packages/ipywidgets/widgets/widget.py", line 191, in __call__
    local_value = callback(*args, **kwargs)
  File "/Users/lballabio/Downloads/solara/.venv/lib/python3.13/site-packages/ipympl/backend_nbagg.py", line 271, in _handle_message
    self.manager.resize(w, h)
    ^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'resize'

ERROR:    Uncaught exception: Traceback (most recent call last):
  File "/Users/lballabio/Downloads/solara/.venv/lib/python3.13/site-packages/ipywidgets/widgets/widget.py", line 191, in __call__
    local_value = callback(*args, **kwargs)
  File "/Users/lballabio/Downloads/solara/.venv/lib/python3.13/site-packages/ipympl/backend_nbagg.py", line 278, in _handle_message
    self.manager.handle_json(content)
    ^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'handle_json'
  1. Move to the "home" page and back to the "not-working" page. The figure will now display and show tooltips.

Specifications

  • Solara Version: 1.47.0
  • Platform: Ubuntu 22.04 and MacOS Sequoia
  • Affected Python Versions: 3.10.12 on Ubuntu, 3.13.3 on MacOS

lballabio avatar May 28 '25 10:05 lballabio