Reputation: 24705
The following code puts some points on the plane and draws a line from center to each point. For each point, there is a label and want to put the label after the point. Therefore, from center, we see a line, then a point and then a text. I want to put the label with the same slope of the line.
Currently, I have this code, but as you can see the rotated text is not properly aligned. How can I fix that?
import matplotlib.pyplot as plt
import numpy as np
from math import *
a = np.array([
[-0.108,0.414],
[0.755,-0.152],
[0.871,-0.039],
],)
labels = ["XXXXXXX", "YYYYYY", "ZZZZZZZ"]
x, y = a.T
plt.scatter(x, y)
plt.xlim(-1,1)
plt.ylim(-1,1)
ax = plt.axes()
for i in range(a.shape[0]):
px = a[i,0]
py = a[i,1]
ax.arrow(0, 0, px, py, head_width=0, head_length=0.1, length_includes_head=True)
angle = degrees(atan(py/px))
ax.annotate(labels[i], (px, py), rotation=angle)
plt.grid(True)
plt.show()
UPDATE:
I used the solution proposed here and modified
text_plot_location = np.array([0.51,0.51])
trans_angle = plt.gca().transData.transform_angles(np.array((45,)),text_plot_location.reshape((1,2)))[0]
ax.annotate(labels[i], (px, py), rotation=text_plot_location)
However, I get this error TypeError: unhashable type: 'numpy.ndarray'
Upvotes: 0
Views: 612
Reputation: 30579
Not ideal but a bit closer to what you want. The drawback is the arbitrary value of 30 points for the text offset that works for the given labels but needs to be adjusted for longer or shorter labels.
import matplotlib.pyplot as plt
import numpy as np
from math import *
a = np.array([[-0.108,0.414],[0.755,-0.152],[0.871,-0.039]])
labels = ["XXXXXXX", "YYYYYY", "ZZZZZZZ"]
x, y = a.T
plt.scatter(x, y)
plt.xlim(-1,1)
plt.ylim(-1,1)
ax = plt.axes()
for i in range(a.shape[0]):
px = a[i,0]
py = a[i,1]
ax.arrow(0, 0, px, py, head_width=0, head_length=0.1, length_includes_head=True)
angle = atan(py/px)
d = (-1 if px < 0 else 1) * 30
ax.annotate(labels[i], (px, py), rotation=degrees(angle), textcoords="offset points",
xytext=(d*cos(angle), d*sin(angle)),
verticalalignment='center', horizontalalignment='center')
plt.grid(True)
plt.show()
Upvotes: 1
Reputation: 2058
You made a simple mistake in your update. You need to pass trans_angle
to the rotation
key word instead of text_plot_location
, however, I'm not sure if the result is what you are looking for.
import matplotlib.pyplot as plt
import numpy as np
from math import *
a = np.array([
[-0.108,0.414],
[0.755,-0.152],
[0.871,-0.039],
],)
labels = ["XXXXXXX", "YYYYYY", "ZZZZZZZ"]
x, y = a.T
plt.scatter(x, y)
plt.xlim(-1,1)
plt.ylim(-1,1)
ax = plt.axes()
for i in range(a.shape[0]):
px = a[i, 0]
py = a[i, 1]
ax.arrow(0, 0, px, py, head_width=0, head_length=0.1,
length_includes_head=True)
text_plot_location = np.array([0.51, 0.51])
angle = degrees(atan(py / px))
trans_angle = plt.gca().transData.transform_angles(
np.array((angle,)), text_plot_location.reshape((1, 2))
)[0]
ax.annotate(labels[i], (px, py), rotation=trans_angle)
plt.grid(True)
plt.show()
Upvotes: 0
Reputation: 1703
The link by @mapf is a bit cleaner, but this is what I came up with:
import matplotlib.pyplot as plt
import numpy as np
a = np.array([
[-0.108,0.414],
[0.755,-0.152],
[0.871,-0.039],
],)
labels = ["XXXXXXX", "YYYYYY", "ZZZZZZZ"]
x, y = a.T
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
line, = ax.plot(*a.T)
for jdx, (label, point) in enumerate(zip(labels, a)):
# find closest point
tmp = np.linalg.norm(a - point, axis = 1)
idx = np.argsort(tmp)[1]
other = a[idx]
# compute angle
deg = np.angle(complex(*(point - other)))
deg = np.rad2deg(deg)
ax.annotate(label, point, rotation = deg,
ha = 'left', va = 'baseline',
transform = ax.transData)
ax.grid(True)
fig.show()
I am not sure why the angle does not match the line exactly.
Upvotes: 1