Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit df253bc

Browse files
authored
Merge pull request #19650 from meeseeksmachine/auto-backport-of-pr-19625-on-v3.4.x
Backport PR #19625 on branch v3.4.x (Restore _AxesStack to track a Figure's Axes order.)
2 parents 7bde7fd + fc362fc commit df253bc

File tree

3 files changed

+95
-7
lines changed

3 files changed

+95
-7
lines changed

lib/matplotlib/cbook/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,6 @@ def __len__(self):
620620
def __getitem__(self, ind):
621621
return self._elements[ind]
622622

623-
def as_list(self):
624-
return list(self._elements)
625-
626623
def forward(self):
627624
"""Move the position forward and return the current element."""
628625
self._pos = min(self._pos + 1, len(self._elements) - 1)

lib/matplotlib/figure.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,71 @@ def _stale_figure_callback(self, val):
5252
self.figure.stale = val
5353

5454

55+
class _AxesStack(cbook.Stack):
56+
"""
57+
Specialization of Stack, to handle all tracking of Axes in a Figure.
58+
59+
This stack stores ``ind, axes`` pairs, where ``ind`` is a serial index
60+
tracking the order in which axes were added.
61+
62+
AxesStack is a callable; calling it returns the current axes.
63+
"""
64+
65+
def __init__(self):
66+
super().__init__()
67+
self._ind = 0
68+
69+
def as_list(self):
70+
"""
71+
Return a list of the Axes instances that have been added to the figure.
72+
"""
73+
return [a for i, a in sorted(self._elements)]
74+
75+
def _entry_from_axes(self, e):
76+
return next(((ind, a) for ind, a in self._elements if a == e), None)
77+
78+
def remove(self, a):
79+
"""Remove the axes from the stack."""
80+
super().remove(self._entry_from_axes(a))
81+
82+
def bubble(self, a):
83+
"""
84+
Move the given axes, which must already exist in the stack, to the top.
85+
"""
86+
return super().bubble(self._entry_from_axes(a))
87+
88+
def add(self, a):
89+
"""
90+
Add Axes *a* to the stack.
91+
92+
If *a* is already on the stack, don't add it again.
93+
"""
94+
# All the error checking may be unnecessary; but this method
95+
# is called so seldom that the overhead is negligible.
96+
_api.check_isinstance(Axes, a=a)
97+
98+
if a in self:
99+
return
100+
101+
self._ind += 1
102+
super().push((self._ind, a))
103+
104+
def __call__(self):
105+
"""
106+
Return the active axes.
107+
108+
If no axes exists on the stack, then returns None.
109+
"""
110+
if not len(self._elements):
111+
return None
112+
else:
113+
index, axes = self._elements[self._pos]
114+
return axes
115+
116+
def __contains__(self, a):
117+
return a in self.as_list()
118+
119+
55120
class SubplotParams:
56121
"""
57122
A class to hold the parameters for a subplot.
@@ -141,7 +206,7 @@ def __init__(self):
141206
self.figure = self
142207
# list of child gridspecs for this figure
143208
self._gridspecs = []
144-
self._localaxes = cbook.Stack() # keep track of axes at this level
209+
self._localaxes = _AxesStack() # track all axes and current axes
145210
self.artists = []
146211
self.lines = []
147212
self.patches = []
@@ -716,8 +781,8 @@ def add_subplot(self, *args, **kwargs):
716781

717782
def _add_axes_internal(self, ax, key):
718783
"""Private helper for `add_axes` and `add_subplot`."""
719-
self._axstack.push(ax)
720-
self._localaxes.push(ax)
784+
self._axstack.add(ax)
785+
self._localaxes.add(ax)
721786
self.sca(ax)
722787
ax._remove_method = self.delaxes
723788
# this is to support plt.subplot's re-selection logic
@@ -2161,7 +2226,7 @@ def __init__(self,
21612226

21622227
self.set_tight_layout(tight_layout)
21632228

2164-
self._axstack = cbook.Stack() # track all figure axes and current axes
2229+
self._axstack = _AxesStack() # track all figure axes and current axes
21652230
self.clf()
21662231
self._cachedRenderer = None
21672232

lib/matplotlib/tests/test_figure.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,28 @@ def test_gca():
188188
assert fig.gca(polar=True) is not ax2
189189
assert fig.gca().get_subplotspec().get_geometry() == (1, 2, 1, 1)
190190

191+
# add_axes on an existing Axes should not change stored order, but will
192+
# make it current.
193+
fig.add_axes(ax0)
194+
assert fig.axes == [ax0, ax1, ax2, ax3]
195+
assert fig.gca() is ax0
196+
197+
# add_subplot on an existing Axes should not change stored order, but will
198+
# make it current.
199+
fig.add_subplot(ax2)
200+
assert fig.axes == [ax0, ax1, ax2, ax3]
201+
assert fig.gca() is ax2
202+
191203
fig.sca(ax1)
192204
with pytest.warns(
193205
MatplotlibDeprecationWarning,
194206
match=r'Calling gca\(\) with keyword arguments was deprecated'):
195207
assert fig.gca(projection='rectilinear') is ax1
196208
assert fig.gca() is ax1
197209

210+
# sca() should not change stored order of Axes, which is order added.
211+
assert fig.axes == [ax0, ax1, ax2, ax3]
212+
198213

199214
def test_add_subplot_subclass():
200215
fig = plt.figure()
@@ -241,6 +256,11 @@ def test_add_subplot_invalid():
241256
match='Passing non-integers as three-element position '
242257
'specification is deprecated'):
243258
fig.add_subplot(2.0, 2, 1)
259+
_, ax = plt.subplots()
260+
with pytest.raises(ValueError,
261+
match='The Subplot must have been created in the '
262+
'present figure'):
263+
fig.add_subplot(ax)
244264

245265

246266
@image_comparison(['figure_suptitle'])
@@ -426,6 +446,12 @@ def test_invalid_figure_add_axes():
426446
with pytest.raises(TypeError, match="multiple values for argument 'rect'"):
427447
fig.add_axes([0, 0, 1, 1], rect=[0, 0, 1, 1])
428448

449+
_, ax = plt.subplots()
450+
with pytest.raises(ValueError,
451+
match="The Axes must have been created in the present "
452+
"figure"):
453+
fig.add_axes(ax)
454+
429455

430456
def test_subplots_shareax_loglabels():
431457
fig, axs = plt.subplots(2, 2, sharex=True, sharey=True, squeeze=False)

0 commit comments

Comments
 (0)