[Bug]: Intermittent `ValueError: The passed figure is not managed by pyplot` with ipympl (`%matplotlib widget`)

Open
#622 13 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
jupyter, jupyter-notebook, matplotlib, python

Research direction

Run the supplied JupyterLab reproduction with the widget backend first. Then inspect matplotlib.pyplot.figure(), reached through plt.sca(ax), and the Gcf managers described in the report. Done means the repeated mosaic plotting completes without ValueError and pyplot recognizes the existing figure manager.

Written by the indexing model from the issue text.

Description

Bug summary

When using Matplotlib with ipympl (%matplotlib widget) in JupyterLab, I intermittently get:

ValueError: The passed figure is not managed by pyplot

This happens with standard pyplot usage (plt.figure, plt.subplots(num=fig), plt.sca(ax)), without explicitly constructing canvases.

Code for reproduction
%matplotlib widget
import time
import matplotlib.pyplot as plt

def try_once():
    fig_num = 1
    plt.close(fig_num)
    fig = plt.figure(fig_num)

    fig, axs = plt.subplots(10, 10, num=fig, squeeze=False)

    for ax in axs.flat:
        plt.sca(ax)  # <-- intermittently raises
        plt.plot([1, 2, 3])
        fig.canvas.draw_idle()
        time.sleep(0.001)

for i in range(300):
    try:
        try_once()
    except Exception as e:
        print('FAILED at iteration', i, '->', repr(e))
        raise
Actual outcome

Image

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 20
     18 for i in range(300):
     19     try:
---> 20         try_once()
     21     except Exception as e:
     22         print('FAILED at iteration', i, '->', repr(e))

Cell In[2], line 13, in try_once()
     10 fig, axs = plt.subplots(10, 10, num=fig, squeeze=False)
     12 for ax in axs.flat:
---> 13     plt.sca(ax)  # <-- intermittently raises
     14     plt.plot([1, 2, 3])
     15     fig.canvas.draw_idle()

File ~/ipympl-debug/issue/.venv/lib/python3.12/site-packages/matplotlib/pyplot.py:1376, in sca(ax)
   1372 # Mypy sees ax.figure as potentially None,
   1373 # but if you are calling this, it won't be None
   1374 # Additionally the slight difference between `Figure` and `FigureBase` mypy catches
   1375 fig = ax.get_figure(root=False)
-> 1376 figure(fig)  # type: ignore[arg-type]
   1377 fig.sca(ax)

File ~/ipympl-debug/issue/.venv/lib/python3.12/site-packages/matplotlib/pyplot.py:995, in figure(num, figsize, dpi, facecolor, edgecolor, frameon, FigureClass, clear, **kwargs)
    993 root_fig = num.get_figure(root=True)
    994 if root_fig.canvas.manager is None:
--> 995     raise ValueError("The passed figure is not managed by pyplot")
    996 elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
    997       or not frameon or kwargs) and root_fig.canvas.manager.num in allnums:
    998     _api.warn_external(
    999         "Ignoring specified arguments in this call because figure "
   1000         f"with num: {root_fig.canvas.manager.num} already exists")

ValueError: The passed figure is not managed by pyplot
Expected outcome

Mosaicked plots will be shown without errors.

Additional information
Observed state at failure

At the moment the exception occurs:

  • root_fig.canvas.manager is None
  • but the corresponding FigureManager still exists in matplotlib._pylab_helpers.Gcf (so pyplot still manages the figure)

So pyplot’s “managed figure” check is making a false negative.

Where the exception comes from

plt.sca(ax) calls pyplot.figure(fig) internally.
When passed a Figure, pyplot.figure() currently checks only:

root_fig = num.get_figure(root=True)
if root_fig.canvas.manager is None:
    raise ValueError("The passed figure is not managed by pyplot")

In this bug, canvas.manager is temporarily/incorrectly None even though the manager still exists in Gcf.

Proposed fix

If root_fig.canvas.manager is None, fall back to a Gcf lookup to find the manager whose m.canvas.figure is root_fig, and reattach it.

manager = root_fig.canvas.manager
if manager is None:
    for m in _pylab_helpers.Gcf.get_all_fig_managers():
        if getattr(getattr(m, "canvas", None), "figure", None) is root_fig:
            manager = m
            root_fig.canvas.manager = m
            break
if manager is None:
    raise ValueError(...)

This same logic eliminates the failures in my environment (0/60) when applied as a small monkey patch around pyplot.figure().

This resembles matplotlib/matplotlib#19380 (same structural cause: FigureCanvasBase.__init__ sets self.manager=None after replacing figure.canvas).

Operating system

Ubuntu 24

Matplotlib Version

3.10.8

ipympl Version

0.10.0

Matplotlib Backend

widget

Python version

Python 3.12.3

Jupyter version

jupyter lab --version: 4.5.4

Installation

uv

Dominant language
Jupyter Notebook
Stars
1.7k
Forks
234
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from matplotlib/ipympl

All issues in matplotlib/ipympl

Similar issues

More Data Visualization issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.