From 91a0b356023557e7f2187b753239b652e5765144 Mon Sep 17 00:00:00 2001 From: Fstarnb Date: Tue, 21 Jul 2026 01:28:09 +0800 Subject: [PATCH] Fix SubFigure spacing when only one of wspace/hspace is set Without a layout engine, Figure.subfigures documents that unspecified wspace/hspace is zero. GridSpec still substitutes figure.subplot.* rc defaults (0.2) for None, so setting only wspace applied a vertical gap from the default hspace (and vice versa). Coerce missing spacings to 0.0 when no layout engine is active. Fixes #32076. --- lib/matplotlib/figure.py | 9 +++++ lib/matplotlib/tests/test_figure.py | 56 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ad0206e0db5c..e43f20493230 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1648,6 +1648,15 @@ def subfigures(self, nrows=1, ncols=1, squeeze=True, relative height of ``height_ratios[i] / sum(height_ratios)``. If not given, all rows will have the same height. """ + # Without a layout engine, unspecified spacing is documented as zero. + # GridSpec falls back to figure.subplot.{w,h}space (rc default 0.2) + # when given None, so coerce missing values here (issue #32076). + if self.get_layout_engine() is None: + if wspace is None: + wspace = 0.0 + if hspace is None: + hspace = 0.0 + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, wspace=wspace, hspace=hspace, width_ratios=width_ratios, diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 8391bda62e3c..9715f0551c87 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1569,6 +1569,62 @@ def test_subfigures_wspace_hspace(): np.testing.assert_allclose(sub_figs[1, 2].bbox.max, [w, h * 0.4]) +def test_subfigures_partial_spacing_none_is_zero(): + """Unspecified wspace/hspace is zero when no layout engine is active. + + Regression for #32076: passing only one of wspace/hspace used to fall + back to figure.subplot.* rc defaults (0.2) for the unspecified axis. + """ + fig = plt.figure(figsize=(6, 4), dpi=100) + # Both None: no spacing (tile the figure). + sfs = fig.subfigures(2, 2, wspace=None, hspace=None) + for row in sfs: + for sf in row: + sf.add_subplot().plot([0, 1], [0, 1]) + fig.canvas.draw() + # Display coords at dpi=100, figsize 6x4 → 600x400. + np.testing.assert_allclose(sfs[0, 0].bbox.bounds, [0.0, 200.0, 300.0, 200.0]) + np.testing.assert_allclose(sfs[0, 1].bbox.bounds, [300.0, 200.0, 300.0, 200.0]) + np.testing.assert_allclose(sfs[1, 0].bbox.bounds, [0.0, 0.0, 300.0, 200.0]) + np.testing.assert_allclose(sfs[1, 1].bbox.bounds, [300.0, 0.0, 300.0, 200.0]) + plt.close(fig) + + fig = plt.figure(figsize=(6, 4), dpi=100) + # Only wspace set: horizontal gap, no vertical gap. + sfs = fig.subfigures(2, 2, wspace=0.2, hspace=None) + for row in sfs: + for sf in row: + sf.add_subplot().plot([0, 1], [0, 1]) + fig.canvas.draw() + # cell_w = 1 / (2 + 0.2) = 1/2.2; sep = 0.2/2.2 + cell = 1 / 2.2 + sep = 0.2 * cell + w_px, h_px = 600.0, 400.0 + np.testing.assert_allclose( + sfs[0, 0].bbox.bounds, [0.0, h_px / 2, cell * w_px, h_px / 2], rtol=1e-5) + np.testing.assert_allclose( + sfs[0, 1].bbox.bounds, + [(cell + sep) * w_px, h_px / 2, cell * w_px, h_px / 2], rtol=1e-5) + np.testing.assert_allclose( + sfs[1, 0].bbox.bounds, [0.0, 0.0, cell * w_px, h_px / 2], rtol=1e-5) + # Heights must be half the figure (no vertical spacing). + assert sfs[0, 0].bbox.height == pytest.approx(h_px / 2) + assert sfs[1, 0].bbox.y1 == pytest.approx(sfs[0, 0].bbox.y0) + plt.close(fig) + + fig = plt.figure(figsize=(6, 4), dpi=100) + # Only hspace set: vertical gap, no horizontal gap. + sfs = fig.subfigures(2, 2, wspace=None, hspace=0.2) + for row in sfs: + for sf in row: + sf.add_subplot().plot([0, 1], [0, 1]) + fig.canvas.draw() + assert sfs[0, 0].bbox.width == pytest.approx(w_px / 2) + assert sfs[0, 1].bbox.x0 == pytest.approx(sfs[0, 0].bbox.x1) + assert sfs[0, 0].bbox.height == pytest.approx(cell * h_px, rel=1e-5) + plt.close(fig) + + def test_subfigure_remove(): fig = plt.figure() sfs = fig.subfigures(2, 2)