Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix colorbar alignment with suptitle in compressed layout mode#30766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
jklymak merged 7 commits intomatplotlib:mainfrommiriamsimone:main
Nov 27, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
38 changes: 33 additions & 5 deletionslib/matplotlib/_constrained_layout.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -137,7 +137,8 @@ def do_constrained_layout(fig, h_pad, w_pad,
layoutgrids[fig].update_variables()
if check_no_collapsed_axes(layoutgrids, fig):
reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad,
w_pad=w_pad, hspace=hspace, wspace=wspace)
w_pad=w_pad, hspace=hspace, wspace=wspace,
compress=True)
else:
_api.warn_external(warn_collapsed)

Expand DownExpand Up@@ -651,7 +652,7 @@ def get_pos_and_bbox(ax, renderer):


def reposition_axes(layoutgrids, fig, renderer, *,
w_pad=0, h_pad=0, hspace=0, wspace=0):
w_pad=0, h_pad=0, hspace=0, wspace=0, compress=False):
"""
Reposition all the Axes based on the new inner bounding box.
"""
Expand All@@ -662,7 +663,7 @@ def reposition_axes(layoutgrids, fig, renderer, *,
bbox=bbox.transformed(trans_fig_to_subfig))
reposition_axes(layoutgrids, sfig, renderer,
w_pad=w_pad, h_pad=h_pad,
wspace=wspace, hspace=hspace)
wspace=wspace, hspace=hspace, compress=compress)

for ax in fig._localaxes:
if ax.get_subplotspec() is None or not ax.get_in_layout():
Expand All@@ -689,10 +690,10 @@ def reposition_axes(layoutgrids, fig, renderer, *,
for nn, cbax in enumerate(ax._colorbars[::-1]):
if ax == cbax._colorbar_info['parents'][0]:
reposition_colorbar(layoutgrids, cbax, renderer,
offset=offset)
offset=offset, compress=compress)


def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None):
def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None, compress=False):
"""
Place the colorbar in its new place.

Expand All@@ -706,6 +707,8 @@ def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None):
offset : array-like
Offset the colorbar needs to be pushed to in order to
account for multiple colorbars.
compress : bool
Whether we're in compressed layout mode.
"""

parents = cbax._colorbar_info['parents']
Expand All@@ -724,6 +727,31 @@ def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None):
aspect = cbax._colorbar_info['aspect']
shrink = cbax._colorbar_info['shrink']

# For colorbars with a single parent in compressed layout,
# use the actual visual size of the parent axis after apply_aspect()
# has been called. This ensures colorbars align with their parent axes.
# This fix is specific to single-parent colorbars where alignment is critical.
if compress and len(parents) == 1:
from matplotlib.transforms import Bbox
# Get the actual parent position after apply_aspect()
parent_ax = parents[0]
actual_pos = parent_ax.get_position(original=False)
# Transform to figure coordinates
actual_pos_fig = actual_pos.transformed(fig.transSubfigure - fig.transFigure)

if location in ('left', 'right'):
# For vertical colorbars, use the actual parent bbox height
# for colorbar sizing
# Keep the pb x-coordinates but use actual y-coordinates
pb = Bbox.from_extents(pb.x0, actual_pos_fig.y0,
pb.x1, actual_pos_fig.y1)
elif location in ('top', 'bottom'):
# For horizontal colorbars, use the actual parent bbox width
# for colorbar sizing
# Keep the pb y-coordinates but use actual x-coordinates
pb = Bbox.from_extents(actual_pos_fig.x0, pb.y0,
actual_pos_fig.x1, pb.y1)

cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)

# Colorbar gets put at extreme edge of outer bbox of the subplotspec
Expand Down
Loading
Sorry, something went wrong.Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong.Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletionslib/matplotlib/tests/test_constrainedlayout.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -688,6 +688,77 @@ def test_compressed_suptitle():
assert title.get_position()[1] == 0.98


@image_comparison(['test_compressed_suptitle_colorbar.png'], style='mpl20')
def test_compressed_suptitle_colorbar():
"""Test that colorbars align with axes in compressed layout with suptitle."""
arr = np.arange(100).reshape((10, 10))
fig, axs = plt.subplots(ncols=2, figsize=(4, 2), layout='compressed')

im0 = axs[0].imshow(arr)
im1 = axs[1].imshow(arr)

cb0 = plt.colorbar(im0, ax=axs[0])
cb1 = plt.colorbar(im1, ax=axs[1])

fig.suptitle('Title')

# Verify colorbar heights match axes heights
# After layout, colorbar should have same height as parent axes
fig.canvas.draw()

for ax, cb in zip(axs, [cb0, cb1]):
ax_pos = ax.get_position()
cb_pos = cb.ax.get_position()

# Check that colorbar height matches axes height (within tolerance)
# Note: We check the actual rendered positions, not the bbox
assert abs(cb_pos.height - ax_pos.height) < 0.01, \
f"Colorbar height {cb_pos.height} doesn't match axes height {ax_pos.height}"

# Also verify vertical alignment (y0 and y1 should match)
assert abs(cb_pos.y0 - ax_pos.y0) < 0.01, \
f"Colorbar y0 {cb_pos.y0} doesn't match axes y0 {ax_pos.y0}"
assert abs(cb_pos.y1 - ax_pos.y1) < 0.01, \
f"Colorbar y1 {cb_pos.y1} doesn't match axes y1 {ax_pos.y1}"


@image_comparison(['test_compressed_supylabel_colorbar.png'], style='mpl20')
def test_compressed_supylabel_colorbar():
"""
Test that horizontal colorbars align with axes
in compressed layout with supylabel.
"""
arr = np.arange(100).reshape((10, 10))
fig, axs = plt.subplots(nrows=2, figsize=(3, 4), layout='compressed')

im0 = axs[0].imshow(arr)
im1 = axs[1].imshow(arr)

cb0 = plt.colorbar(im0, ax=axs[0], orientation='horizontal')
cb1 = plt.colorbar(im1, ax=axs[1], orientation='horizontal')

fig.supylabel('Title')

# Verify colorbar widths match axes widths
# After layout, colorbar should have same width as parent axes
fig.canvas.draw()

for ax, cb in zip(axs, [cb0, cb1]):
ax_pos = ax.get_position()
cb_pos = cb.ax.get_position()

# Check that colorbar width matches axes width (within tolerance)
# Note: We check the actual rendered positions, not the bbox
assert abs(cb_pos.width - ax_pos.width) < 0.01, \
f"Colorbar width {cb_pos.width} doesn't match axes width {ax_pos.width}"

# Also verify horizontal alignment (x0 and x1 should match)
assert abs(cb_pos.x0 - ax_pos.x0) < 0.01, \
f"Colorbar x0 {cb_pos.x0} doesn't match axes x0 {ax_pos.x0}"
assert abs(cb_pos.x1 - ax_pos.x1) < 0.01, \
f"Colorbar x1 {cb_pos.x1} doesn't match axes x1 {ax_pos.x1}"


@pytest.mark.parametrize('arg, state', [
(True, True),
(False, False),
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp