diff --git a/lib/matplotlib/_tight_layout.py b/lib/matplotlib/_tight_layout.py index 81465f9b5db6..3c5be2d70dc4 100644 --- a/lib/matplotlib/_tight_layout.py +++ b/lib/matplotlib/_tight_layout.py @@ -223,8 +223,15 @@ def get_subplotspec_list(axes_list, grid_spec=None): subplotspec_list = [] for ax in axes_list: axes_or_locator = ax.get_axes_locator() + if axes_or_locator is None: axes_or_locator = ax + else: + for a in ax._twinned_axes.get_siblings(ax): + if a != ax and hasattr(a, "get_subplotspec"): + axes_or_locator = a.get_axes_locator() + if axes_or_locator is None: + axes_or_locator = a if hasattr(axes_or_locator, "get_subplotspec"): subplotspec = axes_or_locator.get_subplotspec() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f1ec9406ea6b..8ae605e5bd8c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -366,6 +366,80 @@ def inset_axes(self, bounds, *, transform=None, zorder=5, **kwargs): return inset_ax + def _make_twin_axes(self, *args, **kwargs): + """Make a twinx Axes of self. This is used for twinx and twiny.""" + if 'sharex' in kwargs and 'sharey' in kwargs: + raise ValueError("Twinned Axes may share only one axis") + ax2 = self.figure.add_axes( + self.get_position(True), *args, **kwargs, + axes_locator=_TransformedBoundsLocator( + [0, 0, 1, 1], self.transAxes)) + axes_locator = _TransformedBoundsLocator([0, 0, 1, 1], self.transAxes) + ax2.set_axes_locator(axes_locator) + self.set_adjustable('datalim') + ax2.set_adjustable('datalim') + self._twinned_axes.join(self, ax2) + return ax2 + + def twinx(self): + """ + Create a twin Axes sharing the xaxis. + + Create a new Axes with an invisible x-axis and an independent + y-axis positioned opposite to the original one (i.e. at right). The + x-axis autoscale setting will be inherited from the original + Axes. To ensure that the tick marks of both y-axes align, see + `~matplotlib.ticker.LinearLocator`. + + Returns + ------- + Axes + The newly created Axes instance + + Notes + ----- + For those who are 'picking' artists while using twinx, pick + events are only called for the artists in the top-most Axes. + """ + ax2 = self._make_twin_axes(sharex=self) + ax2.yaxis.tick_right() + ax2.yaxis.set_label_position('right') + ax2.yaxis.set_offset_position('right') + ax2.set_autoscalex_on(self.get_autoscalex_on()) + self.yaxis.tick_left() + ax2.xaxis.set_visible(False) + ax2.patch.set_visible(False) + return ax2 + + def twiny(self): + """ + Create a twin Axes sharing the yaxis. + + Create a new Axes with an invisible y-axis and an independent + x-axis positioned opposite to the original one (i.e. at top). The + y-axis autoscale setting will be inherited from the original Axes. + To ensure that the tick marks of both x-axes align, see + `~matplotlib.ticker.LinearLocator`. + + Returns + ------- + Axes + The newly created Axes instance + + Notes + ----- + For those who are 'picking' artists while using twiny, pick + events are only called for the artists in the top-most Axes. + """ + ax2 = self._make_twin_axes(sharey=self) + ax2.xaxis.tick_top() + ax2.xaxis.set_label_position('top') + ax2.set_autoscaley_on(self.get_autoscaley_on()) + self.xaxis.tick_bottom() + ax2.yaxis.set_visible(False) + ax2.patch.set_visible(False) + return ax2 + @_docstring.dedent_interpd def indicate_inset(self, bounds, inset_ax=None, *, transform=None, facecolor='none', edgecolor='0.5', alpha=0.5, diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 38f77e537a02..db4c7f2c2ce6 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -4521,79 +4521,6 @@ def get_tightbbox(self, renderer, call_axes_locator=True, return mtransforms.Bbox.union( [b for b in bb if b.width != 0 or b.height != 0]) - def _make_twin_axes(self, *args, **kwargs): - """Make a twinx Axes of self. This is used for twinx and twiny.""" - # Typically, SubplotBase._make_twin_axes is called instead of this. - if 'sharex' in kwargs and 'sharey' in kwargs: - raise ValueError("Twinned Axes may share only one axis") - ax2 = self.figure.add_axes( - self.get_position(True), *args, **kwargs, - axes_locator=_TransformedBoundsLocator( - [0, 0, 1, 1], self.transAxes)) - self.set_adjustable('datalim') - ax2.set_adjustable('datalim') - self._twinned_axes.join(self, ax2) - return ax2 - - def twinx(self): - """ - Create a twin Axes sharing the xaxis. - - Create a new Axes with an invisible x-axis and an independent - y-axis positioned opposite to the original one (i.e. at right). The - x-axis autoscale setting will be inherited from the original - Axes. To ensure that the tick marks of both y-axes align, see - `~matplotlib.ticker.LinearLocator`. - - Returns - ------- - Axes - The newly created Axes instance - - Notes - ----- - For those who are 'picking' artists while using twinx, pick - events are only called for the artists in the top-most Axes. - """ - ax2 = self._make_twin_axes(sharex=self) - ax2.yaxis.tick_right() - ax2.yaxis.set_label_position('right') - ax2.yaxis.set_offset_position('right') - ax2.set_autoscalex_on(self.get_autoscalex_on()) - self.yaxis.tick_left() - ax2.xaxis.set_visible(False) - ax2.patch.set_visible(False) - return ax2 - - def twiny(self): - """ - Create a twin Axes sharing the yaxis. - - Create a new Axes with an invisible y-axis and an independent - x-axis positioned opposite to the original one (i.e. at top). The - y-axis autoscale setting will be inherited from the original Axes. - To ensure that the tick marks of both x-axes align, see - `~matplotlib.ticker.LinearLocator`. - - Returns - ------- - Axes - The newly created Axes instance - - Notes - ----- - For those who are 'picking' artists while using twiny, pick - events are only called for the artists in the top-most Axes. - """ - ax2 = self._make_twin_axes(sharey=self) - ax2.xaxis.tick_top() - ax2.xaxis.set_label_position('top') - ax2.set_autoscaley_on(self.get_autoscaley_on()) - self.xaxis.tick_bottom() - ax2.yaxis.set_visible(False) - ax2.patch.set_visible(False) - return ax2 - def get_shared_x_axes(self): """Return a reference to the shared axes Grouper object for x axes.""" return self._shared_axes["x"] diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 18faf2e9d086..8dc518e87263 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -151,19 +151,6 @@ def _label_outer_yaxis(self, *, check_patch): if self.yaxis.offsetText.get_position()[0] == 1: self.yaxis.offsetText.set_visible(False) - def _make_twin_axes(self, *args, **kwargs): - """Make a twinx axes of self. This is used for twinx and twiny.""" - if 'sharex' in kwargs and 'sharey' in kwargs: - # The following line is added in v2.2 to avoid breaking Seaborn, - # which currently uses this internal API. - if kwargs["sharex"] is not self and kwargs["sharey"] is not self: - raise ValueError("Twinned Axes may share only one axis") - twin = self.figure.add_subplot(self.get_subplotspec(), *args, **kwargs) - self.set_adjustable('datalim') - twin.set_adjustable('datalim') - self._twinned_axes.join(self, twin) - return twin - subplot_class_factory = cbook._make_class_factory( SubplotBase, "{}Subplot", "_axes_class") diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2f5bf4c528f8..30db60d31c70 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5118,6 +5118,18 @@ def test_twin_with_aspect(twin): ax_twin.bbox.extents) +@pytest.mark.parametrize('twin', ('x', 'y')) +def test_twin_moved(twin): + fig, ax = plt.subplots() + ax.set_position([0.2, 0.2, 0.5, 0.5]) + # test twinx or twiny + ax_twin = getattr(ax, 'twin{}'.format(twin))() + fig.draw_without_rendering() + + assert_array_equal(ax.bbox.extents, + ax_twin.bbox.extents) + + def test_relim_visible_only(): x1 = (0., 10.) y1 = (0., 10.)