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

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


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