[Bug]: Intermittent `ValueError: The passed figure is not managed by pyplot` with ipympl (`%matplotlib widget`)
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
- Domain
- data-visualization, frontend
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
---------------------------------------------------------------------------
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
FigureManagerstill exists inmatplotlib._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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from matplotlib/ipympl
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
matplotlib/ipympl#623 · 1 comment ·
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
matplotlib/ipympl#612 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
matplotlib/ipympl#611 · 4 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
matplotlib/ipympl#609 · 6 comments ·
-
Difficulty 4/5 3-5 days Newbie friendliness 25/100
matplotlib/ipympl#602 · 1 comment ·
All issues in matplotlib/ipympl
Similar issues
-
Urgent request: Due to vulnerabilities move to API version 12.6.1 (12.6.2 eventually) or 13.1.1 Open
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
jbkunst/highcharter#849 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
bancolombia/sentinel#23 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
AOSSIE-Org/OrgExplorer#245 · 1 assignee ·
-
tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
gordonwatts/test-wsl2-llm#151 ·