在matplotlib中用中点箭头绘制圆形的fancyarrowpatch [英] Draw rounded fancyarrowpatch with midpoint arrow in matplotlib
问题描述
我一直在尝试扩大matplotlib补丁的边界,并指示它绘制一个圆角的 FancyArrowPatch
,并在其中点带有方向箭头.在我尝试创建的网络表示中,这将被证明非常有用.
I've been trying to push the boundaries of matplotlib's patches and instruct it to draw a rounded FancyArrowPatch
with a directional arrow on its midpoint. This would prove incredibly useful in a network representation I am trying to create.
我在python上的编码时间尚未达到两位数,因此我不能说我对matplotlib的patchs.py有清晰的了解,但我已将解决方案的范围缩小到两种可能的策略:
My coding hours with python are not yet in the double digit, so I can't say I have a clear understanding of matplotlib's patches.py, but I have narrowed down the solution to two possible strategies:
- 聪明的,可能是 Pythonic 的方式:创建一个自定义的
arrowstyle
类,它进一步需要修改_get_arrow_wedge()
函数以包含中点坐标.这可能暂时超出了我的可能性,或者 - 懒惰的方法:从引出的 FancyArrowPatch 中提取中点坐标,并在这些坐标上绘制所需的
arrowstyle
.
- the smart, possibly pythonic way: create a custom
arrowstyle
class which further requires a modification of the_get_arrow_wedge()
function to include a midpoint coordinates. This may be beyond my possibilities for now, or - the lazy way: extract the midpoint coordinates from an elicited FancyArrowPatch and draw the desired
arrowstyle
on such coordinates.
当然,到目前为止我选择了懒惰的方式.我做了一些早期的实验,使用 get_path()
和 get_path_in_displaycoord()
提取弯曲的 FancyArrowPatch
的中点坐标,但我似乎无法预测精确的中点坐标.一些帮助将不胜感激.
Of course, so far I've chosen the lazy way. I did some early experimenting with extracting the midpoint coordinates of a curved FancyArrowPatch
using get_path()
and get_path_in_displaycoord()
, but I can't seem to predict the precise midpoint coordinates. Some help would be very appreciated.
到目前为止我的摆弄:
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()
参加@cphlewis建议:我试图重建贝塞尔曲线:
taking onboard @cphlewis suggestions: I tried to reconstruct the Bezier curve:
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
因此,我将生成的线添加到原始图:
I thus add the generated line to the original plot:
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]
下面的绿线是(应该是?)重建的贝塞尔曲线,但显然不是.
The green line below is (should be?) the reconstructed bezier curve, though it's clearly not.
推荐答案
在苦苦挣扎之后,我深信自己要解决这个问题,我不得不放弃 FancyArrowPatch
套件并从头开始创建一些东西.这是一个有效的解决方案,它远没有满足任何完美主义精神,让我满意:
After much struggling, I convinced myself that to solve this I had to part away from the FancyArrowPatch
suite and create something from scratch. Here is a working solution that, far from fulfilling any perfectionist spirit, satisfied me:
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
我已经用三个随机点对它进行了测试,并绘制了来回线.如下图所示:
I have tested it with three random couple of points, plotting back and forth lines. Which gives the figure below:
该功能允许用户设置多个所需的箭头,并将它们均匀地放置在绘制的贝塞尔曲线上,确保表示适当的方向.然而,因为贝塞尔曲线不完全是一个弧",我试探性地推动箭头的起点,使它们看起来更居中.对此解决方案的任何改进将不胜感激.
The function allows for the user to set a number of desired arrow-heads, and it places them evenly on the plotted Bezier, making sure the appropriate direction is represented. However, because the Bezier curve is not exactly an 'arc', I heuristically push the start of the arrow-heads to make them look more centered. Any improvement to this solution will be greatly appreciated.
这篇关于在matplotlib中用中点箭头绘制圆形的fancyarrowpatch的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!