3

I'm trying to figure out how to rotate text in matplotlib to align to a curve in a plot, but I haven't figured what transformations give the proper coordinate system for rotating text to match a specific slope in data coordinates. Here's a minimal example of drawing a line and trying to align some text along it:

# Make some really non-square figure
plt.figure(figsize=(2,5))

# Draw some line between two points
pB=np.array((0,0))
pA=np.array((1,2))
pC=(pA+pB)/2
plt.plot(*zip(pA,pB))

# All the transforms at our disposal
tD=plt.gca().transData
tA=plt.gca().transAxes
tF=plt.gcf().transFigure

# Transform the endpoints of the line two some coordinate system
pA,pB=[
        ##### What goes here???
        p    # <- trivial no transform
        #tD.transform(p)
        #tA.inverted().transform(tD.transform(p))
        #tF.inverted().transform(tD.transform(p))
    for p in (pA,pB)]

# Then calculate the angle of the line
rise,run=pA-pB
rot=(180/np.pi)*np.arctan(rise/run)

# Draw some text at that angle
plt.text(pC[0],pC[1],'hi there',rotation=rot,
         horizontalalignment='center',verticalalignment='bottom');

No matter what I try, the text is still misoriented:

[this image is for the no-transform case above, rendered by the %matplotlib inline option in a Jupyter notebook.]

enter image description here

Sam Bader
  • 185
  • 1
  • 7
  • If you think about it, the rotation angle of the text is an absolute measure when the angle of the curve depends on the axis' scales. For a quick observation, change the size of the plot windows. So, I guess the calculus of the rotation angle should somehow involves information about the axis. – Uncle Ben Ben Jun 25 '18 at 17:17
  • @UncleBenBen I agree with that intuition, and, in the question, I chose a really narrow figure size specifically to make the misorientation obvious. Nonetheless, all three of the transforms attempted (in comments in the code above) take that information into account and still don't capture the angle properly, so I'm at a loss. – Sam Bader Jun 25 '18 at 20:17

1 Answers1

3

As suggested in the comments, you may want to include the proportional relationship between the abstract representation (axis size) and the actual figure (figure size). The rotation angle of the text is an absolute measure when the angle of the curve depends on the axis' scales

# define the figure size
fig_x, fig_y = 4, 8
plt.figure(figsize=(fig_x, fig_y))

# Draw some line between two points
pB = np.array((0, 0))
pA = np.array((1, 2))
pC = (pA+pB)/2
plt.plot(*zip(pA, pB))

# Calculate the angle of the line
dx, dy = pA-pB
# --- retrieve the 'abstract' size
x_min, x_max = plt.xlim()
y_min, y_max = plt.ylim()
# --- apply the proportional conversion
Dx = dx * fig_x / (x_max - x_min)
Dy = dy * fig_y / (y_max - y_min)
# --- convert gaps into an angle
angle = (180/np.pi)*np.arctan( Dy / Dx)

# Draw  text at that angle
plt.text(pC[0], pC[1], 'it worked', rotation=angle,
         horizontalalignment='center', verticalalignment='bottom')
plt.show()

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Uncle Ben Ben
  • 482
  • 3
  • 11
  • Thank you! This works perfectly and I'll mark it accepted! Though if you have any understanding of why that math is different from `tF.inverted().transform(tD.transform(p))` I'd be curious. Because your above code is kind of what I assumed this chain of transformations would do behind the scenes, namely transform from data coordinates to figure coordinates. – Sam Bader Jun 26 '18 at 05:33