如何在Matplotlib中指定类似箭头的线条样式?

Dea*_*ton 13 python matplotlib

我想在Matplotlib中显示一组xy数据,以指示特定路径.理想情况下,linestyle会被修改为使用类似箭头的补丁.我创建了一个模型,如下所示(使用Omnigraphsketcher).好像我应该能够覆盖普通的一个linestyle声明('-','--',':'等),以这种效果.

请注意,我不想简单地用一个箭头连接每个数据点---实际数据点不是均匀间隔的,我需要一致的箭头间距.

在此输入图像描述

Yan*_*ann 7

这是一个起点:

  1. 按固定步骤沿着你的线走(aspace在我的例子中).

    答:这包括沿着由两组点(x1,y1)和(x2,y2)创建的线段采取步骤.

    B.如果您的步长比线段长,请转到下一组点.

  2. 此时确定线的角度.

  3. 绘制一个倾斜度与角度相对应的箭头.

我写了一个小脚本来证明这一点:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
axes = fig.add_subplot(111)

# my random data
scale = 10 
np.random.seed(101)
x = np.random.random(10)*scale
y = np.random.random(10)*scale

# spacing of arrows
aspace = .1 # good value for scale of 1
aspace *= scale

# r is the distance spanned between pairs of points
r = [0]
for i in range(1,len(x)):
    dx = x[i]-x[i-1]
    dy = y[i]-y[i-1]
    r.append(np.sqrt(dx*dx+dy*dy))
r = np.array(r)

# rtot is a cumulative sum of r, it's used to save time
rtot = []
for i in range(len(r)):
    rtot.append(r[0:i].sum())
rtot.append(r.sum())

arrowData = [] # will hold tuples of x,y,theta for each arrow
arrowPos = 0 # current point on walk along data
rcount = 1 
while arrowPos < r.sum():
    x1,x2 = x[rcount-1],x[rcount]
    y1,y2 = y[rcount-1],y[rcount]
    da = arrowPos-rtot[rcount] 
    theta = np.arctan2((x2-x1),(y2-y1))
    ax = np.sin(theta)*da+x1
    ay = np.cos(theta)*da+y1
    arrowData.append((ax,ay,theta))
    arrowPos+=aspace
    while arrowPos > rtot[rcount+1]: 
        rcount+=1
        if arrowPos > rtot[-1]:
            break

# could be done in above block if you want
for ax,ay,theta in arrowData:
    # use aspace as a guide for size and length of things
    # scaling factors were chosen by experimenting a bit
    axes.arrow(ax,ay,
               np.sin(theta)*aspace/10,np.cos(theta)*aspace/10, 
               head_width=aspace/8)


axes.plot(x,y)
axes.set_xlim(x.min()*.9,x.max()*1.1)
axes.set_ylim(y.min()*.9,y.max()*1.1)

plt.show()
Run Code Online (Sandbox Code Playgroud)

这个例子产生了这个数字: 在此输入图像描述

对于初学者来说,这里有很大的改进空间:

  1. 可以使用FancyArrowPatch来自定义箭头的外观.
  2. 在创建箭头时可以添加进一步的测试,以确保它们不会延伸到线之外.这与在顶点处或顶点附近创建的箭头相关,其中线条明显改变方向.对于上面最正确的观点,就是这种情况.
  3. 可以从这个脚本中创建一个方法,该方法适用于更广泛的案例,即使其更具可移植性.

在研究这个时,我发现了箭袋绘图方法.它可能能够取代上述工作,但并不是很明显,这是有保证的.


Ped*_*rte 6

Yann非常好的回答,但是通过使用箭头,生成的箭头会受到轴纵横比和限制的影响.我创建了一个使用axes.annotate()而不是axes.arrow()的版本.我把它包含在这里供其他人使用.

简而言之,这用于在matplotlib中沿着你的线绘制箭头.代码如下所示.通过添加具有不同箭头的可能性仍然可以改进它.在这里,我只包括控制箭头的宽度和长度.

import numpy as np
import matplotlib.pyplot as plt


def arrowplot(axes, x, y, narrs=30, dspace=0.5, direc='pos', \
                          hl=0.3, hw=6, c='black'): 
    ''' narrs  :  Number of arrows that will be drawn along the curve

        dspace :  Shift the position of the arrows along the curve.
                  Should be between 0. and 1.

        direc  :  can be 'pos' or 'neg' to select direction of the arrows

        hl     :  length of the arrow head 

        hw     :  width of the arrow head        

        c      :  color of the edge and face of the arrow head  
    '''

    # r is the distance spanned between pairs of points
    r = [0]
    for i in range(1,len(x)):
        dx = x[i]-x[i-1] 
        dy = y[i]-y[i-1] 
        r.append(np.sqrt(dx*dx+dy*dy))
    r = np.array(r)

    # rtot is a cumulative sum of r, it's used to save time
    rtot = []
    for i in range(len(r)):
        rtot.append(r[0:i].sum())
    rtot.append(r.sum())

    # based on narrs set the arrow spacing
    aspace = r.sum() / narrs

    if direc is 'neg':
        dspace = -1.*abs(dspace) 
    else:
        dspace = abs(dspace)

    arrowData = [] # will hold tuples of x,y,theta for each arrow
    arrowPos = aspace*(dspace) # current point on walk along data
                                 # could set arrowPos to 0 if you want
                                 # an arrow at the beginning of the curve

    ndrawn = 0
    rcount = 1 
    while arrowPos < r.sum() and ndrawn < narrs:
        x1,x2 = x[rcount-1],x[rcount]
        y1,y2 = y[rcount-1],y[rcount]
        da = arrowPos-rtot[rcount]
        theta = np.arctan2((x2-x1),(y2-y1))
        ax = np.sin(theta)*da+x1
        ay = np.cos(theta)*da+y1
        arrowData.append((ax,ay,theta))
        ndrawn += 1
        arrowPos+=aspace
        while arrowPos > rtot[rcount+1]: 
            rcount+=1
            if arrowPos > rtot[-1]:
                break

    # could be done in above block if you want
    for ax,ay,theta in arrowData:
        # use aspace as a guide for size and length of things
        # scaling factors were chosen by experimenting a bit

        dx0 = np.sin(theta)*hl/2. + ax
        dy0 = np.cos(theta)*hl/2. + ay
        dx1 = -1.*np.sin(theta)*hl/2. + ax
        dy1 = -1.*np.cos(theta)*hl/2. + ay

        if direc is 'neg' :
          ax0 = dx0 
          ay0 = dy0
          ax1 = dx1
          ay1 = dy1 
        else:
          ax0 = dx1 
          ay0 = dy1
          ax1 = dx0
          ay1 = dy0 

        axes.annotate('', xy=(ax0, ay0), xycoords='data',
                xytext=(ax1, ay1), textcoords='data',
                arrowprops=dict( headwidth=hw, frac=1., ec=c, fc=c))


    axes.plot(x,y, color = c)
    axes.set_xlim(x.min()*.9,x.max()*1.1)
    axes.set_ylim(y.min()*.9,y.max()*1.1)


if __name__ == '__main__':
    fig = plt.figure()
    axes = fig.add_subplot(111)

    # my random data
    scale = 10 
    np.random.seed(101)
    x = np.random.random(10)*scale
    y = np.random.random(10)*scale
    arrowplot(axes, x, y ) 

    plt.show()
Run Code Online (Sandbox Code Playgroud)

结果图可以在这里看到:

在此输入图像描述