From c159c269296fbfcf412086f46f3a3bca20469f99 Mon Sep 17 00:00:00 2001 From: Arnav_Deva Date: Mon, 30 Mar 2026 20:34:59 +0530 Subject: [PATCH 1/2] Include figure-level texts and legends in constrained layout --- lib/matplotlib/_constrained_layout.py | 69 ++++++++++++++----- .../tests/test_constrainedlayout.py | 11 +++ 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 33ec8ef985e7..9afca6b80260 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -440,22 +440,59 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, # make margins for figure-level legends: for leg in fig.legends: - inv_trans_fig = None - if leg._outside_loc and leg._bbox_to_anchor is None: - if inv_trans_fig is None: - inv_trans_fig = fig.transFigure.inverted().transform_bbox - bbox = inv_trans_fig(leg.get_tightbbox(renderer)) - w = bbox.width + 2 * w_pad - h = bbox.height + 2 * h_pad - legendloc = leg._outside_loc - if legendloc == 'lower': - layoutgrids[fig].edit_margin_min('bottom', h) - elif legendloc == 'upper': - layoutgrids[fig].edit_margin_min('top', h) - if legendloc == 'right': - layoutgrids[fig].edit_margin_min('right', w) - elif legendloc == 'left': - layoutgrids[fig].edit_margin_min('left', w) + # === NEW: handle figure-level texts === + inv_trans_fig = fig.transFigure.inverted().transform_bbox + + for text in fig.texts: + if not text.get_visible() or not text.get_in_layout(): + continue + + try: + bbox = inv_trans_fig(text.get_tightbbox(renderer)) + except Exception: + continue + + if bbox is None: + continue + + w = bbox.width + 2 * w_pad + h = bbox.height + 2 * h_pad + + # Expand margins conservatively + if bbox.y1 > 1: # top overflow + layoutgrids[fig].edit_margin_min('top', h) + if bbox.y0 < 0: # bottom overflow + layoutgrids[fig].edit_margin_min('bottom', h) + if bbox.x0 < 0: # left overflow + layoutgrids[fig].edit_margin_min('left', w) + if bbox.x1 > 1: # right overflow + layoutgrids[fig].edit_margin_min('right', w) + + +# === NEW: handle general figure legends (not just _outside_loc) === +for leg in fig.legends: + if not leg.get_visible() or not leg.get_in_layout(): + continue + + try: + bbox = inv_trans_fig(leg.get_tightbbox(renderer)) + except Exception: + continue + + if bbox is None: + continue + + w = bbox.width + 2 * w_pad + h = bbox.height + 2 * h_pad + + if bbox.y1 > 1: + layoutgrids[fig].edit_margin_min('top', h) + if bbox.y0 < 0: + layoutgrids[fig].edit_margin_min('bottom', h) + if bbox.x0 < 0: + layoutgrids[fig].edit_margin_min('left', w) + if bbox.x1 > 1: + layoutgrids[fig].edit_margin_min('right', w) def make_margin_suptitles(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0): diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 91aaa2fd9172..de0819716715 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -14,7 +14,18 @@ pytestmark = [ pytest.mark.usefixtures('text_placeholders') ] +def test_constrainedlayout_fig_text(): + import matplotlib.pyplot as plt + fig, ax = plt.subplots(layout="constrained") + fig.text(0.5, 0.98, "Title", ha="center") + + fig.draw_without_rendering() + + ax_bbox = ax.get_position() + + # Axes should not touch the top + assert ax_bbox.y1 < 0.98 def example_plot(ax, fontsize=12, nodec=False): ax.plot([1, 2]) From 92132ec57d5e766ed77c83b3106c0710591b4b3b Mon Sep 17 00:00:00 2001 From: Arnav_Deva Date: Mon, 30 Mar 2026 20:51:38 +0530 Subject: [PATCH 2/2] Include figure-level texts and legends in constrained layout --- lib/matplotlib/_constrained_layout.py | 21 +++++++++---------- .../tests/test_constrainedlayout.py | 19 +++++++++++++---- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 9afca6b80260..5d2ad2dff7d0 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -440,20 +440,20 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, # make margins for figure-level legends: for leg in fig.legends: - # === NEW: handle figure-level texts === + # === NEW: handle figure-level texts === inv_trans_fig = fig.transFigure.inverted().transform_bbox - for text in fig.texts: - if not text.get_visible() or not text.get_in_layout(): - continue +for text in fig.texts: + if not text.get_visible() or not text.get_in_layout(): + continue - try: - bbox = inv_trans_fig(text.get_tightbbox(renderer)) - except Exception: - continue + try: + bbox = inv_trans_fig(text.get_tightbbox(renderer)) + except Exception: + continue - if bbox is None: - continue + if bbox is None: + continue w = bbox.width + 2 * w_pad h = bbox.height + 2 * h_pad @@ -494,7 +494,6 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, if bbox.x1 > 1: layoutgrids[fig].edit_margin_min('right', w) - def make_margin_suptitles(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0): # Figure out how large the suptitle is and make the # top level figure margin larger. diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index de0819716715..a9a8eb69aaea 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -14,19 +14,30 @@ pytestmark = [ pytest.mark.usefixtures('text_placeholders') ] -def test_constrainedlayout_fig_text(): +def test_constrainedlayout_fig_text_included(): import matplotlib.pyplot as plt fig, ax = plt.subplots(layout="constrained") - fig.text(0.5, 0.98, "Title", ha="center") + fig.text(0.5, 0.98, "Figure Title", ha="center") fig.draw_without_rendering() + # Ensure no overlap: text should be above axes ax_bbox = ax.get_position() - - # Axes should not touch the top assert ax_bbox.y1 < 0.98 +def test_constrainedlayout_fig_legend_included(): + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(layout="constrained") + ax.plot([1, 2, 3], label="line") + fig.legend(loc="upper center") + + fig.draw_without_rendering() + + ax_bbox = ax.get_position() + assert ax_bbox.y1 < 1.0 # leaves space for legend + def example_plot(ax, fontsize=12, nodec=False): ax.plot([1, 2]) ax.locator_params(nbins=3)