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

Skip to content

Commit 5e2a5fa

Browse files
committed
[3050996] Prevent an Axes from being added to a Figure twice.
The changeset includes refactoring to consolidate all Axes tracking in a single data structure. svn path=/trunk/matplotlib/; revision=8681
1 parent 2840392 commit 5e2a5fa

2 files changed

Lines changed: 78 additions & 23 deletions

File tree

lib/matplotlib/artist.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ def __init__(self):
9292
self.eventson = False # fire events only if eventson
9393
self._oid = 0 # an observer id
9494
self._propobservers = {} # a dict from oids to funcs
95-
self.axes = None
95+
try:
96+
self.axes = None
97+
except AttributeError:
98+
# Handle self.axes as a read-only property, as in Figure.
99+
pass
96100
self._remove_method = None
97101
self._url = None
98102
self._gid = None

lib/matplotlib/figure.py

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,64 @@
3737

3838
docstring.interpd.update(projection_names = get_projection_names())
3939

40+
class AxesStack(Stack):
41+
"""
42+
Specialization of the Stack to handle all
43+
tracking of Axes in a Figure. This requires storing
44+
key, axes pairs. The key is based on the args and kwargs
45+
used in generating the Axes.
46+
"""
47+
def as_list(self):
48+
"""
49+
Return a list of the Axes instances that have been added to the figure
50+
"""
51+
return [a for k, a in self._elements]
52+
53+
def get(self, key):
54+
"""
55+
Return the Axes instance that was added with *key*.
56+
If it is not present, return None.
57+
"""
58+
return dict(self._elements).get(key)
59+
60+
def _entry_from_axes(self, e):
61+
k = dict([(a, k) for (k, a) in self._elements])[e]
62+
return k, e
63+
64+
def remove(self, a):
65+
Stack.remove(self, self._entry_from_axes(a))
66+
67+
def bubble(self, a):
68+
return Stack.bubble(self, self._entry_from_axes(a))
69+
70+
def add(self, key, a):
71+
"""
72+
Add Axes *a*, with key *key*, to the stack, and return the stack.
73+
74+
If *a* is already on the stack, don't add it again, but
75+
return *None*.
76+
"""
77+
# All the error checking may be unnecessary; but this method
78+
# is called so seldom that the overhead is negligible.
79+
if not isinstance(a, Axes):
80+
raise ValueError("second argument, %s, is not an Axes" % a)
81+
try:
82+
hash(key)
83+
except TypeError:
84+
raise ValueError("first argument, %s, is not a valid key" % key)
85+
if a in self:
86+
return None
87+
return Stack.push(self, (key, a))
88+
89+
def __call__(self):
90+
if not len(self._elements):
91+
return self._default
92+
else:
93+
return self._elements[self._pos][1]
94+
95+
def __contains__(self, a):
96+
return a in self.as_list()
97+
4098
class SubplotParams:
4199
"""
42100
A class to hold the parameters for a subplot
@@ -202,11 +260,15 @@ def __init__(self,
202260

203261
self.subplotpars = subplotpars
204262

205-
self._axstack = Stack() # maintain the current axes
206-
self.axes = []
263+
self._axstack = AxesStack() # track all figure axes and current axes
207264
self.clf()
208265
self._cachedRenderer = None
209266

267+
def _get_axes(self):
268+
return self._axstack.as_list()
269+
270+
axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure")
271+
210272
def _get_dpi(self):
211273
return self._dpi
212274
def _set_dpi(self, dpi):
@@ -523,15 +585,9 @@ def set_frameon(self, b):
523585

524586
def delaxes(self, a):
525587
'remove a from the figure and update the current axes'
526-
self.axes.remove(a)
527588
self._axstack.remove(a)
528-
keys = []
529-
for key, thisax in self._seen.items():
530-
if a==thisax: del self._seen[key]
531589
for func in self._axobservers: func(self)
532590

533-
534-
535591
def _make_key(self, *args, **kwargs):
536592
'make a hashable key out of args and kwargs'
537593

@@ -595,8 +651,8 @@ def add_axes(self, *args, **kwargs):
595651

596652
key = self._make_key(*args, **kwargs)
597653

598-
if key in self._seen:
599-
ax = self._seen[key]
654+
ax = self._axstack.get(key)
655+
if ax is not None:
600656
self.sca(ax)
601657
return ax
602658

@@ -618,10 +674,9 @@ def add_axes(self, *args, **kwargs):
618674

619675
a = projection_factory(projection, self, rect, **kwargs)
620676

621-
self.axes.append(a)
622-
self._axstack.push(a)
677+
if a not in self._axstack:
678+
self._axstack.add(key, a)
623679
self.sca(a)
624-
self._seen[key] = a
625680
return a
626681

627682
@docstring.dedent_interpd
@@ -675,19 +730,16 @@ def add_subplot(self, *args, **kwargs):
675730
projection_class = get_projection_class(projection)
676731

677732
key = self._make_key(*args, **kwargs)
678-
if key in self._seen:
679-
ax = self._seen[key]
733+
ax = self._axstack.get(key)
734+
if ax is not None:
680735
if isinstance(ax, projection_class):
681736
self.sca(ax)
682737
return ax
683738
else:
684-
self.axes.remove(ax)
685739
self._axstack.remove(ax)
686740

687741
a = subplot_class_factory(projection_class)(self, *args, **kwargs)
688-
self._seen[key] = a
689-
self.axes.append(a)
690-
self._axstack.push(a)
742+
self._axstack.add(key, a)
691743
self.sca(a)
692744
return a
693745

@@ -703,13 +755,12 @@ def clf(self, keep_observers=False):
703755

704756
for ax in tuple(self.axes): # Iterate over the copy.
705757
ax.cla()
706-
self.delaxes(ax) # removes ax from self.axes
758+
self.delaxes(ax) # removes ax from self._axstack
707759

708760
toolbar = getattr(self.canvas, 'toolbar', None)
709761
if toolbar is not None:
710762
toolbar.update()
711763
self._axstack.clear()
712-
self._seen = {}
713764
self.artists = []
714765
self.lines = []
715766
self.patches = []
@@ -975,7 +1026,7 @@ def _gci(self):
9751026
helper for :func:`~matplotlib.pyplot.gci`;
9761027
do not use elsewhere.
9771028
"""
978-
for ax in reversed(self._axstack):
1029+
for ax in reversed(self.axes):
9791030
im = ax._gci()
9801031
if im is not None:
9811032
return im

0 commit comments

Comments
 (0)