Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib: Draw curved arrow that looks just like pyplot.arrow?

I use pyplot.arrow do draw some straight arrows, e.g.,

import matplotlib.pyplot as plt
import numpy as np

v={}
for i in range (1,4):
    v[i]=np.array([np.cos(-2*np.pi/3*i),np.sin(-2*np.pi/3*i)])

plt.arrow(.85*(.05*v[2]+.95*v[1])[0],.85*(.05*v[2]+.95*v[1])[1],.85*.9*(v[2]-v[1])[0],.85*.9*(v[2]-v[1])[1],width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")
plt.arrow(.85*(.05*v[3]+.95*v[2])[0],.85*(.05*v[3]+.95*v[2])[1],.85*.9*(v[3]-v[2])[0],.85*.9*(v[3]-v[2])[1],width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")
plt.arrow(.85*(.05*v[1]+.95*v[3])[0],.85*(.05*v[1]+.95*v[3])[1],.85*.9*(v[1]-v[3])[0],.85*.9*(v[1]-v[3])[1],width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")

plt.axes().set_xlim(-.5,1)
plt.axes().set_ylim(-np.sqrt(3)/2,np.sqrt(3)/2)
plt.axes().set_aspect(1)
plt.show()

Now I want to also draw some arrows that have circular curvature instead of being straight. I see that I can achieve this with pyplot.annotate() or patches.FancyArrowPatch with connectionstyle="arc3,rad=.5" or so.

But these arrows look completely different from the pyplot.arrows and do not fit with the rest of my figures. And I don't know how I could pass something like connectionstyle to pyplot.arrow. Is there a way to draw curved arrows that look exactly like those that I get from pyplot.arrow?

like image 331
wea0 Avatar asked Jun 13 '17 15:06

wea0


2 Answers

You cannot plot curved arrows with pyplot.arrow. However, patches.FancyArrowPatch should offer all the options to get any arrow style you want, so the idea would be to use a FancyArrowPatch for the straight arrows as well, such that you can use the same style for all arrows.

import matplotlib.pyplot as plt
import matplotlib.patches as patches

plt.axes().set_xlim(-.5,0.5)
plt.axes().set_ylim(-0.9,0.7)
plt.axes().set_aspect(1)

style = "Simple, tail_width=0.5, head_width=4, head_length=8"
kw = dict(arrowstyle=style, color="k")

a1 = patches.FancyArrowPatch((-0.4, -0.6), (0, 0.6), **kw)
a2 = patches.FancyArrowPatch((0, 0.6), (0.4, -0.6), **kw)
a3 = patches.FancyArrowPatch((-0.4, -0.6), (0.4, -0.6),
                             connectionstyle="arc3,rad=.5", **kw)

for a in [a1, a2, a3]:
    plt.gca().add_patch(a)
plt.show()

enter image description here

like image 70
ImportanceOfBeingErnest Avatar answered Nov 08 '22 16:11

ImportanceOfBeingErnest


Here's what I ended up using; it's a bit of a hack and just draws straight arrow heads at the ends of an Arc:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
def circarrow(self,diameter,centX,centY,startangle,angle,**kwargs):
    startarrow=kwargs.pop("startarrow",False)
    endarrow=kwargs.pop("endarrow",False)

    arc = Arc([centX,centY],diameter,diameter,angle=startangle,
          theta1=np.rad2deg(kwargs.get("head_length",1.5*3*.001)) if startarrow else 0,theta2=angle-(np.rad2deg(kwargs.get("head_length",1.5*3*.001)) if endarrow else 0),linestyle="-",color=kwargs.get("color","black"))
    self.axes().add_patch(arc)

    if startarrow:
        startX=diameter/2*np.cos(np.radians(startangle))
        startY=diameter/2*np.sin(np.radians(startangle))
        startDX=+.000001*diameter/2*np.sin(np.radians(startangle)+kwargs.get("head_length",1.5*3*.001))
        startDY=-.000001*diameter/2*np.cos(np.radians(startangle)+kwargs.get("head_length",1.5*3*.001))
        self.arrow(startX-startDX,startY-startDY,startDX,startDY,**kwargs)

    if endarrow:
        endX=diameter/2*np.cos(np.radians(startangle+angle))
        endY=diameter/2*np.sin(np.radians(startangle+angle))
        endDX=-.000001*diameter/2*np.sin(np.radians(startangle+angle)-kwargs.get("head_length",1.5*3*.001))
        endDY=+.000001*diameter/2*np.cos(np.radians(startangle+angle)-kwargs.get("head_length",1.5*3*.001))
        self.arrow(endX-endDX,endY-endDY,endDX,endDY,**kwargs)

import types
plt.circarrow = types.MethodType(circarrow,plt)

The function is called circarrow, and as arguments you pass the diameter, the two coordinates of the center, the angle at which the arc starts and the total angle the arc should pass over, as well as any parameters that are passed to pyplot arrow. To draw an arrowhead at the beginning of the arc you specify startarrow=True, while endarrow=True will enable an arrowhead at the end of the arc.

Here's an example image, where you can also see that the arrow style is consistent with the straight arrows:

plt.plot(0,0,"o",markersize=10,color="black",mfc="none")

plt.circarrow(.85,0,0,0.05*120,.9*120,startarrow=True,width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")
plt.circarrow(.85,0,0,1.05*120,.9*120,startarrow=True,endarrow=True,width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")
plt.arrow(-.2,-.33,.6,+.33,width=0,head_width=.03,head_length=.045,length_includes_head=True,color="black")

plt.axes().set_xlim(-.5,.5)
plt.axes().set_ylim(-.5,.5)
plt.axes().set_aspect(1)
plt.show()

example image

like image 35
wea0 Avatar answered Nov 08 '22 17:11

wea0