|
From: <ef...@us...> - 2010-09-05 19:33:49
|
Revision: 8681
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8681&view=rev
Author: efiring
Date: 2010-09-05 19:33:42 +0000 (Sun, 05 Sep 2010)
Log Message:
-----------
[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.
Modified Paths:
--------------
trunk/matplotlib/lib/matplotlib/artist.py
trunk/matplotlib/lib/matplotlib/figure.py
Modified: trunk/matplotlib/lib/matplotlib/artist.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/artist.py 2010-09-05 00:43:25 UTC (rev 8680)
+++ trunk/matplotlib/lib/matplotlib/artist.py 2010-09-05 19:33:42 UTC (rev 8681)
@@ -92,7 +92,11 @@
self.eventson = False # fire events only if eventson
self._oid = 0 # an observer id
self._propobservers = {} # a dict from oids to funcs
- self.axes = None
+ try:
+ self.axes = None
+ except AttributeError:
+ # Handle self.axes as a read-only property, as in Figure.
+ pass
self._remove_method = None
self._url = None
self._gid = None
Modified: trunk/matplotlib/lib/matplotlib/figure.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/figure.py 2010-09-05 00:43:25 UTC (rev 8680)
+++ trunk/matplotlib/lib/matplotlib/figure.py 2010-09-05 19:33:42 UTC (rev 8681)
@@ -37,6 +37,64 @@
docstring.interpd.update(projection_names = get_projection_names())
+class AxesStack(Stack):
+ """
+ Specialization of the Stack to handle all
+ tracking of Axes in a Figure. This requires storing
+ key, axes pairs. The key is based on the args and kwargs
+ used in generating the Axes.
+ """
+ def as_list(self):
+ """
+ Return a list of the Axes instances that have been added to the figure
+ """
+ return [a for k, a in self._elements]
+
+ def get(self, key):
+ """
+ Return the Axes instance that was added with *key*.
+ If it is not present, return None.
+ """
+ return dict(self._elements).get(key)
+
+ def _entry_from_axes(self, e):
+ k = dict([(a, k) for (k, a) in self._elements])[e]
+ return k, e
+
+ def remove(self, a):
+ Stack.remove(self, self._entry_from_axes(a))
+
+ def bubble(self, a):
+ return Stack.bubble(self, self._entry_from_axes(a))
+
+ def add(self, key, a):
+ """
+ Add Axes *a*, with key *key*, to the stack, and return the stack.
+
+ If *a* is already on the stack, don't add it again, but
+ return *None*.
+ """
+ # All the error checking may be unnecessary; but this method
+ # is called so seldom that the overhead is negligible.
+ if not isinstance(a, Axes):
+ raise ValueError("second argument, %s, is not an Axes" % a)
+ try:
+ hash(key)
+ except TypeError:
+ raise ValueError("first argument, %s, is not a valid key" % key)
+ if a in self:
+ return None
+ return Stack.push(self, (key, a))
+
+ def __call__(self):
+ if not len(self._elements):
+ return self._default
+ else:
+ return self._elements[self._pos][1]
+
+ def __contains__(self, a):
+ return a in self.as_list()
+
class SubplotParams:
"""
A class to hold the parameters for a subplot
@@ -202,11 +260,15 @@
self.subplotpars = subplotpars
- self._axstack = Stack() # maintain the current axes
- self.axes = []
+ self._axstack = AxesStack() # track all figure axes and current axes
self.clf()
self._cachedRenderer = None
+ 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
def _set_dpi(self, dpi):
@@ -523,15 +585,9 @@
def delaxes(self, a):
'remove a from the figure and update the current axes'
- self.axes.remove(a)
self._axstack.remove(a)
- keys = []
- for key, thisax in self._seen.items():
- if a==thisax: del self._seen[key]
for func in self._axobservers: func(self)
-
-
def _make_key(self, *args, **kwargs):
'make a hashable key out of args and kwargs'
@@ -595,8 +651,8 @@
key = self._make_key(*args, **kwargs)
- if key in self._seen:
- ax = self._seen[key]
+ ax = self._axstack.get(key)
+ if ax is not None:
self.sca(ax)
return ax
@@ -618,10 +674,9 @@
a = projection_factory(projection, self, rect, **kwargs)
- self.axes.append(a)
- self._axstack.push(a)
+ if a not in self._axstack:
+ self._axstack.add(key, a)
self.sca(a)
- self._seen[key] = a
return a
@docstring.dedent_interpd
@@ -675,19 +730,16 @@
projection_class = get_projection_class(projection)
key = self._make_key(*args, **kwargs)
- if key in self._seen:
- ax = self._seen[key]
+ ax = self._axstack.get(key)
+ if ax is not None:
if isinstance(ax, projection_class):
self.sca(ax)
return ax
else:
- self.axes.remove(ax)
self._axstack.remove(ax)
a = subplot_class_factory(projection_class)(self, *args, **kwargs)
- self._seen[key] = a
- self.axes.append(a)
- self._axstack.push(a)
+ self._axstack.add(key, a)
self.sca(a)
return a
@@ -703,13 +755,12 @@
for ax in tuple(self.axes): # Iterate over the copy.
ax.cla()
- self.delaxes(ax) # removes ax from self.axes
+ self.delaxes(ax) # removes ax from self._axstack
toolbar = getattr(self.canvas, 'toolbar', None)
if toolbar is not None:
toolbar.update()
self._axstack.clear()
- self._seen = {}
self.artists = []
self.lines = []
self.patches = []
@@ -975,7 +1026,7 @@
helper for :func:`~matplotlib.pyplot.gci`;
do not use elsewhere.
"""
- for ax in reversed(self._axstack):
+ for ax in reversed(self.axes):
im = ax._gci()
if im is not None:
return im
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|