Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bc31177
ENH: Allow to register standalone figures with pyplot
timhoffm Apr 1, 2025
c718eae
Clarifying comment
timhoffm Jul 27, 2025
a38f9bb
Fix: properly decouple from pyplot and specific backends when destroying
timhoffm Jul 27, 2025
eab4a89
TST: do not use non-baseclass methods to get renderer in tests
tacaswell Jul 30, 2025
2396e90
TST: fix wx window closing test
tacaswell Jul 30, 2025
b7a5cfd
TST: try forcing the dpi when testing saving after closing window
tacaswell Jul 30, 2025
c578ec6
FIX: do not add super().destroy to Canvas destroy method
tacaswell Jul 30, 2025
06b97a8
Add what's new note
timhoffm Jul 30, 2025
db30cbe
MNT: restore the old DPI when resetting the canvas
tacaswell Jul 31, 2025
1ea856d
TST: verify that dpi is restored on close
tacaswell Jul 31, 2025
0e078a7
Update lib/matplotlib/figure.py
timhoffm Aug 1, 2025
1784c73
Add docs to state that you should not keep a reference to the canvas
timhoffm Aug 2, 2025
782750e
Reword what's new message
timhoffm Aug 22, 2025
8b77fff
DOC: move whats new to new location
tacaswell Aug 22, 2025
5906a4f
Update what's new according to suggestions
timhoffm Aug 31, 2025
545892a
Move all super().destroy() calls to the end
timhoffm Aug 31, 2025
6af9062
Add note on incompatibility with blitting of widgets
timhoffm Sep 7, 2025
6b1797b
Fix assertion error
timhoffm Sep 8, 2025
b42ce64
Fix macosx
timhoffm Sep 8, 2025
774d02c
Call super().destroy() in _maxosx.m FigureManager
timhoffm Sep 9, 2025
ccf1994
Better document super call
timhoffm Sep 11, 2025
623fe4d
Fix typos
timhoffm Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ENH: Allow to register standalone figures with pyplot
It may be fundamentally nice not to have to create the figure
though pyplot to be able to use it in pyplot afterwards. You can now do

```
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

fig = Figure()
fig.subplots().plot([1, 3, 2])

plt.figure(fig)  # fig is now tracked in pyplot
plt.show()
```

This also opens up the possibility to more dynamically track
and untrack figures in pyplot, which opens up the road to
optimized figure tracking in pyplot (#29849)
  • Loading branch information
timhoffm committed Jul 27, 2025
commit bc3117798d70f72219dfb08a624f33780a309e20
23 changes: 19 additions & 4 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,10 @@ def figure(
window title is set to this value. If num is a ``SubFigure``, its
parent ``Figure`` is activated.

If *num* is a Figure instance that is already tracked in pyplot, it is
activated. If *num* is a Figure instance that is not tracked in pyplot,
it is added to the tracked figures and activated.

figsize : (float, float) or (float, float, str), default: :rc:`figure.figsize`
The figure dimensions. This can be

Expand Down Expand Up @@ -1019,21 +1023,32 @@ def figure(
in the matplotlibrc file.
"""
allnums = get_fignums()
next_num = max(allnums) + 1 if allnums else 1

if isinstance(num, FigureBase):
# type narrowed to `Figure | SubFigure` by combination of input and isinstance
has_figure_property_parameters = (
any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
or not frameon or kwargs
)

root_fig = num.get_figure(root=True)
if root_fig.canvas.manager is None:
raise ValueError("The passed figure is not managed by pyplot")
elif (any(param is not None for param in [figsize, dpi, facecolor, edgecolor])
or not frameon or kwargs) and root_fig.canvas.manager.num in allnums:
if has_figure_property_parameters:
raise ValueError(
"You cannot pass figure properties when calling figure() with "
"an existing Figure instance")
backend = _get_backend_mod()
manager_ = backend.new_figure_manager_given_figure(next_num, root_fig)
_pylab_helpers.Gcf._set_new_active_manager(manager_)
return manager_.canvas.figure
elif has_figure_property_parameters and root_fig.canvas.manager.num in allnums:
_api.warn_external(
"Ignoring specified arguments in this call because figure "
f"with num: {root_fig.canvas.manager.num} already exists")
_pylab_helpers.Gcf.set_active(root_fig.canvas.manager)
return root_fig

next_num = max(allnums) + 1 if allnums else 1
fig_label = ''
if num is None:
num = next_num
Expand Down
2 changes: 0 additions & 2 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ def test_figure_label():
assert plt.get_figlabels() == ['', 'today']
plt.figure(fig_today)
assert plt.gcf() == fig_today
with pytest.raises(ValueError):
plt.figure(Figure())


def test_figure_label_replaced():
Expand Down
24 changes: 24 additions & 0 deletions lib/matplotlib/tests/test_pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,30 @@ def test_multiple_same_figure_calls():
assert fig is fig3


def test_register_existing_figure_with_pyplot():
from matplotlib.figure import Figure
# start with a standalone figure
fig = Figure()
assert fig.canvas.manager is None
with pytest.raises(AttributeError):
# Heads-up: This will change to returning None in the future
# See docstring for the Figure.number property
fig.number
# register the Figure with pyplot
plt.figure(fig)
assert fig.number == 1
# the figure can now be used in pyplot
plt.suptitle("my title")
assert fig.get_suptitle() == "my title"
# it also has a manager that is properly wired up in the pyplot state
assert plt._pylab_helpers.Gcf.get_fig_manager(fig.number) is fig.canvas.manager
# and we can regularly switch the pyplot state
fig2 = plt.figure()
assert fig2.number == 2
assert plt.figure(1) is fig
assert plt.gcf() is fig


def test_close_all_warning():
fig1 = plt.figure()

Expand Down