diff --git a/doc/api/api_changes/2014-12-12_axes_property.rst b/doc/api/api_changes/2014-12-12_axes_property.rst new file mode 100644 index 000000000000..3e98a5cbd4a5 --- /dev/null +++ b/doc/api/api_changes/2014-12-12_axes_property.rst @@ -0,0 +1,16 @@ +Prevent moving artists between Axes, Property-ify Artist.axes, deprecate Artist.{get,set}_axes +`````````````````````````````````````````````````````````````````````````````````````````````` + +The reason this was done was to prevent adding an Artist that is +already associated with an Axes to be moved/added to a different Axes. +This was never supported as it causes havoc with the transform stack. +The apparent support for this (as it did not raise an exception) was +the source of multiple bug reports and questions on SO. + +For almost all use-cases, the assignment of the axes to an artist should be +taken care of by the axes as part of the ``Axes.add_*`` method, hence the +deprecation {get,set}_axes. + +Removing the ``set_axes`` method will also remove the 'axes' line from +the ACCEPTS kwarg tables (assuming that the removal date gets here +before that gets overhauled). diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index af9155c40dd6..7fb492367f1d 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -8,6 +8,7 @@ import inspect import matplotlib import matplotlib.cbook as cbook +from matplotlib.cbook import mplDeprecation from matplotlib import docstring, rcParams from .transforms import (Bbox, IdentityTransform, TransformedBbox, TransformedPath, Transform) @@ -77,6 +78,7 @@ class Artist(object): zorder = 0 def __init__(self): + self._axes = None self.figure = None self._transform = None @@ -175,17 +177,43 @@ def set_axes(self, axes): Set the :class:`~matplotlib.axes.Axes` instance in which the artist resides, if any. + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. + ACCEPTS: an :class:`~matplotlib.axes.Axes` instance """ + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) self.axes = axes def get_axes(self): """ Return the :class:`~matplotlib.axes.Axes` instance the artist - resides in, or *None* + resides in, or *None*. + + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. """ + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) return self.axes + @property + def axes(self): + """ + The :class:`~matplotlib.axes.Axes` instance the artist + resides in, or *None*. + """ + return self._axes + + @axes.setter + def axes(self, new_axes): + if self._axes is not None and new_axes != self._axes: + raise ValueError("Can not reset the axes. You are " + "probably trying to re-use an artist " + "in more than one Axes which is not " + "supported") + self._axes = new_axes + return new_axes + def get_window_extent(self, renderer): """ Get the axes bounding box in display space. @@ -751,10 +779,13 @@ def update(self, props): changed = False for k, v in six.iteritems(props): - func = getattr(self, 'set_' + k, None) - if func is None or not six.callable(func): - raise AttributeError('Unknown property %s' % k) - func(v) + if k in ['axes']: + setattr(self, k, v) + else: + func = getattr(self, 'set_' + k, None) + if func is None or not six.callable(func): + raise AttributeError('Unknown property %s' % k) + func(v) changed = True self.eventson = store if changed: @@ -1328,3 +1359,6 @@ def kwdoc(a): return '\n'.join(ArtistInspector(a).pprint_setters(leadingspace=2)) docstring.interpd.update(Artist=kwdoc(Artist)) + +_get_axes_msg = """This has been deprecated in mpl 1.5, please use the +axes property. A removal date has not been set.""" diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index eff223f92bb4..03a5e4ed07e4 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -237,7 +237,6 @@ def _makeline(self, x, y, kw, kwargs): # (can't use setdefault because it always evaluates # its second argument) seg = mlines.Line2D(x, y, - axes=self.axes, **kw ) self.set_lineprops(seg, **kwargs) @@ -393,7 +392,8 @@ def __init__(self, fig, rect, else: self._position = mtransforms.Bbox.from_bounds(*rect) self._originalPosition = self._position.frozen() - self.set_axes(self) + # self.set_axes(self) + self.axes = self self.set_aspect('auto') self._adjustable = 'box' self.set_anchor('C') @@ -775,7 +775,7 @@ def _set_artist_props(self, a): if not a.is_transform_set(): a.set_transform(self.transData) - a.set_axes(self) + a.axes = self def _gen_axes_patch(self): """ @@ -1432,7 +1432,7 @@ def add_artist(self, a): Returns the artist. """ - a.set_axes(self) + a.axes = self self.artists.append(a) self._set_artist_props(a) a.set_clip_path(self.patch) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 34bfc30c503e..85ad1e6ba48a 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -298,7 +298,11 @@ def __init__(self, Defaults to rc ``figure.autolayout``. """ Artist.__init__(self) - + # remove the non-figure artist _axes property + # as it makes no sense for a figure to be _in_ an axes + # this is used by the property methods in the artist base class + # which are over-ridden in this class + del self._axes self.callbacks = cbook.CallbackRegistry() if figsize is None: diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index e5c0fc47a05a..e0536f5bfd44 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -304,7 +304,7 @@ def __init__(self, parent, handles, labels, if isinstance(parent, Axes): self.isaxes = True - self.set_axes(parent) + self.axes = parent self.set_figure(parent.figure) elif isinstance(parent, Figure): self.isaxes = False @@ -391,7 +391,9 @@ def _set_artist_props(self, a): """ a.set_figure(self.figure) if self.isaxes: - a.set_axes(self.axes) + # a.set_axes(self.axes) + a.axes = self.axes + a.set_transform(self.get_transform()) def _set_loc(self, loc): diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 87bb99896f7d..dd9be576f1b9 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -536,15 +536,17 @@ def get_window_extent(self, renderer): bbox = bbox.padded(ms) return bbox - def set_axes(self, ax): - Artist.set_axes(self, ax) + @Artist.axes.setter + def axes(self, ax): + # call the set method from the base-class property + Artist.axes.fset(self, ax) + # connect unit-related callbacks if ax.xaxis is not None: self._xcid = ax.xaxis.callbacks.connect('units', self.recache_always) if ax.yaxis is not None: self._ycid = ax.yaxis.callbacks.connect('units', self.recache_always) - set_axes.__doc__ = Artist.set_axes.__doc__ def set_data(self, *args): """ diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index e4e709ffc0f0..043ec4c25ad7 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -407,14 +407,12 @@ def test_null_collection_datalim(): def test_add_collection(): # Test if data limits are unchanged by adding an empty collection. # Github issue #1490, pull #1497. - ax = plt.axes() plt.figure() - ax2 = plt.axes() - coll = ax2.scatter([0, 1], [0, 1]) + ax = plt.axes() + coll = ax.scatter([0, 1], [0, 1]) ax.add_collection(coll) bounds = ax.dataLim.bounds - coll = ax2.scatter([], []) - ax.add_collection(coll) + coll = ax.scatter([], []) assert_equal(ax.dataLim.bounds, bounds) diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index feb4a0316e8c..8daa198f7f0c 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -58,20 +58,21 @@ def __call__(self, ax, renderer): return bb + from . import axes_size as Size class AnchoredSizeLocator(AnchoredLocatorBase): def __init__(self, bbox_to_anchor, x_size, y_size, loc, borderpad=0.5, bbox_transform=None): - self.axes = None - self.x_size = Size.from_any(x_size) - self.y_size = Size.from_any(y_size) super(AnchoredSizeLocator, self).__init__(bbox_to_anchor, None, loc, borderpad=borderpad, bbox_transform=bbox_transform) + self.x_size = Size.from_any(x_size) + self.y_size = Size.from_any(y_size) + def get_extent(self, renderer): x, y, w, h = self.get_bbox_to_anchor().bounds
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: