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

Commitfcd99fa

Browse files
committed
Implement xtick and ytick rotation_mode
1 parent84fbae8 commitfcd99fa

File tree

10 files changed

+162
-18
lines changed

10 files changed

+162
-18
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 adjusts the alignment of rotated tick labels,
7+
so that the texts point 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
foriinrange(2):
@@ -35,7 +36,8 @@
3536
ax.set_ylabel('YLabel1 %d'%i)
3637
ax.set_xlabel('XLabel1 %d'%i)
3738
ifi==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: 24 additions & 10 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:]:vfork,vinkwargs.items()}
152+
grid_kw= {k[5:]:vfork,vinkwargs.items()ifk!="rotation_mode"}
153153

154154
self.tick1line=mlines.Line2D(
155155
[], [],
@@ -346,6 +346,11 @@ def _apply_params(self, **kwargs):
346346
ifkin_gridline_param_names}
347347
self.gridline.set(**grid_kw)
348348

349+
if'rotation_mode'inkwargs:
350+
rotation_mode=kwargs.pop('rotation_mode')
351+
self.label1.set_rotation_mode(rotation_mode)
352+
self.label2.set_rotation_mode(rotation_mode)
353+
349354
defupdate_position(self,loc):
350355
"""Set the location of tick in data coords with scalar *loc*."""
351356
raiseNotImplementedError('Derived must override')
@@ -1049,12 +1054,11 @@ def get_tick_params(self, which='major'):
10491054
_api.check_in_list(['major','minor'],which=which)
10501055
ifwhich=='major':
10511056
returnself._translate_tick_params(
1052-
self._major_tick_kw,reverse=True
1053-
)
1057+
self._major_tick_kw,reverse=True)
10541058
returnself._translate_tick_params(self._minor_tick_kw,reverse=True)
10551059

1056-
@staticmethod
1057-
def_translate_tick_params(kw,reverse=False):
1060+
@classmethod
1061+
def_translate_tick_params(cls,kw,reverse=False):
10581062
"""
10591063
Translate the kwargs supported by `.Axis.set_tick_params` to kwargs
10601064
supported by `.Tick._apply_params`.
@@ -1078,7 +1082,7 @@ def _translate_tick_params(kw, reverse=False):
10781082
'tick1On','tick2On','label1On','label2On',
10791083
'length','direction','left','bottom','right','top',
10801084
'labelleft','labelbottom','labelright','labeltop',
1081-
'labelrotation',
1085+
'labelrotation','rotation_mode',
10821086
*_gridline_param_names]
10831087

10841088
keymap= {
@@ -1096,10 +1100,20 @@ def _translate_tick_params(kw, reverse=False):
10961100
'labeltop':'label2On',
10971101
}
10981102
ifreverse:
1099-
kwtrans= {
1100-
oldkey:kw_.pop(newkey)
1101-
foroldkey,newkeyinkeymap.items()ifnewkeyinkw_
1102-
}
1103+
is_x_axis=cls.axis_name=='x'
1104+
kwtrans= {}
1105+
foroldkey,newkeyinkeymap.items():
1106+
ifnewkeyinkw_:
1107+
ifis_x_axisandnewkey=='label1On':
1108+
kwtrans['labelbottom']=kw_.pop(newkey)
1109+
elifis_x_axisandnewkey=='tick1On':
1110+
kwtrans['bottom']=kw_.pop(newkey)
1111+
elifis_x_axisandnewkey=='label2On':
1112+
kwtrans['labeltop']=kw_.pop(newkey)
1113+
elifis_x_axisandnewkey=='tick2On':
1114+
kwtrans['top']=kw_.pop(newkey)
1115+
else:
1116+
kwtrans[oldkey]=kw_.pop(newkey)
11031117
else:
11041118
kwtrans= {
11051119
newkey:kw_.pop(oldkey)

‎lib/matplotlib/tests/test_axis.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,12 @@ def test_axis_not_in_layout():
2929
# Positions should not be affected by overlapping 100 label
3030
assertax1_left.get_position().bounds==ax2_left.get_position().bounds
3131
assertax1_right.get_position().bounds==ax2_right.get_position().bounds
32+
33+
34+
deftest_translate_tick_params_reverse():
35+
fig,ax=plt.subplots()
36+
kw= {'label1On':'a','label2On':'b','tick1On':'c','tick2On':'d'}
37+
assert (ax.xaxis._translate_tick_params(kw,reverse=True)==
38+
{'labelbottom':'a','labeltop':'b','bottom':'c','top':'d'})
39+
assert (ax.yaxis._translate_tick_params(kw,reverse=True)==
40+
{'labelleft':'a','labelright':'b','left':'c','right':'d'})

‎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+
forangleinangles:
1144+
alignment=text_instance.ha_for_angle(angle)
1145+
assertalignmentin ['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+
forangleinangles:
1152+
alignment=text_instance.va_for_angle(angle)
1153+
assertalignmentin ['center','top','baseline']
1154+
1155+
1156+
@image_comparison(baseline_images=['xtick_rotation_mode'],
1157+
remove_text=False,extensions=['png'],style='mpl20')
1158+
deftest_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+
fortick,angleinzip(ax.get_xticklabels(),angles):
1169+
tick.set_rotation(angle)
1170+
fortick,angleinzip(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+
deftest_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+
fortick,angleinzip(ax.get_yticklabels(),angles):
1188+
tick.set_rotation(angle)
1189+
fortick,angleinzip(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
ifmisNone:
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
ifrotation_mode!="anchor":
460+
angle=self.get_rotation()
461+
ifrotation_mode=='xtick':
462+
halign=self._ha_for_angle(angle)
463+
elifrotation_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
ifhalign=='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<5or85<=angle<105or355<=angle<360or
1398+
170<=angle<190or265<=angle<275):
1399+
return'center'
1400+
elif5<=angle<85or190<=angle<265:
1401+
return'left'ifanchor_at_bottomelse'right'
1402+
return'right'ifanchor_at_bottomelse'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<5or355<=angle<360or170<=angle<190
1411+
or85<=angle<105or265<=angle<275):
1412+
return'center'
1413+
elif190<=angle<265or5<=angle<85:
1414+
return'baseline'ifanchor_at_leftelse'top'
1415+
return'top'ifanchor_at_leftelse'baseline'
1416+
13831417

13841418
classOffsetFrom:
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
defset_fontname(self,fontname:str|Iterable[str])->None: ...
107107
defget_antialiased(self)->bool: ...
108108
defset_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
classOffsetFrom:
111113
def__init__(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp