在matplotlib中绘制带有中点箭头的圆形fancyarrowpatch

时间:2016-02-03 12:54:18

标签: python matplotlib patch

我一直试图突破matplotlib补丁的界限,并指示它在中点画一个带有方向箭头的圆角FancyArrowPatch。这在我试图创建的网络表示中非常有用。

我使用python的编码时间还不到两位数,所以我不能说我对matplotlib的patches.py有一个清晰的认识,但我已经将解决​​方案缩小到两种可能的策略:

  1. 智能,可能是pythonic方式:创建自定义arrowstyle类,进一步要求修改_get_arrow_wedge()函数以包含中点坐标。这可能超出了我现在的可能性,或者
  2. 懒惰方式:从引出的FancyArrowPatch中提取中点坐标,并在这些坐标上绘制所需的arrowstyle
  3. 当然,到目前为止,我选择了懒惰的方式。我做了一些早期的实验,使用FancyArrowPatchget_path()提取弯曲的get_path_in_displaycoord()的中点坐标,但我似乎无法预测精确的中点坐标。一些帮助将非常感激。

    到目前为止我的小提琴:

    import matplotlib.pyplot as plt
    from matplotlib.patches import FancyArrowPatch
    
    n1 = (2,3)
    n2 = (4,6)
    
    
    # Try with multiple arc radius sizes, draw a separate plot each time
    for rad in range(20):
        #setup figure
        figure = plt.figure()
        ax = plt.subplot(111)
        plt.annotate('rad:' + str(rad/25.),xy=(2,5))
    
        # create rounded fancyarrowpatch   
        t = FancyArrowPatch(posA=n1,posB=n2,
                            connectionstyle='arc3,rad=%s'%float(rad/25.),
                            arrowstyle='->',
                            shrinkA=0,
                            shrinkB=0,
                            mutation_scale=0.5)
    
        # extract vertices from get_path: points P#
        path = t.get_path().vertices.tolist()
        lab, px, py = ['P{0}'.format(i) for i in range(len(path))], [u[0] for u in path],[u[1] for u in path]
        for i in range(len(path)):
            plt.annotate(lab[i],xy=(px[i],py[i]))
    
        # extract vertices from get_path_in_displaycoord (but they are useless) : points G#
        newpath = t.get_path_in_displaycoord()
        a,b = newpath[0][0].vertices.tolist(), newpath[0][1].vertices.tolist()
        a.extend(b)
        glab, gx, gy = ['G{0}'.format(i) for i in range(len(a))], [u[0] for u in a],[u[1] for u in a]
        for i in range(len(a)):
            plt.annotate(glab[i],xy=(gx[i],gy[i]))    
    
        #point A: start
        x1, y1 = n1
        plt.annotate('A',xy=(x1,y1))    
    
        #point B:end
        x2, y2 = n2
        plt.annotate('B',xy=(x2,y2))
    
        #point M: the 'midpoint' as defined by class Arc3, specifically its connect() function
        x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
        dx, dy = x2 - x1, y2 - y1
        cx, cy = x12 + (rad/100.) * dy, y12 - (rad/100.) * dx    
        plt.annotate('M',xy=(cx,cy))
    
        #point O : midpoint between M and P1, the second vertex from get_path   
        mx,my = (cx + px[1])/2., (cy + py[1])/2.
        plt.annotate('O',xy=(mx,my))
    
        ax.add_patch(t)
        plt.scatter([x1,cx,x2,mx,gx].extend(px),[y1,cy,y2,my,gy].extend(py))
    
    
    plt.show()
    

    objective

    编辑:接受@cphlewis建议:我试图重建Bezier曲线:

    def bezcurv(start,control,end,tau):
        ans = []
        for t in tau:
            B = [(1-t)**2 * start[i] + 2*(1-t)*t*end[i] + (t**2)*control[i] for i in range(len(start))]
            ans.append(tuple(B))
        return ans
    

    因此我将生成的行添加到原始图中:

    tau = [time/100. for time in range(101)]
    bezsim = bezcurv(n1,n2,(cx,cy),tau)
    simx,simy = [b[0] for b in bezsim], [b[1] for b in bezsim]
    

    下面的绿线是(应该是?)重建的贝塞尔曲线,尽管它显然不是。 attempt at bezier curve reconstruction

1 个答案:

答案 0 :(得分:1)

经过多次努力,我确信自己要解决这个问题,我不得不离开FancyArrowPatch套件并从头开始创造一些东西。这是一个有效的解决方案,它远非满足任何完美主义精神,让我满意:

import matplotlib.pyplot as plt
import numpy as np
from numpy.random import seed, randint

# Build function that connects two points with a curved line, 
# and an arrow on the middle of it

seed(1679)

narrow = 3
rad_one = 50
numpoints = 3

random_points = list(randint(1,20,[numpoints,4]))
rpoints = [[(a,b),(c,d)] for a,b,c,d in random_points]

def curvline(start,end,rad,t=100,arrows=1,push=0.8):
    #Compute midpoint
    rad = rad/100.    
    x1, y1 = start
    x2, y2 = end
    y12 = (y1 + y2) / 2
    dy = (y2 - y1)
    cy = y12 + (rad) * dy
    #Prepare line
    tau = np.linspace(0,1,t)
    xsupport = np.linspace(x1,x2,t)
    ysupport = [(1-i)**2 * y1 + 2*(1-i)*i*cy + (i**2)*y2 for i in tau]
    #Create arrow data    
    arset = list(np.linspace(0,1,arrows+2))
    c = zip([xsupport[int(t*a*push)] for a in arset[1:-1]],
                      [ysupport[int(t*a*push)] for a in arset[1:-1]])
    dt = zip([xsupport[int(t*a*push)+1]-xsupport[int(t*a*push)] for a in arset[1:-1]],
                      [ysupport[int(t*a*push)+1]-ysupport[int(t*a*push)] for a in arset[1:-1]])
    arrowpath = zip(c,dt)
    return xsupport, ysupport, arrowpath

def plotcurv(start,end,rad,t=100,arrows=1,arwidth=.25):
    x, y, c = curvline(start,end,rad,t,arrows)
    plt.plot(x,y,'k-')
    for d,dt in c:
        plt.arrow(d[0],d[1],dt[0],dt[1], shape='full', lw=0, 
                  length_includes_head=False, head_width=arwidth)
    return c

#Create figure
figure = plt.figure()
ax = plt.subplot(111)
for n1,n2 in rpoints:    
    #First line
    plotcurv(n1,n2,rad_one,200,narrow,0.5)
    #Second line
    plotcurv(n2,n1,rad_one,200,narrow,0.5)
ax.set_xlim(0,20)
ax.set_ylim(0,20)
plt.show

我用三个随机的几个点测试它,来回绘制线条。这给出了下图:

Lines and directional arrows

该功能允许用户设置多个所需的箭头,并将它们均匀地放置在绘制的贝塞尔曲线上,确保表示适当的方向。但是,因为Bezier曲线并不完全是“弧形”,所以我试探性地推动箭头的开始使它们看起来更加居中。对此解决方案的任何改进都将不胜感激。