如何在matplotlib中制作循环箭头?

12
什么是在matplotlib中画一个箭头并使其回到起点指向的正确方法?我尝试过:
plt.figure()
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.annotate("", xy=(0.6, 0.9),
             xycoords="figure fraction",
             xytext = (0.6, 0.8),
             textcoords="figure fraction",
             fontsize = 10, \
             color = "k",
             arrowprops=dict(edgecolor='black',
                             connectionstyle="angle,angleA=-180,angleB=45",
                             arrowstyle = '<|-',
                             facecolor="k",
                             linewidth=1,
                             shrinkA = 0,
                             shrinkB = 0))
plt.show()

这并不能得出正确的结果:

arrow

从这个页面(http://matplotlib.org/users/annotations_guide.html)上很难理解connectionstyle参数。

我想要的是像这个或者这个

loopy arrow

更新:所链接的答案没有展示如何在plt.annotate中实现此功能,而这个函数有其他我想要使用的特性。建议使用$\circlearrowleft$标记不是一个真正的解决方案。

6个回答

9
似乎创建一个易于修改的循环箭头最简单的方法是使用“patches”库。以下是实现此功能的代码。更改变量部分中的变量,所有旋转和缩放都应该一起进行。您可以尝试调整创建箭头头部的修补程序以创建不同的形状,但我认为这个三角形是最简单的一个。
%matplotlib inline
# from __future__ import division #Uncomment for python2.7
import matplotlib.pyplot as plt
from matplotlib.patches import Arc, RegularPolygon
import numpy as np
from numpy import radians as rad

fig = plt.figure(figsize=(9,9))
ax = plt.gca()
def drawCirc(ax,radius,centX,centY,angle_,theta2_,color_='black'):
    #========Line
    arc = Arc([centX,centY],radius,radius,angle=angle_,
          theta1=0,theta2=theta2_,capstyle='round',linestyle='-',lw=10,color=color_)
    ax.add_patch(arc)


    #========Create the arrow head
    endX=centX+(radius/2)*np.cos(rad(theta2_+angle_)) #Do trig to determine end position
    endY=centY+(radius/2)*np.sin(rad(theta2_+angle_))

    ax.add_patch(                    #Create triangle as arrow head
        RegularPolygon(
            (endX, endY),            # (x,y)
            3,                       # number of vertices
            radius/9,                # radius
            rad(angle_+theta2_),     # orientation
            color=color_
        )
    )
    ax.set_xlim([centX-radius,centY+radius]) and ax.set_ylim([centY-radius,centY+radius]) 
    # Make sure you keep the axes scaled or else arrow will distort

drawCirc(ax,1,1,1,0,250)
drawCirc(ax,2,1,1,90,330,color_='blue')
plt.show()    

enter image description here


9

我没有找到只使用plt.annotate一次创建循环的方法,但是使用它四次可以实现:

import matplotlib.pyplot as plt

fig,ax = plt.subplots()

# coordinates of the center of the loop
x_center = 0.5 
y_center = 0.5 

radius = 0.2
# linewidth of the arrow
linewidth = 1

ax.annotate("", (x_center + radius, y_center), (x_center, y_center + radius),
            arrowprops=dict(arrowstyle="-",
                            shrinkA=10,  # creates a gap between the start point and end point of the arrow
                            shrinkB=0,
                            linewidth=linewidth,
                            connectionstyle="angle,angleB=-90,angleA=180,rad=10"))    

ax.annotate("", (x_center, y_center - radius), (x_center + radius, y_center), 
            arrowprops=dict(arrowstyle="-",
                            shrinkA=0, 
                            shrinkB=0,
                            linewidth=linewidth,
                            connectionstyle="angle,angleB=180,angleA=-90,rad=10"))    

ax.annotate("", (x_center - radius, y_center),  (x_center, y_center - radius), 
            arrowprops=dict(arrowstyle="-",
                            shrinkA=0, 
                            shrinkB=0,
                            linewidth=linewidth,
                            connectionstyle="angle,angleB=-90,angleA=180,rad=10"))    
ax.annotate("", (x_center, y_center + radius), (x_center - radius, y_center), 
            arrowprops=dict(arrowstyle="-|>",
                            facecolor="k",
                            linewidth=linewidth,
                            shrinkA=0, 
                            shrinkB=0,
                            connectionstyle="angle,angleB=180,angleA=-90,rad=10"))


plt.show()

enter image description here


4

试试这个:

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.set_xlim(1,3) 
ax.set_ylim(1,3)
ax.plot([2.5],[2.5],marker=r'$\circlearrowleft$',ms=100)
plt.show()

enter image description here


1
我想在“plt.annotate”中使用箭头样式,而不是这个箭头。此外,这种方法无法灵活调整角度或箭头大小等。你的解决方案还依赖于TeX及其字体。 - mvd

3

我的建议只使用plot命令

import matplotlib.pyplot as plt
import numpy as np


def circarrowdraw(x0, y0, radius=1, aspect=1, direction=270, closingangle=-330,
                  arrowheadrelativesize=0.3, arrowheadopenangle=30, *args):
    """
    Circular arrow drawing. x0 and y0 are the anchor points.
    direction gives the angle of the circle center relative to the anchor
    in degrees. closingangle indicates how much of the circle is drawn
    in degrees with positive being counterclockwise and negative being
    clockwise. aspect is important to make the aspect of the arrow 
    fit the current figure.
    """

    xc = x0 + radius * np.cos(direction * np.pi / 180)
    yc = y0 + aspect * radius * np.sin(direction * np.pi / 180)

    headcorrectionangle = 5

    if closingangle < 0:
        step = -1
    else:
        step = 1
    x = [xc + radius * np.cos((ang + 180 + direction) * np.pi / 180)
         for ang in np.arange(0, closingangle, step)]
    y = [yc + aspect * radius * np.sin((ang + 180 + direction) * np.pi / 180)
         for ang in np.arange(0, closingangle, step)]

    plt.plot(x, y, *args)

    xlast = x[-1]
    ylast = y[-1]

    l = radius * arrowheadrelativesize

    headangle = (direction + closingangle + (90 - headcorrectionangle) *
                 np.sign(closingangle))

    x = [xlast +
         l * np.cos((headangle + arrowheadopenangle) * np.pi / 180),
         xlast,
         xlast +
         l * np.cos((headangle - arrowheadopenangle) * np.pi / 180)]
    y = [ylast +
         aspect * l * np.sin((headangle + arrowheadopenangle) * np.pi / 180),
         ylast,
         ylast +
         aspect * l * np.sin((headangle - arrowheadopenangle) * np.pi / 180)]

    plt.plot(x, y, *args)

测试方法:

plt.figure()
plt.plot(np.arange(10)**2, 'b.')
bb = plt.gca().axis()
asp = (bb[3] - bb[2]) / (bb[1] - bb[0])
circarrowdraw(6, 36 , radius=0.4, aspect=asp, direction=90)
plt.grid()
plt.show()

enter image description here


1
另一种可能性是使用tikz生成图像:
    \documentclass {minimal}
    \usepackage {tikz}
    \begin{document}
    \usetikzlibrary {arrows}
    \begin {tikzpicture}[scale=1.8]
    \draw[-angle 90, line width=5.0mm, rounded corners=20pt] 
    (0.25,0)--   (1.0, 0.0) -- (1.0, -3.0) -- (-3.0, -3.0) -- (-3.0, 0) --(-1,0);
    \end{tikzpicture}
    \end{document}

这是结果: 在此输入图片描述 在matplotlib中有一个pgf/tikz后端,您可以将matplotlib输出生成为tikz代码,pdflatex或lualatex可以处理。因此,我认为您可以在matplotlib图中无缝插入looparrow图。例如,请参见: http://matplotlib.org/users/whats_new.html#pgf-tikz-backend

0

@Aguy的回答非常有用,如果你想要一个平滑的弧线而不是一个完整的圆形。在Aguy的回答中,箭头头部是逐行绘制的,但是可以使用FancyArrowPatch。这会给出一个完整的箭头头部,可能更合适。下面是使用FancyArrowPatch箭头头部的代码。

def circarrowdraw(x0, y0, radius=1, aspect=1, direction=270, closingangle=-330, rotate_head = 0.0, color='b', *args):
    """
    Circular arrow drawing. x0 and y0 are the anchor points.
    direction gives the angle of the circle center relative to the anchor
    in degrees. closingangle indicates how much of the circle is drawn
    in degrees with positive being counterclockwise and negative being
    clockwise. aspect is important to make the aspect of the arrow 
    fit the current figure. rotate_head is used to rotate the arrow head
    by increasing the y value of the arrow's tail coordinate.
    """

    # Center of circle
    xc = x0 + radius * np.cos(direction * np.pi / 180)
    yc = y0 + aspect * radius * np.sin(direction * np.pi / 180)

    # Draw circle
    if closingangle < 0:
        step = -1
    else:
        step = 1
    x = [xc + radius * np.cos((ang + 180 + direction) * np.pi / 180)
         for ang in np.arange(0, closingangle, step)]
    y = [yc + aspect * radius * np.sin((ang + 180 + direction) * np.pi / 180)
         for ang in np.arange(0, closingangle, step)]
    plt.plot(x, y, *args, color=color)

    # Draw arrow head
    arc_arrow_head = patches.FancyArrowPatch((x[-1], y[-1] + rotate_head),
                                             (x[0], y[0]),
                                             arrowstyle="Simple,head_width=10,head_length=10,tail_width=0.01", 
                                             color = color,
                                             zorder = 10)
    plt.gca().add_patch(arc_arrow_head)

测试一下:

plt.plot([0, 0, 1, 1, 0], [0, 1, 1, 0, 0])
circarrowdraw(1.0, 1.0 , radius=0.1, aspect=0.3, direction=90, closingangle=-345, rotate_head = 0.003)
circarrowdraw(0.0, 1.0 , radius=0.1, aspect=1, direction=-90, closingangle=-345, rotate_head = 0.0)
circarrowdraw(0.0, 0.0 , radius=0.1, aspect=3.0, direction=90, closingangle=-345, rotate_head = 0.01)
circarrowdraw(1.0, 0.0 , radius=0.1, aspect=0.3, direction=-90, closingangle=-345)
plt.show()

图片(我没有足够的声望在我的回答中嵌入图片)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接