Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: get_ticklabels/set_ticklabels gives incorrect values in log plot #29284

Open
elimbaum opened this issue Dec 11, 2024 · 9 comments
Open
Labels
Community support Users in need of help.

Comments

@elimbaum
Copy link

Bug summary

In a certain scenario, adjusting the ylim of a log plot and updating the yticklabels gives numerically incorrect ticks while still plotting the correct values. We were calling get_/set_yticklabels because the cmss10 font didn't have a certain glyph which needed to be replaced, but the bug can be reproduced with the more minimal code below.

Code for reproduction

import matplotlib.pyplot as plt

plt.rcParams.update({
    "figure.figsize": (6, 2),
    "font.size": 12,
    "font.family": "cmss10",
})

x = ['A', 'B', 'C']
y = [10, 1000, 10000]

fig, ax = plt.subplots(layout='constrained')

# Plot and label data
b = ax.bar(x, y)
ax.bar_label(b, y)

# Log scale for vertical axis
ax.set_yscale('log')

# Give a bit extra space for the labels
plt.ylim(None, plt.ylim()[1] * 8)

# Originally was using this to replace special characters in the font
ax.set_yticklabels(ax.get_yticklabels())

plt.savefig('bug-output.png', dpi=300)

Actual outcome

bug-output

The y-axis ticks are incorrectly labeled.

Expected outcome

bug-output

Additional information

Changing plt.ylim(None, plt.ylim()[1] * 8) to plt.ylim(None, plt.ylim()[1] * 7) (that is, just a bit less extra room) fixes the issue. Granted, most other sets of changes also fix it: this appears to be an extremely idiosyncratic bug, so I'm not clear what the real issue is. Changing the plot size, removing 'constrained', changing the font, removing the ylim statement entirely... these all appear to fix it. It's just the specific combination of parameters that we happened to stumble into this.

Operating system

Mac OS

Matplotlib Version

3.9.3

Matplotlib Backend

macosx

Python version

3.10.2

Jupyter version

N/A

Installation

pip

@rcomer
Copy link
Member

rcomer commented Dec 11, 2024

There is a note in the set_yticklabels documentation that it should only be used if you have fixed the tick positions. You should also have seen a warning when you ran the code

UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
  ax.set_yticklabels(ax.get_yticklabels())

If you want to keep the automatic tick placement but change the formatting of the labels, have a look at Tick Formatters.

@rcomer rcomer added the Community support Users in need of help. label Dec 11, 2024
@elimbaum
Copy link
Author

Yes, we do get the warning. Does the tick auto-scaling mean that this mis-labeling is expected in certain cases?

@timhoffm
Copy link
Member

Yes, this can happen. tick instances are internally reused. Their position is moved by the locator, their text is updated by the formatter. If you set fixed texts only, ticks can still move, and then, your fixed text is shown in an unexpected position.

@rcomer
Copy link
Member

rcomer commented Dec 11, 2024

I wonder if we should make the documentation and/or warning more explicit, e.g "Use of set_ticklabels with unfixed tick positions may result in mislabelled ticks."

@elimbaum
Copy link
Author

Ok, so to clarify: by calling set_yticklabels, we "move" from having "dynamic ticks" to having "static ticks". When we move to static ticks, the auto-locator might move ticks around, and then (e.g.) tick[0] is no longer at the location corresponding to label[0].

The root of our issue was that font cmss10 doesn't have a certain glyph (unicode MINUS), so I was running a character replacement on that glyph (within mathmode, change - hypen to en-dash). Would the way to do that be to define a custom tick Formatter?

@timhoffm
Copy link
Member

timhoffm commented Dec 11, 2024

@rcomer The documentation already says

Otherwise, ticks are free to move and the labels may end up in unexpected positions.

and IIRC, the warning is issued from a quite generic place, which makes it difficult to give more specific directions.

But if you see a possibility for improving the wording that’s always welcome.

—-

On a side note, I would have expected that at least ax.set_yticklabels(ax.get_yticklabels()) right before the draw is safe, but maybe I’m overlooking something.

@elimbaum
Copy link
Author

elimbaum commented Dec 11, 2024

Ah, it gets even weirder: in my example at the top of this thread, dpi=100 outputs incorrectly, but dpi=90 works. So even something within savefig can change how many ticks you get based on dpi. The bug occurring appears to be connected to the appearance of the log-scale minor tick marks, maybe the render decides whether or not to draw these based on the number of available pixels?

At this point I understand why the bug happened but it was definitely unexpected.

Can the warning just be amended to:

UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator. Otherwise, ticks may be mislabeled.

@timhoffm
Copy link
Member

@elimbaum for the Unicode minus have a look at
https://matplotlib.org/stable/gallery/text_labels_and_annotations/unicode_minus.html

@elimbaum
Copy link
Author

Hmm, "axes.unicode_minus": False doesn't seem to have an effect here. But I can open a separate issue about that since it's not related.

Screenshot 2024-12-11 at 14 00 04

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community support Users in need of help.
Projects
None yet
Development

No branches or pull requests

3 participants