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

Skip to content

Commit 3769792

Browse files
authored
Merge pull request #9037 from anntzer/deprecate-axes-collision
API: Deprecate axes collision
2 parents b19cd40 + d84c9ba commit 3769792

File tree

2 files changed

+70
-84
lines changed

2 files changed

+70
-84
lines changed

lib/matplotlib/figure.py

Lines changed: 65 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ class AxesStack(Stack):
7272
7373
"""
7474
def __init__(self):
75-
cbook.warn_deprecated("2.1")
7675
Stack.__init__(self)
7776
self._ind = 0
7877

@@ -92,6 +91,13 @@ def get(self, key):
9291
item = dict(self._elements).get(key)
9392
if item is None:
9493
return None
94+
cbook.warn_deprecated(
95+
"2.1",
96+
"Adding an axes using the same arguments as a previous axes "
97+
"currently reuses the earlier instance. In a future version, "
98+
"a new instance will always be created and returned. Meanwhile, "
99+
"this warning can be suppressed, and the future behavior ensured, "
100+
"by passing a unique label to each axes instance.")
95101
return item[1]
96102

97103
def _entry_from_axes(self, e):
@@ -159,62 +165,6 @@ def __contains__(self, a):
159165
return a in self.as_list()
160166

161167

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

418-
self._axstack = _AxesStack() # track all figure axes and current axes
368+
self._axstack = AxesStack() # track all figure axes and current axes
419369
self.clf()
420370
self._cachedRenderer = None
421371

@@ -467,8 +417,10 @@ def show(self, warn=True):
467417
"matplotlib is currently using a non-GUI backend, "
468418
"so cannot show the figure")
469419

470-
axes = property(lambda self: self._axstack.as_list(),
471-
doc="Read-only: list of axes in Figure")
420+
def _get_axes(self):
421+
return self._axstack.as_list()
422+
423+
axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure")
472424

473425
def _get_dpi(self):
474426
return self._dpi
@@ -890,6 +842,36 @@ def delaxes(self, a):
890842
func(self)
891843
self.stale = True
892844

845+
def _make_key(self, *args, **kwargs):
846+
'make a hashable key out of args and kwargs'
847+
848+
def fixitems(items):
849+
#items may have arrays and lists in them, so convert them
850+
# to tuples for the key
851+
ret = []
852+
for k, v in items:
853+
# some objects can define __getitem__ without being
854+
# iterable and in those cases the conversion to tuples
855+
# will fail. So instead of using the iterable(v) function
856+
# we simply try and convert to a tuple, and proceed if not.
857+
try:
858+
v = tuple(v)
859+
except Exception:
860+
pass
861+
ret.append((k, v))
862+
return tuple(ret)
863+
864+
def fixlist(args):
865+
ret = []
866+
for a in args:
867+
if iterable(a):
868+
a = tuple(a)
869+
ret.append(a)
870+
return tuple(ret)
871+
872+
key = fixlist(args), fixitems(six.iteritems(kwargs))
873+
return key
874+
893875
def add_axes(self, *args, **kwargs):
894876
"""
895877
Add an axes at position *rect* [*left*, *bottom*, *width*,
@@ -926,14 +908,14 @@ def add_axes(self, *args, **kwargs):
926908
fig.add_axes(rect, projection='polar')
927909
fig.add_axes(ax)
928910
929-
If the figure already has an axes with the same parameters,
930-
then it will simply make that axes current and return it. If
931-
you do not want this behavior, e.g., you want to force the
932-
creation of a new Axes, you must use a unique set of args and
933-
kwargs. The axes :attr:`~matplotlib.axes.Axes.label`
934-
attribute has been exposed for this purpose. e.g., if you want
935-
two axes that are otherwise identical to be added to the
936-
figure, make sure you give them unique labels::
911+
If the figure already has an axes with the same parameters, then it
912+
will simply make that axes current and return it. This behavior
913+
has been deprecated as of Matplotlib 2.1. Meanwhile, if you do
914+
not want this behavior (i.e., you want to force the creation of a
915+
new Axes), you must use a unique set of args and kwargs. The axes
916+
:attr:`~matplotlib.axes.Axes.label` attribute has been exposed for this
917+
purpose: if you want two axes that are otherwise identical to be added
918+
to the figure, make sure you give them unique labels::
937919
938920
fig.add_axes(rect, label='axes1')
939921
fig.add_axes(rect, label='axes2')
@@ -954,9 +936,9 @@ def add_axes(self, *args, **kwargs):
954936

955937
# shortcut the projection "key" modifications later on, if an axes
956938
# with the exact args/kwargs exists, return it immediately.
957-
key = (args, kwargs)
939+
key = self._make_key(*args, **kwargs)
958940
ax = self._axstack.get(key)
959-
if ax:
941+
if ax is not None:
960942
self.sca(ax)
961943
return ax
962944

@@ -976,7 +958,7 @@ def add_axes(self, *args, **kwargs):
976958
# check that an axes of this type doesn't already exist, if it
977959
# does, set it as active and return it
978960
ax = self._axstack.get(key)
979-
if isinstance(ax, projection_class):
961+
if ax is not None and isinstance(ax, projection_class):
980962
self.sca(ax)
981963
return ax
982964

@@ -1021,14 +1003,14 @@ def add_subplot(self, *args, **kwargs):
10211003
-----
10221004
If the figure already has a subplot with key (*args*,
10231005
*kwargs*) then it will simply make that subplot current and
1024-
return it.
1006+
return it. This behavior is deprecated.
10251007
10261008
Examples
10271009
--------
10281010
fig.add_subplot(111)
10291011
10301012
# equivalent but more general
1031-
fig.add_subplot(1,1,1)
1013+
fig.add_subplot(1, 1, 1)
10321014
10331015
# add subplot with red background
10341016
fig.add_subplot(212, facecolor='r')
@@ -1047,29 +1029,29 @@ def add_subplot(self, *args, **kwargs):
10471029
return
10481030

10491031
if len(args) == 1 and isinstance(args[0], int):
1050-
args = tuple([int(c) for c in str(args[0])])
1051-
if len(args) != 3:
1052-
raise ValueError("Integer subplot specification must " +
1053-
"be a three digit number. " +
1054-
"Not {n:d}".format(n=len(args)))
1032+
if not 100 <= args[0] <= 999:
1033+
raise ValueError("Integer subplot specification must be a "
1034+
"three-digit number, not {}".format(args[0]))
1035+
args = tuple(map(int, str(args[0])))
10551036

10561037
if isinstance(args[0], SubplotBase):
10571038

10581039
a = args[0]
10591040
if a.get_figure() is not self:
1060-
msg = ("The Subplot must have been created in the present"
1061-
" figure")
1041+
msg = ("The Subplot must have been created in the present "
1042+
"figure")
10621043
raise ValueError(msg)
10631044
# make a key for the subplot (which includes the axes object id
10641045
# in the hash)
1065-
key = (args, kwargs)
1046+
key = self._make_key(*args, **kwargs)
10661047
else:
10671048
projection_class, kwargs, key = process_projection_requirements(
10681049
self, *args, **kwargs)
10691050

10701051
# try to find the axes with this key in the stack
10711052
ax = self._axstack.get(key)
1072-
if ax:
1053+
1054+
if ax is not None:
10731055
if isinstance(ax, projection_class):
10741056
# the axes already existed, so set it as active & return
10751057
self.sca(ax)
@@ -1638,7 +1620,7 @@ def _gci(self):
16381620
do not use elsewhere.
16391621
"""
16401622
# Look first for an image in the current Axes:
1641-
ckey, cax = self._axstack.current_key_axes()
1623+
cax = self._axstack.current_key_axes()[1]
16421624
if cax is None:
16431625
return None
16441626
im = cax._gci()

lib/matplotlib/projections/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ def process_projection_requirements(figure, *args, **kwargs):
9696
raise TypeError('projection must be a string, None or implement a '
9797
'_as_mpl_axes method. Got %r' % projection)
9898

99-
return projection_class, kwargs, (args, kwargs)
99+
# Make the key without projection kwargs, this is used as a unique
100+
# lookup for axes instances
101+
key = figure._make_key(*args, **kwargs)
102+
103+
return projection_class, kwargs, key
100104

101105

102106
def get_projection_names():

0 commit comments

Comments
 (0)