Have a plot with several diagonal lines with different slopes. I would like to annotate these lines with a text label that matches the slope of the lines.
Something like this:
Is there a robust way to do this?
I've tried both text
's and annotate
's rotation parameters, but those are in screen coordinates, not data coordinates (i.e. it's always x
degrees on the screen no matter the xy ranges). My x
and y
ranges differ by orders of magnitude, and obviously the apparent slope is affected by viewport size among other variables, so a fixed-degree rotation doesn't do the trick. Any other ideas?
Rotate X-Axis Tick Labels in Matplotlib There are two ways to go about it - change it on the Figure-level using plt. xticks() or change it on an Axes-level by using tick. set_rotation() individually, or even by using ax.
The annotate() function in pyplot module of matplotlib library is used to annotate the point xy with text s. Parameters: This method accept the following parameters that are described below: s: This parameter is the text of the annotation. xy: This parameter is the point (x, y) to annotate.
I came up with something that works for me. Note the grey dashed lines:
The rotation must be set manually, but this must be done AFTER draw()
or layout. So my solution is to associate lines with annotations, then iterate through them and do this:
This isn't perfect, because matplotlib's handling of rotated text is all wrong. It aligns by the bounding box and not by the text baseline.
Some font basics if you're interested about text rendering: http://docs.oracle.com/javase/tutorial/2d/text/fontconcepts.html
This example shows what matplotlib does: http://matplotlib.org/examples/pylab_examples/text_rotation.html
The only way I found to have a label properly next to the line is to align by center in both vertical and horizontal. I then offset the label by 10 points to the left to make it not overlap. Good enough for my application.
Here is my code. I draw the line however I want, then draw the annotation, then bind them with a helper function:
line, = fig.plot(xdata, ydata, '--', color=color) # x,y appear on the midpoint of the line t = fig.annotate("text", xy=(x, y), xytext=(-10, 0), textcoords='offset points', horizontalalignment='left', verticalalignment='bottom', color=color) text_slope_match_line(t, x, y, line)
Then call another helper function after layout but before savefig
(For interactive images I think you'll have to register for draw events and call update_text_slopes
in the handler)
plt.tight_layout() update_text_slopes()
The helpers:
rotated_labels = [] def text_slope_match_line(text, x, y, line): global rotated_labels # find the slope xdata, ydata = line.get_data() x1 = xdata[0] x2 = xdata[-1] y1 = ydata[0] y2 = ydata[-1] rotated_labels.append({"text":text, "line":line, "p1":numpy.array((x1, y1)), "p2":numpy.array((x2, y2))}) def update_text_slopes(): global rotated_labels for label in rotated_labels: # slope_degrees is in data coordinates, the text() and annotate() functions need it in screen coordinates text, line = label["text"], label["line"] p1, p2 = label["p1"], label["p2"] # get the line's data transform ax = line.get_axes() sp1 = ax.transData.transform_point(p1) sp2 = ax.transData.transform_point(p2) rise = (sp2[1] - sp1[1]) run = (sp2[0] - sp1[0]) slope_degrees = math.degrees(math.atan(rise/run)) text.set_rotation(slope_degrees)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With