From dd54033827fe3f64b97588cd117bc3d4f727cb46 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 14 Aug 2017 15:22:44 -0400 Subject: [PATCH] Revert "ENH: Switch to a private, simpler AxesStack." --- lib/matplotlib/figure.py | 108 ++++++++++--------------- lib/matplotlib/projections/__init__.py | 6 +- 2 files changed, 47 insertions(+), 67 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index db45e04410e6..0e22ac19034f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -72,7 +72,6 @@ class AxesStack(Stack): """ def __init__(self): - cbook.warn_deprecated("2.1") Stack.__init__(self) self._ind = 0 @@ -159,62 +158,6 @@ def __contains__(self, a): return a in self.as_list() -class _AxesStack(object): - """Lightweight stack that tracks Axes in a Figure. - """ - - def __init__(self): - # We maintain a list of (creation_index, key, axes) tuples. - # We do not use an OrderedDict because 1. the keys may not be hashable - # and 2. we need to directly find a pair corresponding to an axes (i.e. - # we'd really need a two-way dict). - self._items = [] - self._created = 0 - - def as_list(self): - """Copy of the list of axes, in the order of insertion. - """ - return [ax for _, _, ax in sorted(self._items)] - - def get(self, key): - """Find the axes corresponding to a key; defaults to `None`. - """ - return next((ax for _, k, ax in self._items if k == key), None) - - def current_key_axes(self): - """Return the topmost `(key, axes)` pair, or `(None, None)` if empty. - """ - _, key, ax = (self._items or [(None, None, None)])[-1] - return key, ax - - def add(self, key, ax): - """Append a `(key, axes)` pair, unless the axes are already present. - """ - # Skipping existing Axes is needed to support calling `add_axes` with - # an already existing Axes. - if not any(a == ax for _, _, a in self._items): - self._items.append((self._created, key, ax)) - self._created += 1 - - def bubble(self, ax): - """Move an axes and its corresponding key to the top. - """ - idx, = (idx for idx, (_, _, a) in enumerate(self._items) if a == ax) - self._items.append(self._items[idx]) - del self._items[idx] - - def remove(self, ax): - """Remove an axes and its corresponding key. - """ - idx, = (idx for idx, (_, _, a) in enumerate(self._items) if a == ax) - del self._items[idx] - - def clear(self): - """Clear the stack. - """ - del self._items[:] - - class SubplotParams(object): """ A class to hold the parameters for a subplot @@ -415,7 +358,7 @@ def __init__(self, self.subplotpars = subplotpars self.set_tight_layout(tight_layout) - self._axstack = _AxesStack() # track all figure axes and current axes + self._axstack = AxesStack() # track all figure axes and current axes self.clf() self._cachedRenderer = None @@ -467,8 +410,10 @@ def show(self, warn=True): "matplotlib is currently using a non-GUI backend, " "so cannot show the figure") - axes = property(lambda self: self._axstack.as_list(), - doc="Read-only: list of axes in Figure") + def _get_axes(self): + return self._axstack.as_list() + + axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure") def _get_dpi(self): return self._dpi @@ -890,6 +835,36 @@ def delaxes(self, a): func(self) self.stale = True + def _make_key(self, *args, **kwargs): + 'make a hashable key out of args and kwargs' + + def fixitems(items): + #items may have arrays and lists in them, so convert them + # to tuples for the key + ret = [] + for k, v in items: + # some objects can define __getitem__ without being + # iterable and in those cases the conversion to tuples + # will fail. So instead of using the iterable(v) function + # we simply try and convert to a tuple, and proceed if not. + try: + v = tuple(v) + except Exception: + pass + ret.append((k, v)) + return tuple(ret) + + def fixlist(args): + ret = [] + for a in args: + if iterable(a): + a = tuple(a) + ret.append(a) + return tuple(ret) + + key = fixlist(args), fixitems(six.iteritems(kwargs)) + return key + def add_axes(self, *args, **kwargs): """ Add an axes at position *rect* [*left*, *bottom*, *width*, @@ -954,9 +929,9 @@ def add_axes(self, *args, **kwargs): # shortcut the projection "key" modifications later on, if an axes # with the exact args/kwargs exists, return it immediately. - key = (args, kwargs) + key = self._make_key(*args, **kwargs) ax = self._axstack.get(key) - if ax: + if ax is not None: self.sca(ax) return ax @@ -976,7 +951,7 @@ def add_axes(self, *args, **kwargs): # check that an axes of this type doesn't already exist, if it # does, set it as active and return it ax = self._axstack.get(key) - if isinstance(ax, projection_class): + if ax is not None and isinstance(ax, projection_class): self.sca(ax) return ax @@ -1062,14 +1037,15 @@ def add_subplot(self, *args, **kwargs): raise ValueError(msg) # make a key for the subplot (which includes the axes object id # in the hash) - key = (args, kwargs) + key = self._make_key(*args, **kwargs) else: projection_class, kwargs, key = process_projection_requirements( self, *args, **kwargs) # try to find the axes with this key in the stack ax = self._axstack.get(key) - if ax: + + if ax is not None: if isinstance(ax, projection_class): # the axes already existed, so set it as active & return self.sca(ax) @@ -1638,7 +1614,7 @@ def _gci(self): do not use elsewhere. """ # Look first for an image in the current Axes: - ckey, cax = self._axstack.current_key_axes() + cax = self._axstack.current_key_axes()[1] if cax is None: return None im = cax._gci() diff --git a/lib/matplotlib/projections/__init__.py b/lib/matplotlib/projections/__init__.py index 5e5ffcaf2e66..1e423420b0b6 100644 --- a/lib/matplotlib/projections/__init__.py +++ b/lib/matplotlib/projections/__init__.py @@ -96,7 +96,11 @@ def process_projection_requirements(figure, *args, **kwargs): raise TypeError('projection must be a string, None or implement a ' '_as_mpl_axes method. Got %r' % projection) - return projection_class, kwargs, (args, kwargs) + # Make the key without projection kwargs, this is used as a unique + # lookup for axes instances + key = figure._make_key(*args, **kwargs) + + return projection_class, kwargs, key def get_projection_names():