From 2e2cb4a9300bf263eb87e256097867f9eb5cfab1 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 6 Feb 2026 10:29:33 +1000 Subject: [PATCH] Fix UltraLayout gaps for spanning axes --- ultraplot/tests/test_ultralayout.py | 27 +++++++++++++++++++++++++++ ultraplot/ultralayout.py | 10 +++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ultraplot/tests/test_ultralayout.py b/ultraplot/tests/test_ultralayout.py index 2e1244daa..3ea43b1d8 100644 --- a/ultraplot/tests/test_ultralayout.py +++ b/ultraplot/tests/test_ultralayout.py @@ -220,6 +220,33 @@ def test_ultralayout_respects_spacing(): assert width2 < width1 or height2 < height1 +def test_ultralayout_preserves_gap_between_spanning_axes(): + """Test UltraLayout preserves inter-axes gaps for spanning subplots.""" + pytest.importorskip("kiwisolver") + layout = [[1, 1, 2, 2], [0, 3, 3, 0]] + + fig_ultra, axs_ultra = uplt.subplots( + array=layout, ref=3, refwidth=2.3, wspace="1em", ultra_layout=True + ) + fig_ultra.auto_layout() + pos_left_ultra = axs_ultra[0].get_position() + pos_right_ultra = axs_ultra[1].get_position() + gap_ultra = pos_right_ultra.x0 - pos_left_ultra.x1 + uplt.close(fig_ultra) + + fig_legacy, axs_legacy = uplt.subplots( + array=layout, ref=3, refwidth=2.3, wspace="1em", ultra_layout=False + ) + fig_legacy.auto_layout() + pos_left_legacy = axs_legacy[0].get_position() + pos_right_legacy = axs_legacy[1].get_position() + gap_legacy = pos_right_legacy.x0 - pos_left_legacy.x1 + uplt.close(fig_legacy) + + assert gap_ultra > 0 + assert np.isclose(gap_ultra, gap_legacy, rtol=0.1, atol=1e-3) + + def test_ultralayout_respects_ratios(): """Test that UltraLayout respects width/height ratios.""" pytest.importorskip("kiwisolver") diff --git a/ultraplot/ultralayout.py b/ultraplot/ultralayout.py index 75aa9cd18..698900b4e 100644 --- a/ultraplot/ultralayout.py +++ b/ultraplot/ultralayout.py @@ -382,7 +382,15 @@ def _adjust_span( effective = [i for i in spans if not panels[i]] if len(effective) <= 1: return start, end - desired = sum(sizes[i] for i in effective) + # Preserve normal gaps between non-panel slots while collapsing + # gaps introduced by panel slots inside the span. + gap_count = 0 + for idx in range(len(spans) - 1): + i = spans[idx] + j = spans[idx + 1] + if not panels[i] and not panels[j]: + gap_count += 1 + desired = sum(sizes[i] for i in effective) + base_gap * gap_count # Collapse inter-column/row gaps inside spans to keep widths consistent. # This avoids widening subplots that cross internal panel slots. full = end - start