Description
Bug summary
plt.text(...) is not rotating text correctly. This becomes clear when looking at their bounding boxes, where you can see that a character's position within the bounding box differs depending on the rotation (and sometimes, the text will even exit the box).
Code for reproduction
import matplotlib.pyplot as plt
def plot_rotation_period(y, rotation, r):
c0 = plt.gca().annotate('.', xy=(0.5, y), xytext=(0.5, y), rotation=rotation, fontsize=80,
rotation_mode='anchor', fontfamily='monospace', va='bottom', ha='center',
transform_rotates_text = False)
bb0 = c0.get_window_extent(renderer=r).transformed(plt.gca().transData.inverted())
rect0 = plt.Rectangle((bb0.x0, bb0.y0), bb0.width, bb0.height,
facecolor="C1", alpha=0.3, zorder=2)
plt.gca().add_patch(rect0)
if __name__ == '__main__':
fig = plt.gcf()
r = fig.canvas.get_renderer()
fig.set_size_inches(15, 15)
plt.xlim(0, 1)
plt.ylim(0, 1)
plot_rotation_period(0.8, 0, r)
print('1')
plot_rotation_period(0.6, 90, r)
print('2')
plot_rotation_period(0.4, 180, r)
print('3')
plot_rotation_period(0.2, 270, r)
plt.plot([0.5, 0.5], [0, 1], linestyle='--')
plt.show()
# plt.savefig('example_C.png') # this issue becomes a bit clearer if you do plt.savefig()
Actual outcome
These examples were created from the code above, which rotates the text 0/90/180/270 degrees and plots its bonding box. When the character is a ".", it looks like this: https://imgur.com/dBgTRBA
The "." makes the issue obvious, although this issue appears for all characters, e.g., "C": https://imgur.com/a/u3UamAb
Changing the horizontal/vertical alignment does not fix this. I tried every possible combination, and although it moves the text around, it never does so never in the correct way.
Expected outcome
Correct outcome for "C": https://imgur.com/2XT7wdA
Correct outcome for ".": https://imgur.com/Zx22cV1
Here, we can see that the character is in the same position relative to its bounding box for each possible rotation.
Additional information
I took a stab at trying to fix this. It seems like the issue is related to the Descent and/or Offset/Bearing of the font.
I took a look at backend_agg.py, where the text is being drawn. My solution may is quite crude and violates Chesterton's fence. However, I found that if I comment out some lines in backend_agg.RendererAgg.draw_text(...), this fixes the issue (highlighted here):
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
# docstring inherited
if ismath:
return self.draw_mathtext(gc, x, y, s, prop, angle)
flags = get_hinting_flag()
font = self._get_agg_font(prop)
if font is None:
return None
# We pass '0' for angle here, since it will be rotated (in raster
# space) in the following call to draw_text_image).
font.set_text(s, 0, flags=flags)
font.draw_glyphs_to_bitmap(
antialiased=mpl.rcParams['text.antialiased'])
d = font.get_descent() / 64.0
#The descent needs to be adjusted for the angle. [original comment from the package]
# xo, yo = font.get_bitmap_offset() [I deleted these next lines, commented out here]
# xo /= 64.0
# yo /= 64.0
# xd = d * sin(radians(angle))
# yd = d * cos(radians(angle))
# x = round(x + xo + xd)
# y = round(y + yo + yd)
self._renderer.draw_text_image(font, x, y + 1, angle, gc)
By commenting out those lines, I was able to generate those expected outcome pictures above. I tried this for serif fonts and different vertical/horizontal alignments, and it seems to work as expected. Should I do a PR (or would further tests be needed, if so which)?
I'm having trouble understanding why this code would be here in the first place. Maybe there was some code changed upstream/downstream which already addresses the offset/descent issue, and so the code I pointed out is doing it again, leading to problems?
I think someone before tried to come up with a solution for a related problem (see issue), although their fix was not accepted.
Operating system
Windows 10
Matplotlib Version
3.4.3
Matplotlib Backend
Qt5Agg
Python version
3.8.12
Jupyter version
No response
Installation
conda