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

Skip to content

Commit 7eb590d

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 00ae164 commit 7eb590d

File tree

2 files changed

+74
-48
lines changed

2 files changed

+74
-48
lines changed

lib/matplotlib/figure.py

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
import six
1818

19-
import warnings
2019
from operator import itemgetter
20+
import warnings
2121

2222
import numpy as np
2323

@@ -73,6 +73,7 @@ class AxesStack(Stack):
7373
7474
"""
7575
def __init__(self):
76+
cbook.warn_deprecated("2.0")
7677
Stack.__init__(self)
7778
self._ind = 0
7879

@@ -157,6 +158,68 @@ def __contains__(self, a):
157158
return a in self.as_list()
158159

159160

161+
class _AxesStack(object):
162+
"""Lightweight stack that tracks Axes in a Figure.
163+
"""
164+
165+
# We do not subclass the Stack class from cbook to avoid hashability
166+
# issues.
167+
168+
def __init__(self):
169+
self._keys = []
170+
self._axes = []
171+
self._axes_ordered = []
172+
173+
def as_list(self):
174+
"""Copy of the list of axes, in the order of insertion.
175+
"""
176+
return self._axes_ordered[:]
177+
178+
def get(self, key):
179+
"""Find the axes corresponding to a key; defaults to `None`.
180+
"""
181+
try:
182+
return self._axes[self._keys.index(key)]
183+
except ValueError:
184+
return None
185+
186+
def current_key_axes(self):
187+
"""Return the topmost key, axes pair, or `None, None` if empty.
188+
"""
189+
return (self._keys[-1], self._axes[-1]) if self._keys else (None, None)
190+
191+
def add(self, key, ax):
192+
"""Append a key, axes pair, unless the axes are already present.
193+
"""
194+
# Skipping existing Axes is needed to support calling `add_axes` with
195+
# an already existing Axes.
196+
if ax in self._axes_ordered:
197+
return
198+
self._keys.append(key)
199+
self._axes.append(ax)
200+
self._axes_ordered.append(ax)
201+
202+
def bubble(self, ax):
203+
"""Move an axes and its corresponding key to the top.
204+
"""
205+
idx = self._axes.index(ax)
206+
self._keys.append(self._keys[idx])
207+
self._axes.append(self._axes[idx])
208+
del self._keys[idx], self._axes[idx]
209+
210+
def remove(self, ax):
211+
"""Remove an axes and its corresponding key.
212+
"""
213+
del self._keys[self._axes.index(ax)]
214+
self._axes.remove(ax)
215+
self._axes_ordered.remove(ax)
216+
217+
def clear(self):
218+
"""Clear the stack.
219+
"""
220+
del self._keys[:], self._axes[:], self._axes_ordered[:]
221+
222+
160223
class SubplotParams(object):
161224
"""
162225
A class to hold the parameters for a subplot
@@ -350,7 +413,7 @@ def __init__(self,
350413
self.subplotpars = subplotpars
351414
self.set_tight_layout(tight_layout)
352415

353-
self._axstack = AxesStack() # track all figure axes and current axes
416+
self._axstack = _AxesStack() # track all figure axes and current axes
354417
self.clf()
355418
self._cachedRenderer = None
356419

@@ -398,10 +461,8 @@ def show(self, warn=True):
398461
"matplotlib is currently using a non-GUI backend, "
399462
"so cannot show the figure")
400463

401-
def _get_axes(self):
402-
return self._axstack.as_list()
403-
404-
axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure")
464+
axes = property(lambda self: self._axstack.as_list(),
465+
doc="Read-only: list of axes in Figure")
405466

406467
def _get_dpi(self):
407468
return self._dpi
@@ -812,36 +873,6 @@ def delaxes(self, a):
812873
func(self)
813874
self.stale = True
814875

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

896927
# shortcut the projection "key" modifications later on, if an axes
897928
# with the exact args/kwargs exists, return it immediately.
898-
key = self._make_key(*args, **kwargs)
929+
key = (args, kwargs)
899930
ax = self._axstack.get(key)
900-
if ax is not None:
931+
if ax:
901932
self.sca(ax)
902933
return ax
903934

@@ -914,7 +945,7 @@ def add_axes(self, *args, **kwargs):
914945
# check that an axes of this type doesn't already exist, if it
915946
# does, set it as active and return it
916947
ax = self._axstack.get(key)
917-
if ax is not None and isinstance(ax, projection_class):
948+
if isinstance(ax, projection_class):
918949
self.sca(ax)
919950
return ax
920951

@@ -988,15 +1019,14 @@ def add_subplot(self, *args, **kwargs):
9881019
raise ValueError(msg)
9891020
# make a key for the subplot (which includes the axes object id
9901021
# in the hash)
991-
key = self._make_key(*args, **kwargs)
1022+
key = (args, kwargs)
9921023
else:
9931024
projection_class, kwargs, key = process_projection_requirements(
9941025
self, *args, **kwargs)
9951026

9961027
# try to find the axes with this key in the stack
9971028
ax = self._axstack.get(key)
998-
999-
if ax is not None:
1029+
if ax:
10001030
if isinstance(ax, projection_class):
10011031
# the axes already existed, so set it as active & return
10021032
self.sca(ax)
@@ -1496,7 +1526,7 @@ def _gci(self):
14961526
do not use elsewhere.
14971527
"""
14981528
# Look first for an image in the current Axes:
1499-
cax = self._axstack.current_key_axes()[1]
1529+
ckey, cax = self._axstack.current_key_axes()
15001530
if cax is None:
15011531
return None
15021532
im = cax._gci()

lib/matplotlib/projections/__init__.py

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

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

112108

113109
def get_projection_names():

0 commit comments

Comments
 (0)