Лучший способ построить угол между двумя линиями в Matplotlib

Я новичок в использовании matplotlib и не могу найти примеры, показывающие две строки с углом между ними.

Это мое текущее изображение: enter image description here

И это пример того, чего я хочу достичь:

enter image description here

Я обычно смотрю галерею Matplotlib, чтобы получить представление о том, как выполнять определенные задачи, но похоже, что ничего подобного не было,

Ответ 1

Вы можете использовать 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() (поскольку мы установили метку в значение угла + символ степени юникода).

Пример использования вышеуказанных функций:

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()

Angle plot with 2 Lines

Параметр 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

Angle plot with 3 Lines

Ответ 2

Взяв идею из @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 функция - это то, что я опубликовал здесь, но скопирован снова для полноты.

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)