From 42b0f3236544a42fd595d5e04fc9d75d0e44479b Mon Sep 17 00:00:00 2001 From: sanrishi Date: Thu, 23 Jul 2026 02:35:19 +0530 Subject: [PATCH 1/2] fix subfigures partial spacing when no layout engine --- lib/matplotlib/figure.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ad0206e0db5c..015d53432cdd 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1648,6 +1648,13 @@ 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, None is documented to mean zero. + # GridSpec passes None through to SubplotParams, which leaves the + # rcParam default (0.2) in place, so fix it up here. + if self.get_layout_engine() is None: + wspace = wspace if wspace is not None else 0.0 + hspace = hspace if hspace is not None else 0.0 + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, wspace=wspace, hspace=hspace, width_ratios=width_ratios, From 01046b734c8efefbf2b9c4236f286c4411b5775e Mon Sep 17 00:00:00 2001 From: sanrishi Date: Thu, 23 Jul 2026 02:35:26 +0530 Subject: [PATCH 2/2] add regression test for subfigures partial spacing --- lib/matplotlib/tests/test_figure.py | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 8391bda62e3c..d5cc873f20c3 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1569,6 +1569,69 @@ def test_subfigures_wspace_hspace(): np.testing.assert_allclose(sub_figs[1, 2].bbox.max, [w, h * 0.4]) +def test_subfigures_partial_spacing(): + # When no layout engine, wspace/hspace=None should mean zero. + # Regression test for gh-32076 (only one of wspace/hspace set used + # to fall back to the rcParam default for the other axis). + fig = plt.figure(figsize=(6, 4), dpi=100) + w_px, h_px = 600, 400 + + # Both None: no spacing, subfigures 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() + np.testing.assert_allclose(sfs[0, 0].bbox.bounds, + [0, h_px // 2, w_px // 2, h_px // 2]) + np.testing.assert_allclose(sfs[0, 1].bbox.bounds, + [w_px // 2, h_px // 2, w_px // 2, h_px // 2]) + np.testing.assert_allclose(sfs[1, 0].bbox.bounds, + [0, 0, w_px // 2, h_px // 2]) + np.testing.assert_allclose(sfs[1, 1].bbox.bounds, + [w_px // 2, 0, w_px // 2, h_px // 2]) + plt.close(fig) + + # Only wspace set: horizontal gap, no vertical gap. + fig = plt.figure(figsize=(6, 4), dpi=100) + 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 = 1 / 2.2 + sep = 0.2 * cell + np.testing.assert_allclose( + sfs[0, 0].bbox.bounds, + [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) + # Both rows should have equal height (no vertical gap). + 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) + + # Only hspace set: vertical gap, no horizontal gap. + fig = plt.figure(figsize=(6, 4), dpi=100) + 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() + cell = 1 / 2.2 + np.testing.assert_allclose( + sfs[0, 0].bbox.bounds, + [0, (cell + sep) * h_px, w_px / 2, cell * h_px], rtol=1e-5) + np.testing.assert_allclose( + sfs[1, 0].bbox.bounds, + [0, 0, w_px / 2, cell * h_px], rtol=1e-5) + # Both columns should have equal width (no horizontal gap). + assert sfs[0, 0].bbox.width == pytest.approx(w_px / 2) + assert sfs[0, 1].bbox.x0 == pytest.approx(sfs[0, 0].bbox.x1) + plt.close(fig) + + def test_subfigure_remove(): fig = plt.figure() sfs = fig.subfigures(2, 2)