Skip to content

[ENH]: Better positioning of rotated tick labels #28951

Closed
@timhoffm

Description

@timhoffm

Problem

Tick labels are currently centered, also with rotation:

plt.bar(range(3), [1, 3, 2], tick_label=['very long tick labels'] * 3)
plt.tick_params(rotation=30)

image

The rotated labels do not "point" to the ticks anymore, which can make it difficult to see which label belongs to which tick - up to being outright misleading, e.g.

N = 20
plt.bar(range(N), range(N), tick_label=[f'tick label {i}' for i in range(N)])
plt.tick_params(rotation=45)

image

A common workaround is to adapt the alignment of the tick labels (e.g. demonstated in this example). The solution is not satisfying for two reasons:

  • The canonical way to adjust tick styles is tick_params, but we don't have alignment in there yet (and that has reasons, see No support for horizontal alignment in tick_params?  #13774). Therefore people have to resort to setting the alignment on the individual tick instances, which has the major disadvantage that it does not work for dynamically changing plots because tick instances are volatile.
  • When rotating ticks, I expect that it "just works" and I don't have to additionally fine tune the result.

Proposed solution

A new rotation algorithm that rotates the ticks appropriately. We can include this as a new rotation_mode type (see Text.set_rotation_mode).

Essentially, the labels should "point" towards their ticks. Note that this results in a very particular alignment: -90°, 0° and 90° want a centered alignment, whereas diagonal tick labels want a left/right alignment.

One can get approximately good results by tuning the alignment depending on the angle.

Example code (color coding for alignment: black: 'center', green: 'right', red: 'left')
import matplotlib.pyplot as plt
import numpy as np


def ha_for_angle(angle):
    if angle < -90 or angle > 90:
        raise ValueError('Todo: not thought about reasonable values for upside-down labels')
    if angle < -60:
        return 'center'
    if angle < -5:
        return 'left'
    if angle <= 5:
        return 'center'
    if angle <= 60:
        return 'right'
    if angle <= 90:
        return 'center'
   

fig, ax = plt.subplots(figsize=(18, 1))
angles = np.linspace(90, -90, 37)
N = len(angles)
ax.set_xticks(range(N), ['label']*N)
ax.set_xlim(-0.5, N -0.5)
tls = ax.xaxis.get_majorticklabels()
for ticklabel, angle in zip(tls, angles):
    ha = ha_for_angle(angle)
    color = {'left': 'r', 'center': 'k', 'right': 'g'}[ha]
    ticklabel.set(rotation=angle, ha=ha, color=color)

image

While one could try and come up with mathematical more accurate text position methods, this simple trick gives reasonably good results.

Concrete suggestion: Introduce rotation modes "xtick", "ytick" (exact names t.b.d.). "xtick" ignores the horizontal alignment setting and instead chooses a suitable one depending on the angle as above. Likewise "ytick" chooses a suitable vertical alignment.

Depending on how strong we feel about backward compatibility, we can either make these rotation modes the default for ticks. Or we are defensive and only add it to tick_params for now, so that one could write ax.xaxis.tick_params(rotation=45, rotation_mode='xtick').

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions