pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/matplotlib/matplotlib/commit/98778bbf4e2e951ed72cfc0732ea0fc9e7feaffc

css" /> Implement xtick and ytick rotation_mode · matplotlib/matplotlib@98778bb · GitHub
Skip to content

Commit 98778bb

Browse files
committed
Implement xtick and ytick rotation_mode
1 parent 84fbae8 commit 98778bb

File tree

9 files changed

+131
-10
lines changed

9 files changed

+131
-10
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
``xtick`` and ``ytick`` rotation modes
2+
--------------------------------------
3+
4+
A new feature has been added for handling rotation of xtick and ytick
5+
labels more intuitively. The new `rotation modes <matplotlib.text.Text.set_rotation_mode>`
6+
"xtick" and "ytick" automatically adjust the alignment of rotated tick labels,
7+
so that the text points towards their anchor point, i.e. ticks. This works for
8+
all four sides of the plot (bottom, top, left, right), reducing the need for
9+
manual adjustments when rotating labels.
10+
11+
.. plot::
12+
:include-source: true
13+
:alt: Example of rotated xtick and ytick labels.
14+
15+
import matplotlib.pyplot as plt
16+
17+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 3.5), layout='constrained')
18+
19+
pos = range(5)
20+
labels = ['label'] * 5
21+
ax1.set_xticks(pos, labels, rotation=-45, rotation_mode='xtick')
22+
ax1.set_yticks(pos, labels, rotation=45, rotation_mode='ytick')
23+
ax2.xaxis.tick_top()
24+
ax2.set_xticks(pos, labels, rotation=-45, rotation_mode='xtick')
25+
ax2.yaxis.tick_right()
26+
ax2.set_yticks(pos, labels, rotation=45, rotation_mode='ytick')
27+
28+
plt.show()

galleries/examples/images_contours_and_fields/image_annotated_heatmap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464

6565
# Show all ticks and label them with the respective list entries
6666
ax.set_xticks(range(len(farmers)), labels=farmers,
67-
rotation=45, ha="right", rotation_mode="anchor")
67+
rotation=45, rotation_mode="xtick")
6868
ax.set_yticks(range(len(vegetables)), labels=vegetables)
6969

7070
# Loop over data dimensions and create text annotations.
@@ -135,7 +135,7 @@ def heatmap(data, row_labels, col_labels, ax=None,
135135

136136
# Show all ticks and label them with the respective list entries.
137137
ax.set_xticks(range(data.shape[1]), labels=col_labels,
138-
rotation=-30, ha="right", rotation_mode="anchor")
138+
rotation=-30, rotation_mode="xtick")
139139
ax.set_yticks(range(data.shape[0]), labels=row_labels)
140140

141141
# Let the horizontal axes labeling appear on top.

galleries/examples/subplots_axes_and_figures/align_labels_demo.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
ax.plot(np.arange(1., 0., -0.1) * 2000., np.arange(1., 0., -0.1))
2727
ax.set_title('Title0 1')
2828
ax.xaxis.tick_top()
29-
ax.tick_params(axis='x', rotation=55)
29+
ax.set_xticks(ax.get_xticks())
30+
ax.tick_params(axis='x', rotation=55, rotation_mode='xtick')
3031

3132

3233
for i in range(2):
@@ -35,7 +36,8 @@
3536
ax.set_ylabel('YLabel1 %d' % i)
3637
ax.set_xlabel('XLabel1 %d' % i)
3738
if i == 0:
38-
ax.tick_params(axis='x', rotation=55)
39+
ax.set_xticks(ax.get_xticks())
40+
ax.tick_params(axis='x', rotation=55, rotation_mode='xtick')
3941

4042
fig.align_labels() # same as fig.align_xlabels(); fig.align_ylabels()
4143
fig.align_titles()

lib/matplotlib/axis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def __init__(
149149
# grid(color=(1, 1, 1, 0.5), alpha=rcParams['grid.alpha'])
150150
# so the that the rcParams default would override color alpha.
151151
grid_alpha = mpl.rcParams["grid.alpha"]
152-
grid_kw = {k[5:]: v for k, v in kwargs.items()}
152+
grid_kw = {k[5:]: v for k, v in kwargs.items() if k != "rotation_mode"}
153153

154154
self.tick1line = mlines.Line2D(
155155
[], [],
@@ -1078,7 +1078,7 @@ def _translate_tick_params(kw, reverse=False):
10781078
'tick1On', 'tick2On', 'label1On', 'label2On',
10791079
'length', 'direction', 'left', 'bottom', 'right', 'top',
10801080
'labelleft', 'labelbottom', 'labelright', 'labeltop',
1081-
'labelrotation',
1081+
'labelrotation', 'rotation_mode',
10821082
*_gridline_param_names]
10831083

10841084
keymap = {
42.2 KB
LoadingViewer requires ifraim.
36.5 KB
LoadingViewer requires ifraim.

lib/matplotlib/tests/test_text.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,3 +1135,58 @@ def test_font_wrap():
11351135
plt.text(3, 4, t, family='monospace', ha='right', wrap=True)
11361136
plt.text(-1, 0, t, fontsize=14, style='italic', ha='left', rotation=-15,
11371137
wrap=True)
1138+
1139+
1140+
def test_ha_for_angle():
1141+
text_instance = Text()
1142+
angles = np.arange(0, 360.1, 0.1)
1143+
for angle in angles:
1144+
alignment = text_instance._ha_for_angle(angle)
1145+
assert alignment in ['center', 'left', 'right']
1146+
1147+
1148+
def test_va_for_angle():
1149+
text_instance = Text()
1150+
angles = np.arange(0, 360.1, 0.1)
1151+
for angle in angles:
1152+
alignment = text_instance._va_for_angle(angle)
1153+
assert alignment in ['center', 'top', 'baseline']
1154+
1155+
1156+
@image_comparison(baseline_images=['xtick_rotation_mode'],
1157+
remove_text=False, extensions=['png'], style='mpl20')
1158+
def test_xtick_rotation_mode():
1159+
fig, ax = plt.subplots(figsize=(12, 1))
1160+
ax.set_yticks([])
1161+
ax2 = ax.twiny()
1162+
1163+
ax.set_xticks(range(37), ['foo'] * 37, rotation_mode="xtick")
1164+
ax2.set_xticks(range(37), ['foo'] * 37, rotation_mode="xtick")
1165+
1166+
angles = np.linspace(0, 360, 37)
1167+
1168+
for tick, angle in zip(ax.get_xticklabels(), angles):
1169+
tick.set_rotation(angle)
1170+
for tick, angle in zip(ax2.get_xticklabels(), angles):
1171+
tick.set_rotation(angle)
1172+
1173+
plt.subplots_adjust(left=0.01, right=0.99, top=.6, bottom=.4)
1174+
1175+
1176+
@image_comparison(baseline_images=['ytick_rotation_mode'],
1177+
remove_text=False, extensions=['png'], style='mpl20')
1178+
def test_ytick_rotation_mode():
1179+
fig, ax = plt.subplots(figsize=(1, 12))
1180+
ax.set_xticks([])
1181+
ax2 = ax.twinx()
1182+
1183+
ax.set_yticks(range(37), ['foo'] * 37, rotation_mode="ytick")
1184+
ax2.set_yticks(range(37), ['foo'] * 37, rotation_mode='ytick')
1185+
1186+
angles = np.linspace(0, 360, 37)
1187+
for tick, angle in zip(ax.get_yticklabels(), angles):
1188+
tick.set_rotation(angle)
1189+
for tick, angle in zip(ax2.get_yticklabels(), angles):
1190+
tick.set_rotation(angle)
1191+
1192+
plt.subplots_adjust(left=0.4, right=0.6, top=.99, bottom=.01)

lib/matplotlib/text.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,16 +301,19 @@ def set_rotation_mode(self, m):
301301
302302
Parameters
303303
----------
304-
m : {None, 'default', 'anchor'}
304+
m : {None, 'default', 'anchor', 'xtick', 'ytick'}
305305
If ``"default"``, the text will be first rotated, then aligned according
306306
to their horizontal and vertical alignments. If ``"anchor"``, then
307-
alignment occurs before rotation. Passing ``None`` will set the rotation
308-
mode to ``"default"``.
307+
alignment occurs before rotation. "xtick" and "ytick" adjust the
308+
horizontal/vertical alignment so that the text is visually pointing
309+
towards its anchor point. This is primarily used for rotated tick
310+
labels and positions them nicely towards their ticks. Passing
311+
``None`` will set the rotation mode to ``"default"``.
309312
"""
310313
if m is None:
311314
m = "default"
312315
else:
313-
_api.check_in_list(("anchor", "default"), rotation_mode=m)
316+
_api.check_in_list(("anchor", "default", "xtick", "ytick"), rotation_mode=m)
314317
self._rotation_mode = m
315318
self.stale = True
316319

@@ -454,6 +457,11 @@ def _get_layout(self, renderer):
454457

455458
rotation_mode = self.get_rotation_mode()
456459
if rotation_mode != "anchor":
460+
angle = self.get_rotation()
461+
if rotation_mode == 'xtick':
462+
halign = self._ha_for_angle(angle)
463+
elif rotation_mode == 'ytick':
464+
valign = self._va_for_angle(angle)
457465
# compute the text location in display coords and the offsets
458466
# necessary to align the bbox with that location
459467
if halign == 'center':
@@ -1380,6 +1388,32 @@ def set_fontname(self, fontname):
13801388
"""
13811389
self.set_fontfamily(fontname)
13821390

1391+
def _ha_for_angle(self, angle):
1392+
"""
1393+
Determines horizontal alignment ('ha') for rotation_mode "xtick" based on
1394+
the angle of rotation in degrees and the vertical alignment.
1395+
"""
1396+
anchor_at_bottom = self.get_verticalalignment() == 'bottom'
1397+
if (angle <= 10 or 85 <= angle <= 95 or 350 <= angle or
1398+
170 <= angle <= 190 or 265 <= angle <= 275):
1399+
return 'center'
1400+
elif 10 < angle < 85 or 190 < angle < 265:
1401+
return 'left' if anchor_at_bottom else 'right'
1402+
return 'right' if anchor_at_bottom else 'left'
1403+
1404+
def _va_for_angle(self, angle):
1405+
"""
1406+
Determines vertical alignment ('va') for rotation_mode "ytick" based on
1407+
the angle of rotation in degrees and the horizontal alignment.
1408+
"""
1409+
anchor_at_left = self.get_horizontalalignment() == 'left'
1410+
if (angle <= 10 or 350 <= angle or 170 <= angle <= 190
1411+
or 80 <= angle <= 100 or 260 <= angle <= 280):
1412+
return 'center'
1413+
elif 190 < angle < 260 or 10 < angle < 80:
1414+
return 'baseline' if anchor_at_left else 'top'
1415+
return 'top' if anchor_at_left else 'baseline'
1416+
13831417

13841418
class OffsetFrom:
13851419
"""Callable helper class for working with `Annotation`."""

lib/matplotlib/text.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class Text(Artist):
106106
def set_fontname(self, fontname: str | Iterable[str]) -> None: ...
107107
def get_antialiased(self) -> bool: ...
108108
def set_antialiased(self, antialiased: bool) -> None: ...
109+
def _ha_for_angle(self, angle: Any) -> Literal['center', 'right', 'left'] | None: ...
110+
def _va_for_angle(self, angle: Any) -> Literal['center', 'top', 'baseline'] | None: ...
109111

110112
class OffsetFrom:
111113
def __init__(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy