Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to annotate text along curved lines in Python?

I am trying to annotate text in plots so that they follow the curvature of the line. I have the following plot:

enter image description here

And this is what I want to obtain, if I fix a particular y value for the annotation, for each curve it should place the annotation along the curve at the required slope (i.e. it should follow the curvature of the curve) as shown below:

enter image description here

The reproducible code for the plot without annotations is:

import numpy as np
import matplotlib.pyplot as plt

x = np.array([[53.4, 57.6, 65.6, 72.9],
            [60.8, 66.5, 73.1, 83.3],
            [72.8, 80.3, 87.2, 99.3],
            [90.2, 99.7, 109.1, 121.9],
            [113.6, 125.6, 139.8, 152]])

y = np.array([[5.7, 6.4, 7.2, 7.8],
            [5.9, 6.5, 7.2, 7.9],
            [6.0, 6.7, 7.3, 8.0],
            [6.3, 7.0, 7.6, 8.2],
            [6.7, 7.5, 8.2, 8.7]])

plt.figure(figsize=(5.15, 5.15))
plt.subplot(111)
for i in range(len(x)):
    plt.plot(x[i, :] ,y[i, :])
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

How to place such texts in Python with matplotlib?

like image 379
Tom Kurushingal Avatar asked Apr 21 '15 07:04

Tom Kurushingal


1 Answers

You can get the gradient in degrees and use that in matplotlib.text.Text with the rotate argument

rotn = np.degrees(np.arctan2(y[:,1:]-y[:,:-1], x[:,1:]-x[:,:-1]))

EDIT: so it's a bit messier than I suggested as the plot area is scaled to match data and has margins etc. but you get the idea

...
plt.figure(figsize=(7.15, 5.15)) #NB I've changed the x size to check it didn't distort
plt.subplot(111)
for i in range(len(x)):
    plt.plot(x[i, :] ,y[i, :])

rng = plt.axis()
x_scale = 7.15 * 0.78 / (rng[1] - rng[0])         
y_scale = 5.15 * 0.80 / (rng[3] - rng[2])          
rotn = np.degrees(np.arctan2((y[:,1:]-y[:,:-1]) * y_scale,
                              x[:,1:]-x[:,:-1]) * x_scale)
labls = ['first', 'second', 'third', 'fourth', 'fifth']
for i in range(len(x)):
    plt.annotate(labls[i], xy=(x[i,2], y[i,2]), rotation=rotn[i,2])

plt.xlabel('X')

RE-EDIT noticed that the scaling was wrong but just happened to work by coincidence! Also the xy values of labels are a little approximate because of scaling.

like image 192
paddyg Avatar answered Oct 22 '22 23:10

paddyg