diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index f89c231815dc..a09e69b378d9 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -4657,6 +4657,20 @@ def _make_twin_axes(self, *args, **kwargs): twin.set_zorder(self.zorder) self._twinned_axes.join(self, twin) + + # If the parent Axes has been manually positioned (set_position() sets + # in_layout=False), the SubplotSpec-based add_subplot(...) path ignores + # that manual position when creating a twin. In that case, explicitly + # copy both the original and active positions to the twin so they start + # aligned. + # + # For layout-managed Axes (in_layout=True), we keep the existing + # SubplotSpec-driven behavior, so layout engines such as tight_layout + # and constrained_layout continue to control positioning. + if not self.get_in_layout(): + twin._set_position(self.get_position(original=True), which="original") + twin._set_position(self.get_position(original=False), which="active") + return twin def twinx(self, axes_class=None, **kwargs): diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 74d48a89d0c0..75ab359a27eb 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -477,6 +477,42 @@ def test_twin_inherit_autoscale_setting(): assert not ax_y_off.get_autoscaley_on() +@pytest.mark.parametrize("twin", ("x", "y")) +def test_twin_respects_position_after_set_position(twin): + fig, ax = plt.subplots() + + ax.set_position([0.2, 0.2, 0.5, 0.5]) + ax2 = getattr(ax, f"twin{twin}")() + + assert_allclose(ax.get_position(original=True).bounds, + ax2.get_position(original=True).bounds) + + assert_allclose(ax.get_position(original=False).bounds, + ax2.get_position(original=False).bounds) + + +@pytest.mark.parametrize("twin", ("x", "y")) +def test_twin_keeps_layout_participation_for_layout_managed_axes(twin): + fig, ax = plt.subplots() + + ax2 = getattr(ax, f"twin{twin}")() + + assert ax.get_in_layout() + assert ax2.get_in_layout() + + +@pytest.mark.parametrize("twin", ("x", "y")) +def test_twin_stays_aligned_after_constrained_layout(twin): + fig, ax = plt.subplots(constrained_layout=True) + + ax.set_position([0.2, 0.2, 0.5, 0.5]) + ax2 = getattr(ax, f"twin{twin}")() + + fig.canvas.draw() + + assert_allclose(ax.get_position().bounds, ax2.get_position().bounds) + + def test_inverted_cla(): # GitHub PR #5450. Setting autoscale should reset # axes to be non-inverted.