Skip to content

Commit 333e5d1

Browse files
authored
Merge pull request #22114 from anntzer/as
Rewrite AxesStack independently of cbook.Stack.
2 parents 29a60b9 + 8669c46 commit 333e5d1

File tree

1 file changed

+28
-57
lines changed

1 file changed

+28
-57
lines changed

lib/matplotlib/figure.py

+28-57
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from contextlib import ExitStack
1818
import inspect
19+
import itertools
1920
import logging
2021
from numbers import Integral
2122

@@ -50,69 +51,41 @@ def _stale_figure_callback(self, val):
5051
self.figure.stale = val
5152

5253

53-
class _AxesStack(cbook.Stack):
54+
class _AxesStack:
5455
"""
55-
Specialization of Stack, to handle all tracking of Axes in a Figure.
56+
Helper class to track axes in a figure.
5657
57-
This stack stores ``ind, axes`` pairs, where ``ind`` is a serial index
58-
tracking the order in which axes were added.
59-
60-
AxesStack is a callable; calling it returns the current axes.
58+
Axes are tracked both in the order in which they have been added
59+
(``self._axes`` insertion/iteration order) and in the separate "gca" stack
60+
(which is the index to which they map in the ``self._axes`` dict).
6161
"""
6262

6363
def __init__(self):
64-
super().__init__()
65-
self._ind = 0
64+
self._axes = {} # Mapping of axes to "gca" order.
65+
self._counter = itertools.count()
6666

6767
def as_list(self):
68-
"""
69-
Return a list of the Axes instances that have been added to the figure.
70-
"""
71-
return [a for i, a in sorted(self._elements)]
72-
73-
def _entry_from_axes(self, e):
74-
return next(((ind, a) for ind, a in self._elements if a == e), None)
68+
"""List the axes that have been added to the figure."""
69+
return [*self._axes] # This relies on dict preserving order.
7570

7671
def remove(self, a):
7772
"""Remove the axes from the stack."""
78-
super().remove(self._entry_from_axes(a))
73+
self._axes.pop(a)
7974

8075
def bubble(self, a):
81-
"""
82-
Move the given axes, which must already exist in the stack, to the top.
83-
"""
84-
return super().bubble(self._entry_from_axes(a))
76+
"""Move an axes, which must already exist in the stack, to the top."""
77+
if a not in self._axes:
78+
raise ValueError("Axes has not been added yet")
79+
self._axes[a] = next(self._counter)
8580

8681
def add(self, a):
87-
"""
88-
Add Axes *a* to the stack.
89-
90-
If *a* is already on the stack, don't add it again.
91-
"""
92-
# All the error checking may be unnecessary; but this method
93-
# is called so seldom that the overhead is negligible.
94-
_api.check_isinstance(Axes, a=a)
95-
96-
if a in self:
97-
return
98-
99-
self._ind += 1
100-
super().push((self._ind, a))
82+
"""Add an axes to the stack, ignoring it if already present."""
83+
if a not in self._axes:
84+
self._axes[a] = next(self._counter)
10185

102-
def __call__(self):
103-
"""
104-
Return the active axes.
105-
106-
If no axes exists on the stack, then returns None.
107-
"""
108-
if not len(self._elements):
109-
return None
110-
else:
111-
index, axes = self._elements[self._pos]
112-
return axes
113-
114-
def __contains__(self, a):
115-
return a in self.as_list()
86+
def current(self):
87+
"""Return the active axes, or None if the stack is empty."""
88+
return max(self._axes, key=self._axes.__getitem__, default=None)
11689

11790

11891
class SubplotParams:
@@ -1512,10 +1485,8 @@ def gca(self, **kwargs):
15121485
"new axes with default keyword arguments. To create a new "
15131486
"axes with non-default arguments, use plt.axes() or "
15141487
"plt.subplot().")
1515-
if self._axstack.empty():
1516-
return self.add_subplot(1, 1, 1, **kwargs)
1517-
else:
1518-
return self._axstack()
1488+
ax = self._axstack.current()
1489+
return ax if ax is not None else self.add_subplot(**kwargs)
15191490

15201491
def _gci(self):
15211492
# Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere.
@@ -1534,13 +1505,13 @@ def _gci(self):
15341505
Historically, the only colorable artists were images; hence the name
15351506
``gci`` (get current image).
15361507
"""
1537-
# Look first for an image in the current Axes:
1538-
if self._axstack.empty():
1508+
# Look first for an image in the current Axes.
1509+
ax = self._axstack.current()
1510+
if ax is None:
15391511
return None
1540-
im = self._axstack()._gci()
1512+
im = ax._gci()
15411513
if im is not None:
15421514
return im
1543-
15441515
# If there is no image in the current Axes, search for
15451516
# one in a previously created Axes. Whether this makes
15461517
# sense is debatable, but it is the documented behavior.
@@ -2834,7 +2805,7 @@ def clf(self, keep_observers=False):
28342805
toolbar = self.canvas.toolbar
28352806
if toolbar is not None:
28362807
toolbar.update()
2837-
self._axstack.clear()
2808+
self._axstack = _AxesStack()
28382809
self.artists = []
28392810
self.lines = []
28402811
self.patches = []

0 commit comments

Comments
 (0)