Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
11 changes: 11 additions & 0 deletions doc/api/next_api_changes/behavior/19029-FU.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
y-axis labels with non-default rotation no longer overlap tick labels
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Setting a y-axis label with a rotation other than the default (e.g.
``ax.set_ylabel("...", rotation=270)``, or ``cbar.set_label("...",
rotation=270)`` on a colorbar) previously could cause the label to
overlap the tick labels because the automatic label placement assumed a
90° rotation. The label position is now computed from the rendered
label's actual extent, so it sits clear of the tick labels for any
rotation. This may shift the position of y-labels that use non-default
rotations.
21 changes: 17 additions & 4 deletions lib/matplotlib/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2735,14 +2735,27 @@ def _update_label_position(self, renderer):
# Union with extents of the left spine if present, of the axes otherwise.
bbox = mtransforms.Bbox.union([
*bboxes, self.axes.spines.get("left", self.axes).get_window_extent()])
self.label.set_position(
(bbox.x0 - self.labelpad * self.get_figure(root=True).dpi / 72, y))
target_x = bbox.x0 - self.labelpad * self.get_figure(root=True).dpi / 72
self.label.set_position((target_x, y))
# The vertical alignment set in `set_label_position` assumes a
# 90° rotation; for any other rotation the rotated label may
# extend back toward the tick labels and overlap them. Shift
# the label outward by the overhang. See issue #19029.
label_bbox = self.label.get_window_extent(renderer)
overhang = label_bbox.x1 - target_x
if overhang > 0:
self.label.set_position((target_x - overhang, y))
else:
# Union with extents of the right spine if present, of the axes otherwise.
bbox = mtransforms.Bbox.union([
*bboxes2, self.axes.spines.get("right", self.axes).get_window_extent()])
self.label.set_position(
(bbox.x1 + self.labelpad * self.get_figure(root=True).dpi / 72, y))
target_x = bbox.x1 + self.labelpad * self.get_figure(root=True).dpi / 72
self.label.set_position((target_x, y))
# See comment in the 'left' branch above (issue #19029).
label_bbox = self.label.get_window_extent(renderer)
overhang = target_x - label_bbox.x0
if overhang > 0:
self.label.set_position((target_x + overhang, y))

def _update_offset_text_position(self, bboxes, bboxes2):
"""
Expand Down
50 changes: 50 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9070,6 +9070,56 @@ def test_ylabel_ha_with_position(ha):
assert ax.yaxis.label.get_ha() == ha


@pytest.mark.parametrize('label_position', ['left', 'right'])
@pytest.mark.parametrize('rotation', [0, 45, 90, 135, 180, 270, -90])
def test_ylabel_no_overlap_with_ticklabels(label_position, rotation):
"""Regression test for #19029: a y-label with rotation other than 90°
must not overlap the tick labels regardless of label_position.
"""
fig, ax = plt.subplots()
ax.plot([1, 3, 2])
ax.yaxis.set_label_position(label_position)
if label_position == 'right':
ax.yaxis.tick_right()
ax.set_ylabel("test label", rotation=rotation)
fig.canvas.draw()
renderer = fig.canvas.get_renderer()
label_bbox = ax.yaxis.label.get_window_extent(renderer)
tick_bboxes = [t.get_window_extent(renderer)
for t in ax.yaxis.get_ticklabels() if t.get_visible()]
tick_union = mtransforms.Bbox.union(tick_bboxes)
if label_position == 'right':
assert label_bbox.x0 >= tick_union.x1
else:
assert label_bbox.x1 <= tick_union.x0


@pytest.mark.parametrize('label_position', ['left', 'right'])
def test_align_ylabels_mixed_rotation(label_position):
"""Issue #19029: ``fig.align_ylabels`` must keep two y-labels visually
aligned even when they use different rotations. The per-axis overhang
correction in `_update_label_position` shifts each axis independently;
this test guards against that breaking sibling alignment.
"""
fig, axs = plt.subplots(2, figsize=(4, 6))
for ax in axs:
ax.plot([1, 30, 2])
ax.yaxis.set_label_position(label_position)
if label_position == 'right':
ax.yaxis.tick_right()
axs[0].set_ylabel('first', rotation=90)
axs[1].set_ylabel('second', rotation=270)
fig.align_ylabels(axs)
fig.canvas.draw()
renderer = fig.canvas.get_renderer()
bbox0 = axs[0].yaxis.label.get_window_extent(renderer)
bbox1 = axs[1].yaxis.label.get_window_extent(renderer)
# Both visible label bboxes should occupy the same horizontal range,
# even though the underlying anchor positions differ between rotations.
assert bbox0.x0 == pytest.approx(bbox1.x0, abs=0.5)
assert bbox0.x1 == pytest.approx(bbox1.x1, abs=0.5)


def test_bar_label_location_vertical():
ax = plt.gca()
xs, heights = [1, 2], [3, -4]
Expand Down
19 changes: 19 additions & 0 deletions lib/matplotlib/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,25 @@ def test_colorbar_label():
assert cbar3.ax.get_xlabel() == 'horizontal cbar'


def test_colorbar_label_rotation_no_overlap():
"""Smoke test for #19029: a rotation=270 colorbar label must not
overlap negative tick labels (the original user-facing symptom).
"""
fig, ax = plt.subplots()
im = ax.imshow([[-100, 0], [50, 100]])
cbar = fig.colorbar(im, ax=ax)
cbar.set_label('RT(1ax) - RT(2ax) [ms]', rotation=270)
fig.canvas.draw()
renderer = fig.canvas.get_renderer()
label_bbox = cbar.ax.yaxis.label.get_window_extent(renderer)
# Colorbar labels live on the right, so the label must sit clear of
# the rightmost visible tick-label edge.
tick_x1_max = max(t.get_window_extent(renderer).x1
for t in cbar.ax.yaxis.get_ticklabels()
if t.get_visible())
assert label_bbox.x0 >= tick_x1_max


# TODO: tighten tolerance after baseline image is regenerated for text overhaul
@image_comparison(['colorbar_keeping_xlabel.png'], style='mpl20', tol=0.03)
def test_keeping_xlabel():
Expand Down
Loading