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

Skip to content

Commit 9b7bfdc

Browse files
committed
Switch to a private, simpler AxesStack.
The current implementation of AxesStack subclasses cbook.Stack, which requires hashable keys, which leads to additional complexity on the caller's side (`_make_key`). Instead, switch to using two lists (keys and axes) and relying on `list.index`, which makes the implementation much simpler. Also make the new class private and deprecate the previous one.
1 parent fa95b58 commit 9b7bfdc

File tree

2 files changed

+67
-47
lines changed

2 files changed

+67
-47
lines changed

lib/matplotlib/figure.py

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class AxesStack(Stack):
7272
7373
"""
7474
def __init__(self):
75+
cbook.warn_deprecated("2.1")
7576
Stack.__init__(self)
7677
self._ind = 0
7778

@@ -156,6 +157,62 @@ def __contains__(self, a):
156157
return a in self.as_list()
157158

158159

160+
class _AxesStack(object):
161+
"""Lightweight stack that tracks Axes in a Figure.
162+
"""
163+
164+
def __init__(self):
165+
# We maintain a list of (creation_index, key, axes) tuples.
166+
# We do not use an OrderedDict because 1. the keys may not be hashable
167+
# and 2. we need to directly find a pair corresponding to an axes (i.e.
168+
# we'd really need a two-way dict).
169+
self._items = []
170+
self._created = 0
171+
172+
def as_list(self):
173+
"""Copy of the list of axes, in the order of insertion.
174+
"""
175+
return [ax for _, _, ax in sorted(self._items)]
176+
177+
def get(self, key):
178+
"""Find the axes corresponding to a key; defaults to `None`.
179+
"""
180+
return next((ax for _, k, ax in self._items if k == key), None)
181+
182+
def current_key_axes(self):
183+
"""Return the topmost `(key, axes)` pair, or `(None, None)` if empty.
184+
"""
185+
_, key, ax = (self._items or [(None, None, None)])[-1]
186+
return key, ax
187+
188+
def add(self, key, ax):
189+
"""Append a `(key, axes)` pair, unless the axes are already present.
190+
"""
191+
# Skipping existing Axes is needed to support calling `add_axes` with
192+
# an already existing Axes.
193+
if not any(a == ax for _, _, a in self._items):
194+
self._items.append((self._created, key, ax))
195+
self._created += 1
196+
197+
def bubble(self, ax):
198+
"""Move an axes and its corresponding key to the top.
199+
"""
200+
idx, = (idx for idx, (_, _, a) in enumerate(self._items) if a == ax)
201+
self._items.append(self._items[idx])
202+
del self._items[idx]
203+
204+
def remove(self, ax):
205+
"""Remove an axes and its corresponding key.
206+
"""
207+
idx, = (idx for idx, (_, _, a) in enumerate(self._items) if a == ax)
208+
del self._items[idx]
209+
210+
def clear(self):
211+
"""Clear the stack.
212+
"""
213+
del self._items[:]
214+
215+
159216
class SubplotParams(object):
160217
"""
161218
A class to hold the parameters for a subplot
@@ -354,7 +411,7 @@ def __init__(self,
354411
self.subplotpars = subplotpars
355412
self.set_tight_layout(tight_layout)
356413

357-
self._axstack = AxesStack() # track all figure axes and current axes
414+
self._axstack = _AxesStack() # track all figure axes and current axes
358415
self.clf()
359416
self._cachedRenderer = None
360417

@@ -402,10 +459,8 @@ def show(self, warn=True):
402459
"matplotlib is currently using a non-GUI backend, "
403460
"so cannot show the figure")
404461

405-
def _get_axes(self):
406-
return self._axstack.as_list()
407-
408-
axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure")
462+
axes = property(lambda self: self._axstack.as_list(),
463+
doc="Read-only: list of axes in Figure")
409464

410465
def _get_dpi(self):
411466
return self._dpi
@@ -817,36 +872,6 @@ def delaxes(self, a):
817872
func(self)
818873
self.stale = True
819874

820-
def _make_key(self, *args, **kwargs):
821-
'make a hashable key out of args and kwargs'
822-
823-
def fixitems(items):
824-
#items may have arrays and lists in them, so convert them
825-
# to tuples for the key
826-
ret = []
827-
for k, v in items:
828-
# some objects can define __getitem__ without being
829-
# iterable and in those cases the conversion to tuples
830-
# will fail. So instead of using the iterable(v) function
831-
# we simply try and convert to a tuple, and proceed if not.
832-
try:
833-
v = tuple(v)
834-
except Exception:
835-
pass
836-
ret.append((k, v))
837-
return tuple(ret)
838-
839-
def fixlist(args):
840-
ret = []
841-
for a in args:
842-
if iterable(a):
843-
a = tuple(a)
844-
ret.append(a)
845-
return tuple(ret)
846-
847-
key = fixlist(args), fixitems(six.iteritems(kwargs))
848-
return key
849-
850875
@docstring.dedent_interpd
851876
def add_axes(self, *args, **kwargs):
852877
"""
@@ -900,9 +925,9 @@ def add_axes(self, *args, **kwargs):
900925

901926
# shortcut the projection "key" modifications later on, if an axes
902927
# with the exact args/kwargs exists, return it immediately.
903-
key = self._make_key(*args, **kwargs)
928+
key = (args, kwargs)
904929
ax = self._axstack.get(key)
905-
if ax is not None:
930+
if ax:
906931
self.sca(ax)
907932
return ax
908933

@@ -919,7 +944,7 @@ def add_axes(self, *args, **kwargs):
919944
# check that an axes of this type doesn't already exist, if it
920945
# does, set it as active and return it
921946
ax = self._axstack.get(key)
922-
if ax is not None and isinstance(ax, projection_class):
947+
if isinstance(ax, projection_class):
923948
self.sca(ax)
924949
return ax
925950

@@ -993,15 +1018,14 @@ def add_subplot(self, *args, **kwargs):
9931018
raise ValueError(msg)
9941019
# make a key for the subplot (which includes the axes object id
9951020
# in the hash)
996-
key = self._make_key(*args, **kwargs)
1021+
key = (args, kwargs)
9971022
else:
9981023
projection_class, kwargs, key = process_projection_requirements(
9991024
self, *args, **kwargs)
10001025

10011026
# try to find the axes with this key in the stack
10021027
ax = self._axstack.get(key)
1003-
1004-
if ax is not None:
1028+
if ax:
10051029
if isinstance(ax, projection_class):
10061030
# the axes already existed, so set it as active & return
10071031
self.sca(ax)
@@ -1483,7 +1507,7 @@ def _gci(self):
14831507
do not use elsewhere.
14841508
"""
14851509
# Look first for an image in the current Axes:
1486-
cax = self._axstack.current_key_axes()[1]
1510+
ckey, cax = self._axstack.current_key_axes()
14871511
if cax is None:
14881512
return None
14891513
im = cax._gci()

lib/matplotlib/projections/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,7 @@ def process_projection_requirements(figure, *args, **kwargs):
101101
raise TypeError('projection must be a string, None or implement a '
102102
'_as_mpl_axes method. Got %r' % projection)
103103

104-
# Make the key without projection kwargs, this is used as a unique
105-
# lookup for axes instances
106-
key = figure._make_key(*args, **kwargs)
107-
108-
return projection_class, kwargs, key
104+
return projection_class, kwargs, (args, kwargs)
109105

110106

111107
def get_projection_names():

0 commit comments

Comments
 (0)