diff --git a/doc/api/next_api_changes/2017-12-06-KL.rst b/doc/api/next_api_changes/2017-12-06-KL.rst new file mode 100644 index 000000000000..1321454ebbff --- /dev/null +++ b/doc/api/next_api_changes/2017-12-06-KL.rst @@ -0,0 +1,26 @@ +Change return value of Axes.get_shared_[x,y,z]_axes() +----------------------------------------------------- + +The method `matplotlib.Axes.get_shared_x_axes` (and y and z) used to return `~.cbook.Grouper` objects. +Now it returns a `~.weakref.WeakSet` object. + +Workarounds: +* If the intention is to get siblings as previous then the WeakSet contains all the siblings. +An example:: + + sharedx = ax.get_shared_x_axes().get_siblings() + # is now + sharedx = list(ax.get_shared_x_axes()) + +* If the intention was to use `join` then there is a new share axes method. An example:: + + ax1.get_shared_x_axes().join(ax1, ax2) + # is now + ax1.share_x_axes(ax2) + +* If the intention was to check if two elements are in the same group then use the `in` operator. An example:: + + ax1.get_shared_x_axes().joined(ax1, ax2) + # is now + ax2 in ax1.get_shared_x_axes() + diff --git a/doc/api/next_api_changes/remove_old_share_axes_after_creation.rst b/doc/api/next_api_changes/remove_old_share_axes_after_creation.rst new file mode 100644 index 000000000000..067533834e4b --- /dev/null +++ b/doc/api/next_api_changes/remove_old_share_axes_after_creation.rst @@ -0,0 +1,8 @@ +Remove share through `matplotlib.Axes.get_shared_{x,y,z}_axes` +-------------------------------------------------------------- + +Previously when different axes are created with different parent/master axes, +the share would still be symmetric and transitive if an unconventional +method through `matplotlib.Axes.get_shared_x_axes` +is used to share the axes after creation. With the new sharing mechanism +this is no longer possible. diff --git a/doc/users/next_whats_new/2017-12-05_share_unshare_axes_after_creation.rst b/doc/users/next_whats_new/2017-12-05_share_unshare_axes_after_creation.rst new file mode 100644 index 000000000000..74f88b1762b1 --- /dev/null +++ b/doc/users/next_whats_new/2017-12-05_share_unshare_axes_after_creation.rst @@ -0,0 +1,12 @@ +Share and unshare `axes` after creation +--------------------------------------- + +`matplotlib.axes.Axes` have `matplotlib.axes.Axes.unshare_x_axes`, +`matplotlib.axes.Axes.unshare_y_axes`, `matplotlib.axes.Axes.unshare_z_axes` +and `matplotlib.axes.Axes.unshare_axes` methods to unshare axes. +Similiar there are `matplotlib.axes.Axes.share_x_axes`, +`matplotlib.axes.Axes.share_y_axes`, `matplotlib.axes.Axes.share_z_axes` and +`matplotlib.axes.Axes.share_axes` methods to share axes. + +Unshare an axis will decouple the viewlimits for further changes. +Share an axis will couple the viewlimits. \ No newline at end of file diff --git a/examples/mplot3d/share_unshare_3d_axes.py b/examples/mplot3d/share_unshare_3d_axes.py new file mode 100644 index 000000000000..6568ae025292 --- /dev/null +++ b/examples/mplot3d/share_unshare_3d_axes.py @@ -0,0 +1,39 @@ +""" +============================================ +Parametric Curve with Share and Unshare Axes +============================================ + +This example demonstrates plotting a parametric curve in 3D, +and how to share and unshare 3D plot axes. +""" +import matplotlib as mpl +from mpl_toolkits.mplot3d import Axes3D +import numpy as np +import matplotlib.pyplot as plt + +mpl.rcParams['legend.fontsize'] = 10 + +# Prepare arrays x, y, z +theta = np.linspace(-4 * np.pi, 4 * np.pi, 100) +z = np.linspace(-2, 2, 100) +r = z ** 2 + 1 +x = r * np.sin(theta) +y = r * np.cos(theta) + +fig = plt.figure() +ax = fig.add_subplot(311, projection='3d') + +ax.plot(x, y, z, label='parametric curve') +ax.legend() + +ax1 = fig.add_subplot(312) +ax1.plot(range(10)) +ax1.share_axes(ax) + +ax2 = fig.add_subplot(313, projection='3d', sharex=ax) +ax2.plot(x, y, z) + +ax2.unshare_x_axes() +ax2.share_z_axes(ax) + +plt.show() diff --git a/examples/subplots_axes_and_figures/unshare_axis_demo.py b/examples/subplots_axes_and_figures/unshare_axis_demo.py new file mode 100644 index 000000000000..8baa48128fd7 --- /dev/null +++ b/examples/subplots_axes_and_figures/unshare_axis_demo.py @@ -0,0 +1,36 @@ +""" +====================== +Unshare and share axis +====================== + +The example shows how to share and unshare axes after they are created. +""" + +import matplotlib.pyplot as plt +import numpy as np + +t = np.arange(0.01, 5.0, 0.01) +s1 = np.sin(2 * np.pi * t) +s2 = np.exp(-t) +s3 = np.sin(4 * np.pi * t) + +ax1 = plt.subplot(311) +plt.plot(t, s1) + +ax2 = plt.subplot(312) +plt.plot(t, s2) + +ax3 = plt.subplot(313) +plt.plot(t, s3) + +ax1.share_x_axes(ax2) +ax1.share_y_axes(ax2) + +# Share both axes. +ax3.share_axes(ax1) +plt.xlim(0.01, 5.0) + +ax3.unshare_y_axes() +ax2.unshare_x_axes() + +plt.show() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index c201c09b9189..95cfdd03533b 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1,4 +1,6 @@ from collections import OrderedDict +from weakref import WeakSet +import copy import itertools import logging import math @@ -400,8 +402,6 @@ class _AxesBase(martist.Artist): """ name = "rectilinear" - _shared_x_axes = cbook.Grouper() - _shared_y_axes = cbook.Grouper() _twinned_axes = cbook.Grouper() def __str__(self): @@ -482,12 +482,18 @@ def __init__(self, fig, rect, self._aspect = 'auto' self._adjustable = 'box' self._anchor = 'C' + #Adding yourself to shared xy axes. Reflexive. + self._shared_x_axes = WeakSet([self]) + self._shared_y_axes = WeakSet([self]) self._sharex = sharex self._sharey = sharey + if sharex is not None: - self._shared_x_axes.join(self, sharex) + self.share_x_axes(sharex) + if sharey is not None: - self._shared_y_axes.join(self, sharey) + self.share_y_axes(sharey) + self.set_label(label) self.set_figure(fig) @@ -571,10 +577,14 @@ def __getstate__(self): state.pop('_layoutbox') state.pop('_poslayoutbox') + state['_shared_x_axes'] = list(self._shared_x_axes) + state['_shared_y_axes'] = list(self._shared_y_axes) return state def __setstate__(self, state): self.__dict__ = state + self._shared_x_axes = WeakSet(state['_shared_x_axes']) + self._shared_y_axes = WeakSet(state['_shared_y_axes']) self._stale = True self._layoutbox = None self._poslayoutbox = None @@ -1102,8 +1112,6 @@ def cla(self): self.xaxis.set_clip_path(self.patch) self.yaxis.set_clip_path(self.patch) - self._shared_x_axes.clean() - self._shared_y_axes.clean() if self._sharex: self.xaxis.set_visible(xaxis_visible) self.patch.set_visible(patch_visible) @@ -1280,8 +1288,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): if not (isinstance(aspect, str) and aspect in ('equal', 'auto')): aspect = float(aspect) # raise ValueError if necessary if share: - axes = set(self._shared_x_axes.get_siblings(self) - + self._shared_y_axes.get_siblings(self)) + axes = set(self._shared_x_axes | self._shared_y_axes) else: axes = [self] for ax in axes: @@ -1335,8 +1342,7 @@ def set_adjustable(self, adjustable, share=False): if adjustable not in ('box', 'datalim', 'box-forced'): raise ValueError("argument must be 'box', or 'datalim'") if share: - axes = set(self._shared_x_axes.get_siblings(self) - + self._shared_y_axes.get_siblings(self)) + axes = set(self._shared_x_axes | self._shared_y_axes) else: axes = [self] for ax in axes: @@ -1403,8 +1409,7 @@ def set_anchor(self, anchor, share=False): raise ValueError('argument must be among %s' % ', '.join(mtransforms.Bbox.coefs)) if share: - axes = set(self._shared_x_axes.get_siblings(self) - + self._shared_y_axes.get_siblings(self)) + axes = set(self._shared_x_axes | self._shared_y_axes) else: axes = [self] for ax in axes: @@ -1556,8 +1561,8 @@ def apply_aspect(self, position=None): xm = 0 ym = 0 - shared_x = self in self._shared_x_axes - shared_y = self in self._shared_y_axes + shared_x = self.is_sharing_x_axes() + shared_y = self.is_sharing_y_axes() # Not sure whether we need this check: if shared_x and shared_y: raise RuntimeError("adjustable='datalim' is not allowed when both" @@ -2389,8 +2394,7 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, if not (scale and autoscaleon): return # nothing to do... - shared = shared_axes.get_siblings(self) - dl = [ax.dataLim for ax in shared] + dl = [ax.dataLim for ax in shared_axes] # ignore non-finite data limits if good limits exist finite_dl = [d for d in dl if np.isfinite(d).all()] if len(finite_dl): @@ -3126,7 +3130,7 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, if emit: self.callbacks.process('xlim_changed', self) # Call all of the other x-axes that are shared with this one - for other in self._shared_x_axes.get_siblings(self): + for other in self._shared_x_axes: if other is not self: other.set_xlim(self.viewLim.intervalx, emit=False, auto=auto) @@ -3165,8 +3169,7 @@ def set_xscale(self, value, **kwargs): matplotlib.scale.LogisticTransform : logit transform """ - g = self.get_shared_x_axes() - for ax in g.get_siblings(self): + for ax in self._shared_x_axes: ax.xaxis._set_scale(value, **kwargs) ax._update_transScale() ax.stale = True @@ -3459,7 +3462,7 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, if emit: self.callbacks.process('ylim_changed', self) # Call all of the other y-axes that are shared with this one - for other in self._shared_y_axes.get_siblings(self): + for other in self._shared_y_axes: if other is not self: other.set_ylim(self.viewLim.intervaly, emit=False, auto=auto) @@ -3498,8 +3501,7 @@ def set_yscale(self, value, **kwargs): matplotlib.scale.LogisticTransform : logit transform """ - g = self.get_shared_y_axes() - for ax in g.get_siblings(self): + for ax in self._shared_y_axes: ax.yaxis._set_scale(value, **kwargs) ax._update_transScale() ax.stale = True @@ -4231,9 +4233,100 @@ def twiny(self): return ax2 def get_shared_x_axes(self): - """Return a reference to the shared axes Grouper object for x axes.""" - return self._shared_x_axes + """Return a copy of the shared axes Weakset object for x axes""" + return WeakSet(self._shared_x_axes) def get_shared_y_axes(self): - """Return a reference to the shared axes Grouper object for y axes.""" - return self._shared_y_axes + """Return a copy of the shared axes Weakset object for y axes""" + return WeakSet(self._shared_y_axes) + + def is_sharing_x_axes(self): + return len(self.get_shared_x_axes()) > 1 + + def is_sharing_y_axes(self): + return len(self.get_shared_y_axes()) > 1 + + def _unshare_axes(self, shared_axes): + for ax in getattr(self, "_shared_{}_axes".format(shared_axes)): + parent = getattr(ax, "_share{}".format(shared_axes)) + + if parent is self: + setattr(ax, "_share{}".format(shared_axes), None) + self._copy_axis_major_minor( + getattr(ax, "{}axis".format(shared_axes))) + + getattr(self, "_shared_{}_axes".format(shared_axes)).remove(self) + setattr(self, "_shared_{}_axes".format(shared_axes), WeakSet([self])) + setattr(self, "_share{}".format(shared_axes), None) + + self._copy_axis_major_minor( + getattr(self, "{}axis".format(shared_axes))) + + @staticmethod + def _copy_axis_major_minor(axis): + major = axis.major + minor = axis.minor + + axis.major = copy.deepcopy(major) + axis.minor = copy.deepcopy(minor) + + axis.major.set_axis(axis) + axis.minor.set_axis(axis) + + def unshare_x_axes(self): + """ Unshare x axis. """ + self._unshare_axes("x") + + def unshare_y_axes(self): + """ Unshare y axis. """ + self._unshare_axes("y") + + def unshare_axes(self): + """ Unshare both x and y axes. """ + self.unshare_x_axes() + self.unshare_y_axes() + + def _share_axes(self, axes, shared_axes): + if not iterable(axes): + axes = [axes] + + shared = getattr(self, "_shared_{}_axes".format(shared_axes)) + for ax in axes: + shared |= getattr(ax, "_shared_{}_axes".format(shared_axes)) + + for ax in shared: + setattr(ax, "_shared_{}_axes".format(shared_axes), shared) + + def share_x_axes(self, axes): + """ + Share x axis. + + Parameters + ---------- + axes: Axes + Axes to share. + """ + self._share_axes(axes, 'x') + + def share_y_axes(self, axes): + """ + Share y axis. + + Parameters + ---------- + axes: Axes + Axes to share. + """ + self._share_axes(axes, 'y') + + def share_axes(self, axes): + """ + Share both x and y axes. + + Parameters + ---------- + axes: Axes + Axes to share. + """ + self.share_x_axes(axes) + self.share_y_axes(axes) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 8ca2209b3a0f..83ad56b50626 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -7,6 +7,7 @@ import warnings import numpy as np +import copy from matplotlib import rcParams import matplotlib.artist as artist @@ -651,6 +652,31 @@ class Ticker(object): locator = None formatter = None + def __deepcopy__(self, memodict={}): + """ + Perform deep copy by disable axis attribute from + locator and formatter. + + """ + axisLocator = self.locator.axis + axisFormatter = self.formatter.axis + self.locator.axis = None + self.formatter.axis = None + + cls = self.__class__ + result = cls.__new__(cls) + result.locator = copy.deepcopy(self.locator) + result.formatter = copy.deepcopy(self.formatter) + + self.locator.axis = axisLocator + self.formatter.axis = axisFormatter + + return result + + def set_axis(self, axis): + self.locator.axis = axis + self.formatter.axis = axis + class _LazyTickList(object): """ diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 54728fe05d6b..ffa98ed4e147 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2922,9 +2922,9 @@ def release_zoom(self, event): twinx, twiny = False, False if last_a: for la in last_a: - if a.get_shared_x_axes().joined(a, la): + if la in a.get_shared_x_axes(): twinx = True - if a.get_shared_y_axes().joined(a, la): + if la in a.get_shared_y_axes(): twiny = True last_a.append(a) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 803ce1c02c26..d8b29d435de6 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -934,9 +934,9 @@ def _release(self, event): twinx, twiny = False, False if last_a: for la in last_a: - if a.get_shared_x_axes().joined(a, la): + if la in a.get_shared_x_axes(): twinx = True - if a.get_shared_y_axes().joined(a, la): + if la in a.get_shared_y_axes(): twiny = True last_a.append(a) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 81ecfe9781bd..514648d82385 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1395,21 +1395,17 @@ def _reset_loc_form(axis): axis.set_minor_formatter(axis.get_minor_formatter()) axis.set_minor_locator(axis.get_minor_locator()) - def _break_share_link(ax, grouper): - siblings = grouper.get_siblings(ax) - if len(siblings) > 1: - grouper.remove(ax) - for last_ax in siblings: - if ax is not last_ax: - return last_ax - return None - self.delaxes(ax) - last_ax = _break_share_link(ax, ax._shared_y_axes) + + shared_y_axes = ax._shared_y_axes + shared_x_axes = ax._shared_x_axes + ax.unshare_axes() + + last_ax = next((a for a in shared_y_axes if a is not ax), None) if last_ax is not None: _reset_loc_form(last_ax.yaxis) - last_ax = _break_share_link(ax, ax._shared_x_axes) + last_ax = next((a for a in shared_x_axes if a is not ax), None) if last_ax is not None: _reset_loc_form(last_ax.xaxis) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index fda0fe0f362b..50dd78f28f86 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5719,3 +5719,115 @@ def test_tick_padding_tightbbox(): bb2 = ax.get_window_extent(fig.canvas.get_renderer()) assert bb.x0 < bb2.x0 assert bb.y0 < bb2.y0 + + +def test_share_unshare_axes(): + fig = plt.figure() + ax1 = fig.add_subplot(211) + ax2 = fig.add_subplot(212, sharex=ax1, sharey=ax1) + + # Testing unsharing + ax1.unshare_axes() + assert ax2._sharex is None + assert ax2._sharey is None + + sharedx = ax2._shared_x_axes + sharedy = ax2._shared_y_axes + + assert ax1 not in sharedx + assert ax1 not in sharedy + + sharedx = ax1._shared_x_axes + sharedy = ax1._shared_y_axes + + assert ax2 not in sharedx + assert ax2 not in sharedy + + assert not ax1.is_sharing_x_axes() + assert not ax1.is_sharing_y_axes() + + assert not ax2.is_sharing_x_axes() + assert not ax2.is_sharing_y_axes() + + # Testing sharing + ax1.share_x_axes(ax2) + + sharedx = ax1._shared_x_axes + assert ax2 in sharedx + + sharedx = ax2._shared_x_axes + assert ax1 in sharedx + + ax2.share_y_axes(ax1) + + sharedy = ax1._shared_y_axes + assert ax2 in sharedy + + sharedy = ax2._shared_y_axes + assert ax1 in sharedy + + assert ax1.is_sharing_x_axes() + assert ax1.is_sharing_y_axes() + + assert ax2.is_sharing_x_axes() + assert ax2.is_sharing_y_axes() + + +def test_share_unshare_3d_axes(): + fig = plt.figure() + ax1 = fig.add_subplot(211, projection="3d") + ax2 = fig.add_subplot(212, projection="3d", + sharex=ax1, + sharey=ax1, + sharez=ax1) + + # Testing unsharing + ax1.unshare_axes() + assert ax2._sharex is None + assert ax2._sharey is None + assert ax2._sharez is None + + sharedx = ax2._shared_x_axes + sharedy = ax2._shared_y_axes + sharedz = ax2._shared_z_axes + + assert ax1 not in sharedx + assert ax1 not in sharedy + assert ax1 not in sharedz + + sharedx = ax1._shared_x_axes + sharedy = ax1._shared_y_axes + sharedz = ax1._shared_z_axes + + assert ax2 not in sharedx + assert ax2 not in sharedy + assert ax2 not in sharedz + + # Testing sharing + + # x axis + ax1.share_x_axes(ax2) + + sharedx = ax1._shared_x_axes + assert ax2 in sharedx + + sharedx = ax2._shared_x_axes + assert ax1 in sharedx + + # y axis + ax2.share_y_axes(ax1) + + sharedy = ax1._shared_y_axes + assert ax2 in sharedy + + sharedy = ax2._shared_y_axes + assert ax1 in sharedy + + # z axis + ax1.share_z_axes(ax2) + + sharedz = ax2._shared_z_axes + assert ax1 in sharedz + + sharedz = ax1._shared_z_axes + assert ax2 in sharedz diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 719dc7356fc8..66d461e7d6f1 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -147,6 +147,45 @@ def test_polar(): plt.draw() +def test_sharing_axes(): + fig = plt.figure() + ax1 = fig.add_subplot(211) + ax2 = fig.add_subplot(212, sharex=ax1, sharey=ax1) + + pf = pickle.dumps(fig) + fig1 = pickle.loads(pf) + + ax = fig1.axes + + assert ax[0] in ax[1]._shared_x_axes + assert ax[0] in ax[1]._shared_y_axes + + assert ax[1] in ax[0]._shared_x_axes + assert ax[1] in ax[0]._shared_y_axes + + +def test_sharing_3d_axes(): + fig = plt.figure() + ax1 = fig.add_subplot(211, projection="3d") + ax2 = fig.add_subplot(212, projection="3d", + sharex=ax1, + sharey=ax1, + sharez=ax1) + + pf = pickle.dumps(fig) + fig1 = pickle.loads(pf) + + ax = fig1.axes + + assert ax[0] in ax[1]._shared_x_axes + assert ax[0] in ax[1]._shared_y_axes + assert ax[0] in ax[1]._shared_z_axes + + assert ax[1] in ax[0]._shared_x_axes + assert ax[1] in ax[0]._shared_y_axes + assert ax[1] in ax[0]._shared_z_axes + + class TransformBlob(object): def __init__(self): self.identity = mtransforms.IdentityTransform() diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index 9a1a5e7f7a8d..76ef9ecca089 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -19,8 +19,10 @@ def check_shared(axs, x_shared, y_shared): enumerate(zip("xy", [x_shared, y_shared]))): if i2 <= i1: continue + share = getattr(ax1, "_shared_{}_axes".format(name)) + share = ax2 in share assert \ - (getattr(axs[0], "_shared_{}_axes".format(name)).joined(ax1, ax2) + (share == shared[i1, i2]), \ "axes %i and %i incorrectly %ssharing %s axis" % ( i1, i2, "not " if shared[i1, i2] else "", name) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 16bb0ebec65e..937300620d1d 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -13,6 +13,8 @@ from collections import defaultdict import math import warnings +import copy +from weakref import WeakSet import numpy as np @@ -44,7 +46,6 @@ class Axes3D(Axes): 3D axes object. """ name = '3d' - _shared_z_axes = cbook.Grouper() def __init__( self, fig, rect=None, *args, @@ -90,10 +91,11 @@ def __init__( self.view_init(self.initial_elev, self.initial_azim) self._ready = 0 + self._shared_z_axes = WeakSet([self]) self._sharez = sharez + if sharez is not None: - self._shared_z_axes.join(self, sharez) - self._adjustable = 'datalim' + self.share_z_axes(sharez) super().__init__(fig, rect, frameon=True, *args, **kwargs) # Disable drawing of axes by base class @@ -125,6 +127,17 @@ def __init__( self.figure.add_axes(self) + def __getstate__(self): + state = super(Axes3D, self).__getstate__() + state['_shared_z_axes'] = list(self._shared_z_axes) + + return state + + def __setstate__(self, state): + self.__dict__ = state + + self._shared_z_axes = WeakSet(state['_shared_z_axes']) + def set_axis_off(self): self._axis3don = False self.stale = True @@ -528,7 +541,6 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, _tight = self._tight = bool(tight) if scalex and self._autoscaleXon: - self._shared_x_axes.clean() x0, x1 = self.xy_dataLim.intervalx xlocator = self.xaxis.get_major_locator() try: @@ -545,7 +557,6 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, self.set_xbound(x0, x1) if scaley and self._autoscaleYon: - self._shared_y_axes.clean() y0, y1 = self.xy_dataLim.intervaly ylocator = self.yaxis.get_major_locator() try: @@ -562,7 +573,6 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True, self.set_ybound(y0, y1) if scalez and self._autoscaleZon: - self._shared_z_axes.clean() z0, z1 = self.zz_dataLim.intervalx zlocator = self.zaxis.get_major_locator() try: @@ -640,7 +650,7 @@ def set_xlim3d(self, left=None, right=None, emit=True, auto=False, if emit: self.callbacks.process('xlim_changed', self) # Call all of the other x-axes that are shared with this one - for other in self._shared_x_axes.get_siblings(self): + for other in self._shared_x_axes: if other is not self: other.set_xlim(self.xy_viewLim.intervalx, emit=False, auto=auto) @@ -698,7 +708,7 @@ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, if emit: self.callbacks.process('ylim_changed', self) # Call all of the other y-axes that are shared with this one - for other in self._shared_y_axes.get_siblings(self): + for other in self._shared_y_axes: if other is not self: other.set_ylim(self.xy_viewLim.intervaly, emit=False, auto=auto) @@ -756,7 +766,7 @@ def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False, if emit: self.callbacks.process('zlim_changed', self) # Call all of the other y-axes that are shared with this one - for other in self._shared_z_axes.get_siblings(self): + for other in self._shared_z_axes: if other is not self: other.set_zlim(self.zz_viewLim.intervalx, emit=False, auto=auto) @@ -1105,12 +1115,14 @@ def cla(self): super().cla() self.zaxis.cla() - if self._sharez is not None: - self.zaxis.major = self._sharez.zaxis.major - self.zaxis.minor = self._sharez.zaxis.minor - z0, z1 = self._sharez.get_zlim() + sharez = self._sharez + + if sharez is not None: + self.zaxis.major = sharez.zaxis.major + self.zaxis.minor = sharez.zaxis.minor + z0, z1 = sharez.get_zlim() self.set_zlim(z0, z1, emit=False, auto=None) - self.zaxis._set_scale(self._sharez.zaxis.get_scale()) + self.zaxis._set_scale(sharez.zaxis.get_scale()) else: self.zaxis._set_scale('linear') try: @@ -2885,6 +2897,40 @@ def permutation_matrices(n): return polygons + def unshare_z_axes(self): + """ Unshare z axis. """ + self._unshare_axes("z") + + def unshare_axes(self): + """ Unshare x, y and z axes. """ + self.unshare_x_axes() + self.unshare_y_axes() + self.unshare_z_axes() + + def share_z_axes(self, axes): + """ + Share z axis. + + Parameters + ---------- + axes: Axes + Axes to share. + """ + self._share_axes(axes, 'z') + + def share_axes(self, axes): + """ + Share x, y, z axes. + + Parameters + ---------- + axes: Axes + Axes to share. + """ + self.share_x_axes(axes) + self.share_y_axes(axes) + self.share_z_axes(axes) + def get_test_data(delta=0.05): '''