Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0a4385c

Browse filesBrowse files
authored
Merge pull request #28177 from rcomer/get_figure
Rationalise artist get_figure methods; make figure attribute a property
2 parents 48c8f07 + 9a795c2 commit 0a4385c
Copy full SHA for 0a4385c

File tree

Expand file treeCollapse file tree

13 files changed

+171
-26
lines changed
Filter options
Expand file treeCollapse file tree

13 files changed

+171
-26
lines changed

‎ci/mypy-stubtest-allowlist.txt

Copy file name to clipboardExpand all lines: ci/mypy-stubtest-allowlist.txt
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ matplotlib.tri.*TriInterpolator.gradient
4646
matplotlib.backend_bases.FigureCanvasBase._T
4747
matplotlib.backend_managers.ToolManager._T
4848
matplotlib.spines.Spine._T
49+
50+
# Parameter inconsistency due to 3.10 deprecation
51+
matplotlib.figure.FigureBase.get_figure
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(Sub)Figure.get_figure
2+
~~~~~~~~~~~~~~~~~~~~~~
3+
4+
...in future will by default return the direct parent figure, which may be a SubFigure.
5+
This will make the default behavior consistent with the
6+
`~matplotlib.artist.Artist.get_figure` method of other artists. To control the
7+
behavior, use the newly introduced *root* parameter.
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(Sub)Figure.set_figure
2+
~~~~~~~~~~~~~~~~~~~~~~
3+
4+
...is deprecated and in future will always raise an exception. The parent and
5+
root figures of a (Sub)Figure are set at instantiation and cannot be changed.

‎lib/matplotlib/artist.py

Copy file name to clipboardExpand all lines: lib/matplotlib/artist.py
+26-11Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __init__(self):
181181
self._stale = True
182182
self.stale_callback = None
183183
self._axes = None
184-
self.figure = None
184+
self._parent_figure = None
185185

186186
self._transform = None
187187
self._transformSet = False
@@ -251,7 +251,7 @@ def remove(self):
251251
if self.figure:
252252
if not _ax_flag:
253253
self.figure.stale = True
254-
self.figure = None
254+
self._parent_figure = None
255255

256256
else:
257257
raise NotImplementedError('cannot remove artist')
@@ -720,34 +720,49 @@ def set_path_effects(self, path_effects):
720720
def get_path_effects(self):
721721
return self._path_effects
722722

723-
def get_figure(self):
724-
"""Return the `.Figure` instance the artist belongs to."""
725-
return self.figure
723+
def get_figure(self, root=False):
724+
"""
725+
Return the `.Figure` or `.SubFigure` instance the artist belongs to.
726+
727+
Parameters
728+
----------
729+
root : bool, default=False
730+
If False, return the (Sub)Figure this artist is on. If True,
731+
return the root Figure for a nested tree of SubFigures.
732+
"""
733+
if root and self._parent_figure is not None:
734+
return self._parent_figure.get_figure(root=True)
735+
736+
return self._parent_figure
726737

727738
def set_figure(self, fig):
728739
"""
729-
Set the `.Figure` instance the artist belongs to.
740+
Set the `.Figure` or `.SubFigure` instance the artist belongs to.
730741
731742
Parameters
732743
----------
733-
fig : `~matplotlib.figure.Figure`
744+
fig : `~matplotlib.figure.Figure` or `~matplotlib.figure.SubFigure`
734745
"""
735746
# if this is a no-op just return
736-
if self.figure is fig:
747+
if self._parent_figure is fig:
737748
return
738749
# if we currently have a figure (the case of both `self.figure`
739750
# and *fig* being none is taken care of above) we then user is
740751
# trying to change the figure an artist is associated with which
741752
# is not allowed for the same reason as adding the same instance
742753
# to more than one Axes
743-
if self.figure is not None:
754+
if self._parent_figure is not None:
744755
raise RuntimeError("Can not put single artist in "
745756
"more than one figure")
746-
self.figure = fig
747-
if self.figure and self.figure is not self:
757+
self._parent_figure = fig
758+
if self._parent_figure and self._parent_figure is not self:
748759
self.pchanged()
749760
self.stale = True
750761

762+
figure = property(get_figure, set_figure,
763+
doc=("The (Sub)Figure that the artist is on. For more "
764+
"control, use the `get_figure` method."))
765+
751766
def set_clip_box(self, clipbox):
752767
"""
753768
Set the artist's clip `.Bbox`.

‎lib/matplotlib/artist.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/artist.pyi
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class _Unset: ...
3131
class Artist:
3232
zorder: float
3333
stale_callback: Callable[[Artist, bool], None] | None
34-
figure: Figure | SubFigure | None
34+
@property
35+
def figure(self) -> Figure | SubFigure: ...
3536
clipbox: BboxBase | None
3637
def __init__(self) -> None: ...
3738
def remove(self) -> None: ...
@@ -87,8 +88,8 @@ class Artist:
8788
) -> None: ...
8889
def set_path_effects(self, path_effects: list[AbstractPathEffect]) -> None: ...
8990
def get_path_effects(self) -> list[AbstractPathEffect]: ...
90-
def get_figure(self) -> Figure | None: ...
91-
def set_figure(self, fig: Figure) -> None: ...
91+
def get_figure(self, root: bool = ...) -> Figure | SubFigure | None: ...
92+
def set_figure(self, fig: Figure | SubFigure) -> None: ...
9293
def set_clip_box(self, clipbox: BboxBase | None) -> None: ...
9394
def set_clip_path(
9495
self,

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,7 @@ def __clear(self):
12961296
self._gridOn = mpl.rcParams['axes.grid']
12971297
old_children, self._children = self._children, []
12981298
for chld in old_children:
1299-
chld.axes = chld.figure = None
1299+
chld.axes = chld._parent_figure = None
13001300
self._mouseover_set = _OrderedSet()
13011301
self.child_axes = []
13021302
self._current_image = None # strictly for pyplot via _sci, _gci

‎lib/matplotlib/axes/_base.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.pyi
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ from matplotlib.cm import ScalarMappable
1313
from matplotlib.legend import Legend
1414
from matplotlib.lines import Line2D
1515
from matplotlib.gridspec import SubplotSpec, GridSpec
16-
from matplotlib.figure import Figure
16+
from matplotlib.figure import Figure, SubFigure
1717
from matplotlib.image import AxesImage
1818
from matplotlib.patches import Patch
1919
from matplotlib.scale import ScaleBase
@@ -81,7 +81,7 @@ class _AxesBase(martist.Artist):
8181
def get_subplotspec(self) -> SubplotSpec | None: ...
8282
def set_subplotspec(self, subplotspec: SubplotSpec) -> None: ...
8383
def get_gridspec(self) -> GridSpec | None: ...
84-
def set_figure(self, fig: Figure) -> None: ...
84+
def set_figure(self, fig: Figure | SubFigure) -> None: ...
8585
@property
8686
def viewLim(self) -> Bbox: ...
8787
def get_xaxis_transform(

‎lib/matplotlib/figure.py

Copy file name to clipboardExpand all lines: lib/matplotlib/figure.py
+64-2Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from contextlib import ExitStack
3131
import inspect
3232
import itertools
33+
import functools
3334
import logging
3435
from numbers import Integral
3536
import threading
@@ -227,6 +228,67 @@ def get_children(self):
227228
*self.legends,
228229
*self.subfigs]
229230

231+
def get_figure(self, root=None):
232+
"""
233+
Return the `.Figure` or `.SubFigure` instance the (Sub)Figure belongs to.
234+
235+
Parameters
236+
----------
237+
root : bool, default=True
238+
If False, return the (Sub)Figure this artist is on. If True,
239+
return the root Figure for a nested tree of SubFigures.
240+
241+
.. deprecated:: 3.10
242+
243+
From version 3.12 *root* will default to False.
244+
"""
245+
if self._root_figure is self:
246+
# Top level Figure
247+
return self
248+
249+
if self._parent is self._root_figure:
250+
# Return early to prevent the deprecation warning when *root* does not
251+
# matter
252+
return self._parent
253+
254+
if root is None:
255+
# When deprecation expires, consider removing the docstring and just
256+
# inheriting the one from Artist.
257+
message = ('From Matplotlib 3.12 SubFigure.get_figure will by default '
258+
'return the direct parent figure, which may be a SubFigure. '
259+
'To suppress this warning, pass the root parameter. Pass '
260+
'`True` to maintain the old behavior and `False` to opt-in to '
261+
'the future behavior.')
262+
_api.warn_deprecated('3.10', message=message)
263+
root = True
264+
265+
if root:
266+
return self._root_figure
267+
268+
return self._parent
269+
270+
def set_figure(self, fig):
271+
"""
272+
.. deprecated:: 3.10
273+
Currently this method will raise an exception if *fig* is anything other
274+
than the root `.Figure` this (Sub)Figure is on. In future it will always
275+
raise an exception.
276+
"""
277+
no_switch = ("The parent and root figures of a (Sub)Figure are set at "
278+
"instantiation and cannot be changed.")
279+
if fig is self._root_figure:
280+
_api.warn_deprecated(
281+
"3.10",
282+
message=(f"{no_switch} From Matplotlib 3.12 this operation will raise "
283+
"an exception."))
284+
return
285+
286+
raise ValueError(no_switch)
287+
288+
figure = property(functools.partial(get_figure, root=True), set_figure,
289+
doc=("The root `Figure`. To get the parent of a `SubFigure`, "
290+
"use the `get_figure` method."))
291+
230292
def contains(self, mouseevent):
231293
"""
232294
Test whether the mouse event occurred on the figure.
@@ -2222,7 +2284,7 @@ def __init__(self, parent, subplotspec, *,
22222284

22232285
self._subplotspec = subplotspec
22242286
self._parent = parent
2225-
self.figure = parent.figure
2287+
self._root_figure = parent._root_figure
22262288

22272289
# subfigures use the parent axstack
22282290
self._axstack = parent._axstack
@@ -2503,7 +2565,7 @@ def __init__(self,
25032565
%(Figure:kwdoc)s
25042566
"""
25052567
super().__init__(**kwargs)
2506-
self.figure = self
2568+
self._root_figure = self
25072569
self._layout_engine = None
25082570

25092571
if layout is not None:

‎lib/matplotlib/figure.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/figure.pyi
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ class FigureBase(Artist):
260260
) -> dict[Hashable, Axes]: ...
261261

262262
class SubFigure(FigureBase):
263-
figure: Figure
263+
@property
264+
def figure(self) -> Figure: ...
264265
subplotpars: SubplotParams
265266
dpi_scale_trans: Affine2D
266267
transFigure: Transform
@@ -298,7 +299,8 @@ class SubFigure(FigureBase):
298299
def get_axes(self) -> list[Axes]: ...
299300

300301
class Figure(FigureBase):
301-
figure: Figure
302+
@property
303+
def figure(self) -> Figure: ...
302304
bbox_inches: Bbox
303305
dpi_scale_trans: Affine2D
304306
bbox: BboxBase

‎lib/matplotlib/offsetbox.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/offsetbox.pyi
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import matplotlib.artist as martist
22
from matplotlib.backend_bases import RendererBase, Event, FigureCanvasBase
33
from matplotlib.colors import Colormap, Normalize
44
import matplotlib.text as mtext
5-
from matplotlib.figure import Figure
5+
from matplotlib.figure import Figure, SubFigure
66
from matplotlib.font_manager import FontProperties
77
from matplotlib.image import BboxImage
88
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
@@ -26,7 +26,7 @@ class OffsetBox(martist.Artist):
2626
width: float | None
2727
height: float | None
2828
def __init__(self, *args, **kwargs) -> None: ...
29-
def set_figure(self, fig: Figure) -> None: ...
29+
def set_figure(self, fig: Figure | SubFigure) -> None: ...
3030
def set_offset(
3131
self,
3232
xy: tuple[float, float]
@@ -271,7 +271,7 @@ class AnnotationBbox(martist.Artist, mtext._AnnotationBase):
271271
| Callable[[RendererBase], Bbox | Transform],
272272
) -> None: ...
273273
def get_children(self) -> list[martist.Artist]: ...
274-
def set_figure(self, fig: Figure) -> None: ...
274+
def set_figure(self, fig: Figure | SubFigure) -> None: ...
275275
def set_fontsize(self, s: str | float | None = ...) -> None: ...
276276
def get_fontsize(self) -> float: ...
277277
def get_tightbbox(self, renderer: RendererBase | None = ...) -> Bbox: ...

‎lib/matplotlib/quiver.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/quiver.pyi
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import matplotlib.artist as martist
22
import matplotlib.collections as mcollections
33
from matplotlib.axes import Axes
4-
from matplotlib.figure import Figure
4+
from matplotlib.figure import Figure, SubFigure
55
from matplotlib.text import Text
66
from matplotlib.transforms import Transform, Bbox
77

@@ -49,7 +49,7 @@ class QuiverKey(martist.Artist):
4949
) -> None: ...
5050
@property
5151
def labelsep(self) -> float: ...
52-
def set_figure(self, fig: Figure) -> None: ...
52+
def set_figure(self, fig: Figure | SubFigure) -> None: ...
5353

5454
class Quiver(mcollections.PolyCollection):
5555
X: ArrayLike

‎lib/matplotlib/tests/test_artist.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_artist.py
+34Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,37 @@ def draw(self, renderer, extra):
562562

563563
assert 'aardvark' == art.draw(renderer, 'aardvark')
564564
assert 'aardvark' == art.draw(renderer, extra='aardvark')
565+
566+
567+
def test_get_figure():
568+
fig = plt.figure()
569+
sfig1 = fig.subfigures()
570+
sfig2 = sfig1.subfigures()
571+
ax = sfig2.subplots()
572+
573+
assert fig.get_figure(root=True) is fig
574+
assert fig.get_figure(root=False) is fig
575+
576+
assert ax.get_figure() is sfig2
577+
assert ax.get_figure(root=False) is sfig2
578+
assert ax.get_figure(root=True) is fig
579+
580+
# SubFigure.get_figure has separate implementation but should give consistent
581+
# results to other artists.
582+
assert sfig2.get_figure(root=False) is sfig1
583+
assert sfig2.get_figure(root=True) is fig
584+
# Currently different results by default.
585+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
586+
assert sfig2.get_figure() is fig
587+
# No deprecation warning if root and parent figure are the same.
588+
assert sfig1.get_figure() is fig
589+
590+
# An artist not yet attached to anything has no figure.
591+
ln = mlines.Line2D([], [])
592+
assert ln.get_figure(root=True) is None
593+
assert ln.get_figure(root=False) is None
594+
595+
# figure attribute is root for (Sub)Figures but parent for other artists.
596+
assert ax.figure is sfig2
597+
assert fig.figure is fig
598+
assert sfig2.figure is fig

‎lib/matplotlib/tests/test_figure.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_figure.py
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,22 @@ def test_warn_colorbar_mismatch():
17351735
subfig3_1.colorbar(im4_1)
17361736

17371737

1738+
def test_set_figure():
1739+
fig = plt.figure()
1740+
sfig1 = fig.subfigures()
1741+
sfig2 = sfig1.subfigures()
1742+
1743+
for f in fig, sfig1, sfig2:
1744+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
1745+
f.set_figure(fig)
1746+
1747+
with pytest.raises(ValueError, match="cannot be changed"):
1748+
sfig2.set_figure(sfig1)
1749+
1750+
with pytest.raises(ValueError, match="cannot be changed"):
1751+
sfig1.set_figure(plt.figure())
1752+
1753+
17381754
def test_subfigure_row_order():
17391755
# Test that subfigures are drawn in row-major order.
17401756
fig = plt.figure()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.