From b085825e4ebcd1bad7788d0d810314bf1b569064 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Tue, 23 Jun 2026 20:35:47 +1000 Subject: [PATCH] Fix bug --- ultraplot/axes/container.py | 18 ++++++- .../test_external_container_edge_cases.py | 47 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/ultraplot/axes/container.py b/ultraplot/axes/container.py index 028d98b80..98bbcba88 100644 --- a/ultraplot/axes/container.py +++ b/ultraplot/axes/container.py @@ -17,6 +17,8 @@ __all__ = ["ExternalAxesContainer"] +_ABOVE_AXES_TITLE_LOCS = {"left", "center", "right"} + class ExternalAxesContainer(CartesianAxes): """ @@ -399,7 +401,9 @@ def _ensure_external_fits_within_container(self, renderer): container_bbox = container_pos.transformed(self.figure.transFigure) # Reserve vertical space for titles/abc labels. title_pad_px = 0.0 - for obj in self._title_dict.values(): + for loc, obj in self._title_dict.items(): + if not self._title_reserves_external_space(loc): + continue if not obj.get_visible(): continue if not obj.get_text(): @@ -548,7 +552,9 @@ def _update_title_position(self, renderer): container_bbox = self.get_position().transformed(fig.transFigure) if container_bbox.height <= 0: return - for obj in self._title_dict.values(): + for loc, obj in self._title_dict.items(): + if not self._title_reserves_external_space(loc): + continue bbox = obj.get_window_extent(renderer) overflow = bbox.y1 - container_bbox.y1 if overflow > 0: @@ -556,6 +562,14 @@ def _update_title_position(self, renderer): y -= overflow / container_bbox.height obj.set_position((x, y)) + def _title_reserves_external_space(self, loc): + """ + Return whether a title-like artist needs room above an external axes. + """ + if loc == "abc": + loc = self._abc_loc + return loc in _ABOVE_AXES_TITLE_LOCS + def _iter_axes(self, hidden=True, children=True, panels=True): """ Override to only yield the container itself, not the external axes. diff --git a/ultraplot/tests/test_external_container_edge_cases.py b/ultraplot/tests/test_external_container_edge_cases.py index 01c8939f5..9c86194d3 100644 --- a/ultraplot/tests/test_external_container_edge_cases.py +++ b/ultraplot/tests/test_external_container_edge_cases.py @@ -666,6 +666,53 @@ def points_to_pixels(self, value): assert new_pos.height <= initial_pos.height +class ContainerTightBboxAxes(MinimalExternalAxes): + """External axes whose tight bbox exactly matches its current position.""" + + def get_tightbbox(self, renderer): + return self._position.transformed(self.figure.transFigure) + + +def _external_position_after_abc_fit(abcloc): + fig = uplt.figure() + ax = ExternalAxesContainer( + fig, + 1, + 1, + 1, + external_axes_class=ContainerTightBboxAxes, + external_axes_kwargs={}, + external_padding=0.0, + external_shrink_factor=1.0, + ) + ax.number = 1 + ax.format(abc="A", abcloc=abcloc) + + child = ax.get_external_child() + child.set_position(ax.get_position()) + initial_pos = child.get_position().frozen() + renderer = fig.canvas.get_renderer() + ax._update_title_position(renderer) + ax._ensure_external_fits_within_container(renderer) + return initial_pos, child.get_position() + + +def test_inside_abc_location_does_not_reserve_external_title_space(): + """Inside abc labels should not shrink the external axes title area.""" + initial_pos, new_pos = _external_position_after_abc_fit("upper left") + + assert new_pos.height == pytest.approx(initial_pos.height) + assert new_pos.y0 == pytest.approx(initial_pos.y0) + + +def test_above_axes_abc_location_reserves_external_title_space(): + """Above-axes abc labels should still leave room above external axes.""" + initial_pos, new_pos = _external_position_after_abc_fit("left") + + assert new_pos.height < initial_pos.height + assert new_pos.y0 > initial_pos.y0 + + def test_external_axes_fallback_to_rect_on_typeerror(): """Test fallback to rect init when subplotspec is unsupported.""" fig = uplt.figure()