Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.1k
Implement xtick and ytick rotation modes#28968
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
Uh oh!
There was an error while loading.Please reload this page.
Conversation
92c0a43 toee92312Compare9356808 toefc02caCompare03d0f37 tofe4a1c0Compare
scottshambaugh left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is looking pretty good I think! I know this is still a draft so apologies if I'm jumping the gun with a review, but please add:
- Image comparison tests exercising the new codepaths
- A "What's new" entry explaining the new feature
- Updates to some of the examples to show this behavior. Good candidate might behttps://matplotlib.org/devdocs/gallery/subplots_axes_and_figures/align_labels_demo.html orhttps://matplotlib.org/devdocs/gallery/images_contours_and_fields/image_annotated_heatmap.html
lib/matplotlib/text.py Outdated
| """ | ||
| self.set_fontfamily(fontname) | ||
| defha_for_angle(self,angle): |
scottshambaughOct 15, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
angle here and below should be restricted to [0, 360) deg. Docstring should also call out thatangle is measured in degrees
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't think this is being covered yet - the angle input here and the va function should be wrapped to [0, 360). In other words:angle = angle % 360
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Sorry if I didn't mention this earlier! Matplotlib already applies the modulo operation to the input angle before it reaches_ha_for_angle and_va_for_angle. It applies the modulo operation here:
defset_rotation(self,s):""" Set the rotation of the text. Parameters ---------- s : float or {'vertical', 'horizontal'} The rotation angle in degrees in mathematically positive direction (counterclockwise). 'horizontal' equals 0, 'vertical' equals 90. """ifisinstance(s,Real):self._rotation=float(s)%360elifcbook._str_equal(s,'horizontal')orsisNone:self._rotation=0.elifcbook._str_equal(s,'vertical'):self._rotation=90.else:raiseValueError("rotation must be 'vertical', 'horizontal' or "f"a number, not{s}")self.stale=True
Example
Temporarily adding print statements:
def_ha_for_angle(self,angle,is_tick_top_enabled):""" Determines horizontal alignment ('ha') based on the angle of rotation in degrees. Adjusts for is_tick_top_enabled. """print('angle:',angle)if (angle<5or85<=angle<105or355<=angle<360or170<=angle<190or265<=angle<275):return'center'elif5<=angle<85or190<=angle<265:return'left'ifis_tick_top_enabledelse'right'return'right'ifis_tick_top_enabledelse'left'def_va_for_angle(self,angle,is_tick_right_enabled):""" Determines vertical alignment ('va') based on the angle of rotation in degrees. Adjusts for is_tick_right_enabled. """print('angle:',angle)if (angle<5or355<=angle<360or170<=angle<190or85<=angle<105or265<=angle<275):return'center'elif190<=angle<265or5<=angle<85:return'baseline'ifis_tick_right_enabledelse'top'return'top'ifis_tick_right_enabledelse'baseline'
Exercising the code path:
importmatplotlib.pyplotaspltimportnumpyasnpangles,n_cols=np.linspace(-45,405,2),2fig,axs=plt.subplots(nrows=1,ncols=n_cols,figsize=(4,2.5))axs=axs.flatten()fori,angleinenumerate(angles):ax=axs[i]ax.set_title(f'Rotation:{angle:.0f}°',fontsize=10)ax.set_xticks([0.5])ax.set_xticklabels(['L'])ax.xaxis.set_tick_params(rotation=angle,rotation_mode='xtick')ax.set_yticks([0.5])ax.set_yticklabels(['L'])ax.yaxis.set_tick_params(rotation=angle,rotation_mode='ytick')plt.savefig('rotated_ticks_figure.png')plt.show()
Terminal:
angle: 315.0angle: 45.0I restricted the covered angles to [0,360) from [0,360] based on your suggestion. If you still want the modulo operations to be added to_va_for_angle and_ha_for_angle as a precaution, let me know. Thanks!
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
anntzer commentedOct 16, 2024
I didn't actually check the implementation, but does this also work with ticks on the right or the top? |
43d9c02 toe451e7fComparekyracho commentedOct 20, 2024
Thanks for the feedback! Added an image comparison test just now. I'll work on the "What's new" entry and the examples once I'm confident that the new changes are OK.
Thanks! It didn’t handle ticks on the right or top before, but I've updated it to work with those ticks now. |
c074a15 toab27d59CompareUh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
06c5dff to249bb58Compare
timhoffm left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The fundamental implementation is already correct. The following are merely quality and style improvements.
| forjinrange(2): | ||
| ax=axs[i][j] | ||
| ax.plot(np.arange(1.,0.,-0.1)*1000.,np.arange(1.,0.,-0.1)) | ||
| ax.set_title(f'Title{i}{j}') | ||
| ax.set_xlabel(f'XLabel{i}{j}') | ||
| ax.set_ylabel(f'YLabel{i}{j}') | ||
| if (i==0andj==1)or (i==1andj==0): | ||
| ifi==0andj==1: | ||
| ax.xaxis.tick_top() | ||
| ax.set_xticks(np.linspace(0,1000,5)) | ||
| ax.set_xticklabels([250*nforninrange(5)]) | ||
| ax.xaxis.set_tick_params(rotation=55,rotation_mode='xtick') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I agree that the example is a bit messy and could be improved, but it's largly orthogonal to the tick rotation topic. While one can do small additional improvements along the way, there's a couple of aspects I'd have to comment on.
I suggest to keep the change for this PR to the minimalax.tick_params(axis='x', rotation=55, rotation_mode='xtick'). If you are interested in improving the example, I'm happy to discuss that separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
TYSM for explaining so clearly, and for your hard work to help me contribute. I really appreciate it! 🩵
A few questions:
There seems to be an issue with
ax.tick_params(axis='x',rotation=55,rotation_mode='xtick')
It leads to
AttributeError: Line2D.set() got an unexpected keyword argument 'ion_mode'Tracing to
classTick(martist.Artist): ⋮def__init__(.**kwargs): ⋮grid_kw= {k[5:]:vfork,vinkwargs.items()}
The fix I came up with is
grid_kw= {k[5:]:vfork,vinkwargs.items()ifk!="rotation_mode"}
to filter outrotation_mode and prevention_mode from being passed to theLine2D object.
I'm not sure if it would be better to apply it separately, to the__init__ method, though?
ax.tick_params(axis='x',rotation=55,rotation_mode='xtick')
seems to applyrotation_mode to only the first tick on the axis. Is there a delayed creation of the ticks at render time? so therotation_mode isn't being applied to all the labels?
I've tried using
ax.set_xticks(ax.get_xticks())ax.tick_params(axis='x',rotation=55,rotation_mode='xtick')
which seems to create the ticks before settingtick_params, but I'm not sure if it's a good solution?
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
timhoffm commentedDec 3, 2024
@kyracho gentle ping. Do you plan to continue with this? |
fcd99fa toef26713Compare8e00cc3 to1466f04Compare
timhoffm left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This is good, apart from mixing the kw translation bugfix into the new feature.
lib/matplotlib/axis.py Outdated
| kwtrans= {} | ||
| foroldkey,newkeyinkeymap.items(): | ||
| ifnewkeyinkw_: | ||
| ifis_x_axisandnewkey=='label1On': | ||
| kwtrans['labelbottom']=kw_.pop(newkey) | ||
| elifis_x_axisandnewkey=='tick1On': | ||
| kwtrans['bottom']=kw_.pop(newkey) | ||
| elifis_x_axisandnewkey=='label2On': | ||
| kwtrans['labeltop']=kw_.pop(newkey) | ||
| elifis_x_axisandnewkey=='tick2On': | ||
| kwtrans['top']=kw_.pop(newkey) | ||
| else: | ||
| kwtrans[oldkey]=kw_.pop(newkey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'd like to have the bugfix separated from the rotation feature. Either as two commits in this PR, or (slightly perferred) as two separate PRs.
d08f106 tob670278CompareUh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
0a30c7e to6207976Compare09c58e5 to98778bbComparekyracho commentedJan 29, 2025
Hi@QuLogic , just checking in on this PR; whenever u have the time, I'd really appreciate your feedback. Please let me know if there is anything I can improve. Thank you! |
39c1d50 intomatplotlib:mainUh oh!
There was an error while loading.Please reload this page.
tacaswell commentedJan 31, 2025
Thank you@kyracho ! This is a pretty nice quality of life improvement. |
Follow-up tomatplotlib#28968.The rotation_mode was only wired up half way, so that the parameter wasaccepted but did not have any effect.
Follow-up tomatplotlib#28968.The rotation_mode was only wired up half way, so that the parameter wasaccepted but did not have any effect.
* Fix tick_params() label rotation modeFollow-up to#28968.The rotation_mode was only wired up half way, so that the parameter wasaccepted but did not have any effect.* Update axis.pyi

Uh oh!
There was an error while loading.Please reload this page.
Hello, the goal of this PR is to implement two new$[0,360)$ .
rotation_modetypes'xtick'and'ytick'for the axis labels.The idea is that these new rotation_modes automatically set the label alignments to match any
rotationin'xtick'aligns the x-axis labels, and 'ytick' aligns the y-axis labels.Closes#28951
Plots illustrating the proposed
'xtick'and'ytick'rotation modes:For bottom and left labels:
For top and right labels:
PR checklist