在 matplotlib 中以一定角度绘制直角三角形时遇到问题

Trouble plotting a right triangle at an angle in matplotlib

我正在尝试生成一个斜边 = 1、内角 25、底角旋转 30 度的直角三角形。根据我的理解,我已经正确输入了三角公式,但我怀疑存在某种舍入错误。因为在 matplot 中生成的三角形与直角三角形略有不同。

import math
import matplotlib.pyplot as plt
from annotation import label

angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle base and the x axis.

point_A = (0,0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))

# Label our points
label(plt, 'A', point_A)
label(plt, 'B', point_B)
label(plt, 'C', point_C)

# Draw the right triangle between our points.
plt.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')

如您所见,角度 ACB 不是直角 trigonometry would predict

@Mad Physicist 指出了 ABC 确实看起来是直角三角形的评论。我想让 ACB 成为一个直角,比如 this example.

下面是angle_a形成的直角三角形:

point_D = (math.cos(angle_a) * math.cos(angle_b), 0)
plt.plot(*zip(point_A, point_C, point_D, point_A), marker='o', color='black')
label(plt, 'D', point_D) 

现已解决。由于 ax.set_aspect('equal')

行,我终于让它工作了

你好像搞错了三角函数。我将建议逐步缓慢移动,并使用可帮助您记住正在发生的事情的变量名称。

让我们从原来的三角形开始。如果interior_angle = np.deg2rad(25)且斜边的长度为1,则x轴上的直角在(np.cos(interior_angle), 0)处,另一个内角在(np.cos(interior_angle), np.sin(interior_angle))处。你图表中的两个点都不对应。

现在让我们将三角形表示为以顶点为列的矩阵:

interior_angle = np.deg2rad(25)
vertices = np.array([
    [0, np.cos(interior_angle), np.cos(interior_angle), 0],
    [0, 0, np.sin(interior_angle), 0],
])

最后一个顶点是原点的重复,使绘图更容易。

现在让我们看看旋转。对于 rotation_angle = np.deg2rad(30),点 (x, y) 旋转到 (np.cos(rotation_angle) * x - np.sin(rotation_angle) * y, np.sin(rotation_angle) * x + np.cos(rotation_angle) * y)。这可以表示为矩阵方程:

rotation_matrix = np.array([
    [np.cos(rotation_angle), -np.sin(rotation_angle)],
    [np.sin(rotation_angle), np.cos(rotation_angle)]])
p_out = rotation_matrix @ p_in

数组vertices是这样构造的,可以直接乘以一个旋转矩阵。因此你可以写

rotation_angle = np.deg2rad(30)
rotation_matrix = np.array([
    [np.cos(rotation_angle), -np.sin(rotation_angle)],
    [np.sin(rotation_angle), np.cos(rotation_angle)]])
rotated_vertices = rotation_matrix @ vertices

绘制的图像现在应该更有意义了:

plt.plot(*vertices)
plt.plot(*rotated_vertices)
plt.axis('equal')
plt.show()

问题是默认情况下,matplotlib 在 x 和 y 方向上不使用相同的距离。相反,matplotlib 会尝试将所有内容很好地放入给定范围内。

这些不均匀的距离会扭曲角度,也会使圆变形。

您可以通过 ax.set_aspect('equal').

强制设置相同的宽高比

要通过角度计算位置,并在 B 点有右角,您需要考虑长度 AC 是 AC 长度的 cos(b) 倍。可以选择 AC 的长度为 1。或者,您可以将 B 和 C 除以 cos(b) 以获得更大的三角形,其中 AB 的长度为 1.

import matplotlib.pyplot as plt
import math

angle_b = math.radians(25)  # the interior 25 degree angle of our right triangle
angle_a = math.radians(30)  # 30 degree angle between the plotted angle_b triangle and the x axis.

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 5), sharey=True)

for ax in (ax1, ax2):
    point_A = (0, 0)
    if ax == ax1:
        point_B = (math.cos(angle_a + angle_b) * math.cos(angle_b), math.sin(angle_a + angle_b) * math.cos(angle_b))
        point_C = (math.cos(angle_a), math.sin(angle_a))
        ax.set_title('length AC is 1')
    else:
        point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
        point_C = (math.cos(angle_a) / math.cos(angle_b), math.sin(angle_a) / math.cos(angle_b))
        ax.set_title('length AB is 1')
    point_M = ((point_A[0] + point_C[0]) / 2, (point_A[1] + point_C[1]) / 2)

    # Draw the right triangle between our points.
    ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
    # draw a circle around the 3 points
    ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
                            ec='r', ls='--', fc='none'))
    ax.set_aspect('equal', 'datalim')

plt.show()

同样的计算适用于任何角度。这是以 30 度为步长旋转 12 次的效果:

下面的代码展示了ax.set_aspect('equal')对原始点的影响

import matplotlib.pyplot as plt
import math

angle_b = math.radians(25)  # the interior 25 degree angle of our right triangle
angle_a = math.radians(30)  # 30 degree angle between the plotted angle_b triangle and the x axis.

point_A = (0, 0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))

point_M = ((point_A[0] + point_B[0]) / 2, (point_A[1] + point_B[1]) / 2)

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 4), gridspec_kw={'width_ratios': [2, 1]})

for ax in (ax1, ax2):
    # Draw the right triangle between our points.
    ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
    # draw a circle around the 3 points
    ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
                            ec='r', ls='--', fc='none'))
ax1.set_title('Default aspect ratio, deforms the angles')
ax2.set_aspect('equal')  # or plt.axis('equal')
ax2.set_title('Equal aspect ratio')
plt.tight_layout()
plt.show()