我对使用matplotlib相当新,但找不到任何显示两条线的示例,并且它们之间的角度也是如此。
这是我目前的形象:
这是我想要实现的一个例子:
我通常会看一下Matplotlib gallery来了解如何执行某些任务,但似乎没有任何相似之处。
答案 0 :(得分:20)
您可以使用matplotlib.patches.Arc
绘制相应角度测量的弧线。
绘制角度弧:
定义一个可以拍摄2 matplotlib.lines.Line2D
个物体的函数,计算角度并返回一个matplotlib.patches.Arc
物体,您可以将其与线条一起添加到地块中。
def get_angle_plot(line1, line2, offset = 1, color = None, origin = [0,0], len_x_axis = 1, len_y_axis = 1):
l1xy = line1.get_xydata()
# Angle between line1 and x-axis
slope1 = (l1xy[1][1] - l1xy[0][2]) / float(l1xy[1][0] - l1xy[0][0])
angle1 = abs(math.degrees(math.atan(slope1))) # Taking only the positive angle
l2xy = line2.get_xydata()
# Angle between line2 and x-axis
slope2 = (l2xy[1][3] - l2xy[0][4]) / float(l2xy[1][0] - l2xy[0][0])
angle2 = abs(math.degrees(math.atan(slope2)))
theta1 = min(angle1, angle2)
theta2 = max(angle1, angle2)
angle = theta2 - theta1
if color is None:
color = line1.get_color() # Uses the color of line 1 if color parameter is not passed.
return Arc(origin, len_x_axis*offset, len_y_axis*offset, 0, theta1, theta2, color=color, label = str(angle)+u"\u00b0")
打印角度值:
如果您希望角度值以内联方式显示,请参阅this SO问题,了解如何在matplotlib中打印内联标签。请注意,您必须打印弧的标签。
我做了一个小函数,它提取弧的顶点并尝试计算角度文本的坐标。
这可能不是最佳的,并且可能无法适用于所有角度值。
def get_angle_text(angle_plot):
angle = angle_plot.get_label()[:-1] # Excluding the degree symbol
angle = "%0.2f"%float(angle)+u"\u00b0" # Display angle upto 2 decimal places
# Get the vertices of the angle arc
vertices = angle_plot.get_verts()
# Get the midpoint of the arc extremes
x_width = (vertices[0][0] + vertices[-1][0]) / 2.0
y_width = (vertices[0][5] + vertices[-1][6]) / 2.0
#print x_width, y_width
separation_radius = max(x_width/2.0, y_width/2.0)
return [ x_width + separation_radius, y_width + separation_radius, angle]
或者您可以随时手动预先计算标签点,然后使用text
显示角度值。您可以使用label
方法从Arc
对象的get_label()
获取角度值(因为我们已将标签设置为角度值+ unicode度符号)。
上述功能的使用示例:
fig = plt.figure()
line_1 = Line2D([0,1], [0,4], linewidth=1, linestyle = "-", color="green")
line_2 = Line2D([0,4.5], [0,3], linewidth=1, linestyle = "-", color="red")
ax = fig.add_subplot(1,1,1)
ax.add_line(line_1)
ax.add_line(line_2)
angle_plot = get_angle_plot(line_1, line_2, 1)
angle_text = get_angle_text(angle_plot)
# Gets the arguments to be passed to ax.text as a list to display the angle value besides the arc
ax.add_patch(angle_plot) # To display the angle arc
ax.text(*angle_text) # To display the angle value
ax.set_xlim(0,7)
ax.set_ylim(0,5)
如果您不关心角度文本的内联放置。您可以使用plt.legend()
打印角度值。
最后:
plt.legend()
plt.show()
函数offset
中的get_angle_plot
参数用于指定弧的psudo-radius值。
当角度弧相互重叠时,这将非常有用。
(在这个图中,就像我说的那样,我的get_angle_text
函数在放置文本值时并不是最优的,但是应该让你知道如何计算点数。)
添加第三行:
line_3 = Line2D([0,7], [0,1], linewidth=1, linestyle = "-", color="brown")
ax.add_line(line_3)
angle_plot = get_angle_plot(line_1, line_3, 2, color="red") # Second angle arc will be red in color
angle_text = get_angle_text(angle_plot)
ax.add_patch(angle_plot) # To display the 2nd angle arc
ax.text(*angle_text) # To display the 2nd angle value
答案 1 :(得分:1)
从@ user3197452获取想法是我使用的。该版本结合了text
,并且还处理了比例轴比率。
def add_corner_arc(ax, line, radius=.7, color=None, text=None, text_radius=.5, text_rotatation=0, **kwargs):
''' display an arc for p0p1p2 angle
Inputs:
ax - axis to add arc to
line - MATPLOTLIB line consisting of 3 points of the corner
radius - radius to add arc
color - color of the arc
text - text to show on corner
text_radius - radius to add text
text_rotatation - extra rotation for text
kwargs - other arguments to pass to Arc
'''
lxy = line.get_xydata()
if len(lxy) < 3:
raise ValueError('at least 3 points in line must be available')
p0 = lxy[0]
p1 = lxy[1]
p2 = lxy[2]
width = np.ptp([p0[0], p1[0], p2[0]])
height = np.ptp([p0[1], p1[1], p2[1]])
n = np.array([width, height]) * 1.0
p0_ = (p0 - p1) / n
p1_ = (p1 - p1)
p2_ = (p2 - p1) / n
theta0 = -get_angle(p0_, p1_)
theta1 = -get_angle(p2_, p1_)
if color is None:
# Uses the color line if color parameter is not passed.
color = line.get_color()
arc = ax.add_patch(Arc(p1, width * radius, height * radius, 0, theta0, theta1, color=color, **kwargs))
if text:
v = p2_ / np.linalg.norm(p2_)
if theta0 < 0:
theta0 = theta0 + 360
if theta1 < 0:
theta1 = theta1 + 360
theta = (theta0 - theta1) / 2 + text_rotatation
pt = np.dot(rotation_transform(theta), v[:,None]).T * n * text_radius
pt = pt + p1
pt = pt.squeeze()
ax.text(pt[0], pt[1], text,
horizontalalignment='left',
verticalalignment='top',)
return arc
get_angle
功能是我发布的here,但为了完整性再次复制。
def get_angle(p0, p1=np.array([0,0]), p2=None):
''' compute angle (in degrees) for p0p1p2 corner
Inputs:
p0,p1,p2 - points in the form of [x,y]
'''
if p2 is None:
p2 = p1 + np.array([1, 0])
v0 = np.array(p0) - np.array(p1)
v1 = np.array(p2) - np.array(p1)
angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
return np.degrees(angle)
def rotation_transform(theta):
''' rotation matrix given theta
Inputs:
theta - theta (in degrees)
'''
theta = np.radians(theta)
A = [[np.math.cos(theta), -np.math.sin(theta)],
[np.math.sin(theta), np.math.cos(theta)]]
return np.array(A)
要使用它,可以这样做:
ax = gca()
line, = ax.plot([0, 0, 2], [-1, 0, 0], 'ro-', lw=2)
add_corner_arc(ax, line, text=u'%d\u00b0' % 90)
答案 2 :(得分:1)
我编写了一个函数来创建一个带有几个有用参数的 matplotlib Arc 对象。它也适用于在原点不相交的线。对于一组给定的两条线,用户可能想要绘制许多可能的弧线。这个函数让一个人使用参数指定哪一个。在圆弧和原点之间的中点绘制文本。欢迎在评论中或 gist containing this function 上进行改进。
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
Arc = matplotlib.patches.Arc
def halfangle(a, b):
"Gets the middle angle between a and b, when increasing from a to b"
if b < a:
b += 360
return (a + b)/2 % 360
def get_arc_patch(lines, radius=None, flip=False, obtuse=False, reverse=False, dec=0, fontsize=8):
"""For two sets of two points, create a matplotlib Arc patch drawing
an arc between the two lines.
lines: list of lines, of shape [[(x0, y0), (x1, y1)], [(x0, y0), (x1, y1)]]
radius: None, float or tuple of floats. If None, is set to half the length
of the shortest line
orgio: If True, draws the arc around the point (0,0). If False, estimates
the intersection of the lines and uses that point.
flip: If True, flips the arc to the opposite side by 180 degrees
obtuse: If True, uses the other set of angles. Often used with reverse=True.
reverse: If True, reverses the two angles so that the arc is drawn
"the opposite way around the circle"
dec: The number of decimals to round to
fontsize: fontsize of the angle label
"""
import numpy as np
from matplotlib.patches import Arc
linedata = [np.array(line.T) for line in lines]
scales = [np.diff(line).T[0] for line in linedata]
scales = [s[1] / s[0] for s in scales]
# Get angle to horizontal
angles = np.array([np.rad2deg(np.arctan(s/1)) for s in scales])
if obtuse:
angles[1] = angles[1] + 180
if flip:
angles += 180
if reverse:
angles = angles[::-1]
angle = abs(angles[1]-angles[0])
if radius is None:
lengths = np.linalg.norm(lines, axis=(0,1))
radius = min(lengths)/2
# Solve the point of intersection between the lines:
t, s = np.linalg.solve(np.array([line1[1]-line1[0], line2[0]-line2[1]]).T, line2[0]-line1[0])
intersection = np.array((1-t)*line1[0] + t*line1[1])
# Check if radius is a single value or a tuple
try:
r1, r2 = radius
except:
r1 = r2 = radius
arc = Arc(intersection, 2*r1, 2*r2, theta1=angles[1], theta2=angles[0])
half = halfangle(*angles[::-1])
sin = np.sin(np.deg2rad(half))
cos = np.cos(np.deg2rad(half))
r = r1*r2/(r1**2*sin**2+r2**2*cos**2)**0.5
xy = np.array((r*cos, r*sin))
xy = intersection + xy/2
textangle = half if half > 270 or half < 90 else 180 + half
textkwargs = {
'x':xy[0],
'y':xy[1],
's':str(round(angle, dec)) + "°",
'ha':'center',
'va':'center',
'fontsize':fontsize,
'rotation':textangle
}
return arc, textkwargs
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
# lines are formatted like this: [(x0, y0), (x1, y1)]
line1 = np.array([(1,-2), (3,2)])
line2 = np.array([(2,2), (2,-2)])
lines = [line1, line2]
fig, AX = plt.subplots(nrows=2, ncols=2)
for ax in AX.flatten():
for line in lines:
x,y = line.T
ax.plot(x,y)
ax.axis('equal')
ax1, ax2, ax3, ax4 = AX.flatten()
arc, angle_text = get_arc_patch(lines)
ax1.add_artist(arc)
ax1.set(title='Default')
ax1.text(**angle_text)
arc, angle_text = get_arc_patch(lines, flip=True)
ax2.add_artist(arc)
ax2.set(title='flip=True')
ax2.text(**angle_text)
arc, angle_text = get_arc_patch(lines, obtuse=True, reverse=True)
ax3.add_artist(arc)
ax3.set(title='obtuse=True, reverse=True')
ax3.text(**angle_text)
arc, angle_text = get_arc_patch(lines, radius=(2,1))
ax4.add_artist(arc)
ax4.set(title='radius=(2,1)')
ax4.text(**angle_text)
plt.tight_layout()
plt.show()
答案 3 :(得分:1)
我一直在寻找更多合一的解决方案,并找到了 AngleAnnotation 类。我强烈推荐它。
<块引用>用圆弧标记线条或内部形状之间的角度通常很有用。虽然 Matplotlib 提供了一个 Arc,但直接将其用于此类目的时的一个固有问题是数据空间中的圆弧在显示空间中不一定是圆的。此外,弧的半径通常最好在独立于实际数据坐标的坐标系中定义 - 至少如果您希望能够自由放大您的绘图而注释不会增长到无穷大。
你可以在这里找到它https://matplotlib.org/stable/gallery/text_labels_and_annotations/angle_annotation.html 我将它保存为 AngleAnnotation.py(当然你可以用不同的方式命名)在我的工作目录中,并用
将它导入到我的代码中from AngleAnnotation import AngleAnnotation
这是我如何使用它的片段:
...
#intersection of the two lines
center = (0.0,0.0)
#any point (other than center) on one line
p1 = (6,2)
# any point (other than center) on the other line
p2 = (6,0)
# you may need to switch around p1 and p2 if the arc is drawn enclosing the lines instead
# of between
# ax0 is the axes in which your lines exist
# size sets how large the arc will be
# text sets the label for your angle while textposition lets you rougly set where the label is, here "inside"
# you can pass kwargs to the textlabel using text_kw=dict(...)
# especially useful is the xytext argument which lets you customize the relative position of your label more precisely
am1 = AngleAnnotation(center, p1, p2, ax=ax0, size=130, text="some_label", textposition = "inside", text_kw=dict(fontsize=20, xytext = (10,-5)))
您可以在上面的链接中找到更多详细信息。它现在在 matplotlib 3.4.2 上对我有用。