I am using matplotlib.pyplot.annotate to draw an arrow on my plot, like so:
import matplotlib.pyplot as plt
plt.annotate("",(x,ybottom),(x,ytop),arrowprops=dict(arrowstyle="->"))
I want to use an arrow style that has a flat line at one end and an arrow at the other, so combining the styles "|-|" and "->" to make something we might call "|->", but I can't figure out how to define my own style.
I thought I might try something like
import matplotlib.patches as patches
myarrow = patches.ArrowStyle("Fancy", head_length=0.4,head_width=0.2)
(which should just be the same as "->" for now; I can tweak the style later) but then how do I tell plt.annotate to use myarrow as the style? There is no arrowstyle property for plt.annotate, and arrowprops=dict(arrowstyle=myarrow) doesn't work either.
I've also tried defining it in the arrowprops dictionary, such as
plt.annotate("",(x,ybottom),(x,ytop),arrowprops=dict(head_length=0.4,head_width=0.2))
but that gives me errors about no attribute 'set_head_width'.
So, how can I define my own style for pyplot.annotate to use?
In the last example in your code you could have used headwidth
, frac
and width
to customize the arrow, the result is arrow0
shown below. For highly customized arrows you can used arbitrary polygons. Below you can see the code I used to produce the figures.
To add more polygons you have to edit polygons
dictionary, and the new polygons must have the first and the last point at the origin (0,0)
, the rescaling and repositioning are done automatically. The figure below illustrates how the polygons are defined.
There is still an issue with shrinking that disconnects the line with the polygons. The '|-|>' arrow that you requested can be easily created using this customization.
The code follows:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib import transforms
import numpy as np
from numpy import cos, sin
plt.close()
plt.plot([1,2],[0,4], 'w')
ax = plt.gcf().axes[0]
def patchesAB(styleA, styleB, orig, target,
widthA, lengthA, widthB, lengthB,
kwargsA, kwargsB, shrinkA=0., shrinkB=0.):
'''
Select 'styleA' and 'styleB' from the dictionary 'polygons'
widthA, lengthA, widthB, lenghtB, shrinkA, shrinkB are defined in points
kwargsA and kwargsB are dictionaries
'''
polygons = {\
'|':np.array([[0,0],[0,1],[0.1,1],[0.1,-1],[0,-1],[0,0]], dtype=float),
'arrow1':np.array([[0,0],[0,1],[-1,2],[3,0],[-1,-2],[0,-1],[0,0]], dtype=float),
'arrow2':np.array([[0,0],[-1,1],[0,2],[3,0],[0,-2],[-1,-1],[0,0]], dtype=float),
}
xyA = polygons.get( styleA )
xyB = polygons.get( styleB )
#
fig = plt.gcf()
ax = fig.axes[0]
trans = ax.transData
pixPunit = trans.transform([(1,0),(0,1)])-ax.transData.transform((0,0))
unitPpix = pixPunit
unitPpix[0,0] = 1/unitPpix[0,0]
unitPpix[1,1] = 1/unitPpix[1,1]
#
orig = np.array(orig)
target = np.array(target)
vec = target-orig
angle = np.arctan2( vec[1], vec[0] )
#
lengthA *= unitPpix[0,0]
lengthB *= unitPpix[0,0]
widthA *= unitPpix[1,1]
widthB *= unitPpix[1,1]
orig += (unitPpix[1,1]*sin(angle)+unitPpix[0,0]*cos(angle))*vec*shrinkA
target -= (unitPpix[1,1]*sin(angle)+unitPpix[0,0]*cos(angle))*vec*shrinkB
#TODO improve shrinking... another attempt:
#orig += unitPpix.dot(vec) * shrinkA
#target -= unitPpix.dot(vec) * shrinkB
# polA
if xyA != None:
a = transforms.Affine2D()
tA = a.rotate_around( orig[0], orig[1], angle+np.pi ) + trans
xyA = np.float_(xyA)
xyA[:,0] *= lengthA/(xyA[:,0].max()-xyA[:,0].min())
xyA[:,1] *= widthA/(xyA[:,1].max()-xyA[:,1].min())
xyA += orig
polA = patches.Polygon( xyA, **kwargsA )
polA.set_transform( tA )
else:
polA = None
# polB
if xyB != None:
a = transforms.Affine2D()
tB = a.rotate_around( target[0], target[1], angle ) + trans
xyB = np.float_(xyB)
xyB[:,0] *= lengthB/(xyB[:,0].max()-xyB[:,0].min())
xyB[:,1] *= widthB/(xyB[:,1].max()-xyB[:,1].min())
xyB += target
polB = patches.Polygon( xyB, **kwargsB )
polB.set_transform( tB )
else:
polB = None
return polA, polB
# ARROW 0
plt.annotate('arrow0',xy=(2,1.5),xycoords='data',
xytext=(1.1,1), textcoords='data',
arrowprops=dict(frac=0.1,headwidth=10., width=2.))
#
kwargsA = dict( lw=1., ec='k', fc='gray' )
kwargsB = dict( lw=1., ec='k', fc='b' )
# ARROW 1
orig = (1.1,2.)
target = (2.,2.5)
shrinkA = 0.
shrinkB = 0.
polA, polB = patchesAB( '|', 'arrow1', orig, target, 20.,1.,60.,60.,
kwargsA, kwargsB, shrinkA, shrinkB )
ax.add_patch(polA)
ax.add_patch(polB)
ax.annotate('arrow1', xy=target, xycoords='data',
xytext=orig, textcoords='data',
arrowprops=dict(arrowstyle='-', patchA=polA, patchB=polB,
lw=1., shrinkA=shrinkA, shrinkB=shrinkB, relpos=(0.,0.),
mutation_scale=1.))
# ARROW 2
orig = (1.1,3.)
target = (2.,3.5)
polA, polB = patchesAB( '|', 'arrow2', orig, target, 20.,1.,60.,60.,
kwargsA, kwargsB, shrinkA, shrinkB )
ax.add_patch(polA)
ax.add_patch(polB)
ax.annotate('arrow2', xy=target, xycoords='data',
xytext=orig, textcoords='data',
arrowprops=dict(arrowstyle='-', patchA=polA, patchB=polB,
lw=1., shrinkA=shrinkA, shrinkB=shrinkB, relpos=(0.,0.),
mutation_scale=1.))
plt.autoscale()
plt.xlim(1.,2.2)
plt.ylim(0.5,4)
plt.show()