Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions ultraplot/axes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2459,7 +2459,20 @@ def _reposition_subplot(self):
ncols=gs.ncols_total
)

# Check if the panel has a span override (spans more columns/rows
# than its parent). When it does, use the SubplotSpec position for
# the "along" dimension so the span is respected. Otherwise use
# parent_bbox which correctly tracks aspect-ratio adjustments.
parent_ss = self._panel_parent.get_subplotspec().get_topmost_subplotspec()
p_row1, p_row2, p_col1, p_col2 = parent_ss._get_rows_columns(
ncols=gs.ncols_total
)

if side in ("right", "left"):
has_span_override = (row1 < p_row1) or (row2 > p_row2)
along_bbox = (
ss.get_position(self.figure) if has_span_override else parent_bbox
)
boundary = None
width = sum(gs._wratios_total[col1 : col2 + 1]) / figwidth
if a_col2 < col1:
Expand All @@ -2480,9 +2493,13 @@ def _reposition_subplot(self):
else:
x0 = anchor_bbox.x0 - pad - width
bbox = mtransforms.Bbox.from_bounds(
x0, parent_bbox.y0, width, parent_bbox.height
x0, along_bbox.y0, width, along_bbox.height
)
else:
has_span_override = (col1 < p_col1) or (col2 > p_col2)
along_bbox = (
ss.get_position(self.figure) if has_span_override else parent_bbox
)
boundary = None
height = sum(gs._hratios_total[row1 : row2 + 1]) / figheight
if a_row2 < row1:
Expand All @@ -2502,7 +2519,7 @@ def _reposition_subplot(self):
else:
y0 = anchor_bbox.y0 - pad - height
bbox = mtransforms.Bbox.from_bounds(
parent_bbox.x0, y0, parent_bbox.width, height
along_bbox.x0, y0, along_bbox.width, height
)
setter(bbox)

Expand Down
56 changes: 56 additions & 0 deletions ultraplot/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,3 +926,59 @@ def test_colorbar_multiple_sides_with_span():
assert cb_top is not None
assert cb_right is not None
assert cb_left is not None


def test_colorbar_span_position_matches_target_columns():
"""Regression: UltraLayout must not clip span panels to parent width.

The _reposition_subplot UltraLayout block used parent_bbox for the
"along" dimension, overriding the SubplotSpec span. Verify the drawn
panel actually spans the requested columns/rows.
"""
fig, axs = uplt.subplots(nrows=2, ncols=3)
data = np.random.random((10, 10))
cm = axs[0, 0].pcolormesh(data)

# Bottom colorbar anchored to axs[0,:] spanning columns 1-2
cb = fig.colorbar(cm, ax=axs[0, :], span=(1, 2), loc="bottom")
fig.canvas.draw()

panel_pos = cb.ax.get_position()
col0_pos = axs[0, 0].get_position()
col1_pos = axs[0, 1].get_position()

# Panel must start at column 0's left edge and end at column 1's right edge
assert (
abs(panel_pos.x0 - col0_pos.x0) < 0.02
), f"Panel x0={panel_pos.x0:.3f} != col0 x0={col0_pos.x0:.3f}"
assert (
abs(panel_pos.x1 - col1_pos.x1) < 0.02
), f"Panel x1={panel_pos.x1:.3f} != col1 x1={col1_pos.x1:.3f}"
# Sanity: panel must be wider than a single column
assert panel_pos.width > col0_pos.width * 1.5


def test_colorbar_span_position_matches_target_rows():
"""Regression: right colorbar with rows= must span the requested rows."""
fig, axs = uplt.subplots(nrows=3, ncols=2)
data = np.random.random((10, 10))
cm = axs[0, 0].pcolormesh(data)

# Right colorbar anchored to axs[:,0] spanning rows 1-2
cb = fig.colorbar(cm, ax=axs[:, 0], rows=(1, 2), loc="right")
fig.canvas.draw()

panel_pos = cb.ax.get_position()
row0_pos = axs[0, 0].get_position()
row1_pos = axs[1, 0].get_position()

# Panel must start at row 1's bottom and end at row 0's top
# (row 0 is top, row 1 is below it)
assert (
abs(panel_pos.y1 - row0_pos.y1) < 0.02
), f"Panel y1={panel_pos.y1:.3f} != row0 y1={row0_pos.y1:.3f}"
assert (
abs(panel_pos.y0 - row1_pos.y0) < 0.02
), f"Panel y0={panel_pos.y0:.3f} != row1 y0={row1_pos.y0:.3f}"
# Sanity: panel must be taller than a single row
assert panel_pos.height > row0_pos.height * 1.5