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

Skip to content

Commit 1143621

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 1143621

File tree

2 files changed

+68
-48
lines changed

2 files changed

+68
-48
lines changed

lib/matplotlib/figure.py

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

353-
self._axstack = AxesStack() # track all figure axes and current axes
410+
self._axstack = _AxesStack() # track all figure axes and current axes
354411
self.clf()
355412
self._cachedRenderer = None
356413

@@ -398,10 +455,8 @@ def show(self, warn=True):
398455
"matplotlib is currently using a non-GUI backend, "
399456
"so cannot show the figure")
400457

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")
458+
axes = property(lambda self: self._axstack.as_list(),
459+
doc="Read-only: list of axes in Figure")
405460

406461
def _get_dpi(self):
407462
return self._dpi
@@ -812,36 +867,6 @@ def delaxes(self, a):
812867
func(self)
813868
self.stale = True
814869

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-
845870
@docstring.dedent_interpd
846871
def add_axes(self, *args, **kwargs):
847872
"""
@@ -895,9 +920,9 @@ def add_axes(self, *args, **kwargs):
895920

896921
# shortcut the projection "key" modifications later on, if an axes
897922
# with the exact args/kwargs exists, return it immediately.
898-
key = self._make_key(*args, **kwargs)
923+
key = (args, kwargs)
899924
ax = self._axstack.get(key)
900-
if ax is not None:
925+
if ax:
901926
self.sca(ax)
902927
return ax
903928

@@ -914,7 +939,7 @@ def add_axes(self, *args, **kwargs):
914939
# check that an axes of this type doesn't already exist, if it
915940
# does, set it as active and return it
916941
ax = self._axstack.get(key)
917-
if ax is not None and isinstance(ax, projection_class):
942+
if isinstance(ax, projection_class):
918943
self.sca(ax)
919944
return ax
920945

@@ -988,15 +1013,14 @@ def add_subplot(self, *args, **kwargs):
9881013
raise ValueError(msg)
9891014
# make a key for the subplot (which includes the axes object id
9901015
# in the hash)
991-
key = self._make_key(*args, **kwargs)
1016+
key = (args, kwargs)
9921017
else:
9931018
projection_class, kwargs, key = process_projection_requirements(
9941019
self, *args, **kwargs)
9951020

9961021
# try to find the axes with this key in the stack
9971022
ax = self._axstack.get(key)
998-
999-
if ax is not None:
1023+
if ax:
10001024
if isinstance(ax, projection_class):
10011025
# the axes already existed, so set it as active & return
10021026
self.sca(ax)
@@ -1496,7 +1520,7 @@ def _gci(self):
14961520
do not use elsewhere.
14971521
"""
14981522
# Look first for an image in the current Axes:
1499-
cax = self._axstack.current_key_axes()[1]
1523+
ckey, cax = self._axstack.current_key_axes()
15001524
if cax is None:
15011525
return None
15021526
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)