Repository navigation
Arabic nicknames render reversed in charts on matplotlib 3.11+ (_reshape_for_chart double-processes) #28
Description
Summary
_reshape_for_chart() in cogs/bear_track.py reverses Arabic and other RTL nicknames on
matplotlib 3.11 and newer. The chart still looks like Arabic, so it is easy to miss unless
someone who reads the script checks the output.
Cause
The helper's docstring states the assumption that no longer holds:
def _reshape_for_chart(text) -> str:
"""Shape Arabic for matplotlib (which doesn't run bidi itself)."""
...
return _bidi_get_display(_arabic_reshaper.reshape(text))That was correct for over a decade. matplotlib 3.11 added text shaping with libraqm
(matplotlib#30000), so it now shapes and applies the bidi algorithm itself. The helper therefore
does the work twice: reshape() converts to presentation forms, get_display() puts the string
into visual order, and matplotlib then reorders a string that is already visual.
requirements.txt pins matplotlib>=3.10.5 with no upper bound, so a fresh install today
resolves to 3.11.0 and hits this. Existing installs with a pinned older matplotlib are fine.
Reproduction
import matplotlib; matplotlib.use("Agg")
import matplotlib.pyplot as plt
import arabic_reshaper
from bidi.algorithm import get_display
nick = "محمد الفارس"
plt.figure(figsize=(9, 2))
plt.title(get_display(arabic_reshaper.reshape(nick)), fontsize=28) # current code path
plt.savefig("out.png")On matplotlib 3.11.0 the title renders as سرافلا دمحم. The letters and the word order are both
reversed. Passing nick straight through renders محمد الفارس correctly.
Affected path is bear_player_history_embed(): nickname goes through _reshape_for_chart() into
chart_nick, which becomes the chart title at line 2613.
Note that a font covering Arabic still has to be configured for matplotlib; that is a separate
issue from the ordering, and this report is only about the ordering.
Suggested fix
On matplotlib >= 3.11, pass the logical string through unchanged and drop the helper for the
chart path. If you need to keep supporting older matplotlib in the same code, one option that
orders correctly on both is wrapping the pre-processed string in U+202D / U+202C:
artext = "" + get_display(reshape(text)) + ""That was suggested by a matplotlib maintainer. One caveat from testing it across several fonts:
the wrapped string draws the font's static presentation forms rather than letting the font shape
the text, so joins can loosen in some fonts. The order is correct either way.
The simpler route, given the pin is already >=3.10.5, is probably to require matplotlib>=3.11
and delete the call.
Reference
matplotlib's upgrade guide now tells 3.11 upgraders to remove this workaround:
matplotlib/matplotlib#32263
I came across this while working on Arabic text rendering upstream, and ran a code search for the
pattern to find projects still carrying it. Happy to open a PR if you would prefer that.
The same helper, the same docstring and the same pins are present in
whiteout-project/bot, where I have filed this as
whiteout-project/bot#110. Filing separately here because it is a
separate project and neither can pick up the other's fix.
Activity
justncodes commentedon Sep 12, 2026
Confirmed, and thanks for the thorough report. Our updater only installs missing packages rather than enforcing version bumps, so requiring 3.11 would have left existing installs on 3.10 with no shaping at all; we've gone with a version gate instead (pass through on 3.11+, pre-shape below). The same pattern also existed in our attendance table reports and is fixed there too. Will ship in the next release.
README: the correct fix depends on the project's dependency floor
Profile: two of the four filed reports are confirmed and fixed
Advice strings: the right repair depends on the dependency floor