-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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))
raiseActual 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 #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
