Aha. I just managed to have the stem drawn. My silly
> mistake; i thought that to instantiate a Line2D i
> needed to pass it (x0, y0) and (x1, y1), but it rather
> expects (x0, x1) and (y0, y1). The arrow looks cool
> now.
Rather than a line and a polygon, it might be more flexible and
attractive to design the arrow simply as a polygon (you could then
have control of the linewidth, facecolor, and edgewidth, something
like
p0
/ \
/ \
/ \
p6--p5 p2--p1
> >
> >
> >
> >
> >
p4--p3
> My remaining problem is the coordinates. It seems that
> matplotlib is positioning the arrow using pixels as
> coordinates, from the bottom left corner of the figure
> window.
> Is my problem a 'transformation' issue?
Yes. If you derive your class from Artist and add it to the axes with
ax.add_artist (or Patch if you use the polygon approach above and add
it with ax.add_artist), the axes will set the default data
transformation for you, iff and only if you haven't already set the
transform. There are three default transforms you can choose from
fig.transFigure # 0,0 is lower left of fig and 1,1 is upper right
ax.transAxes # 0,0 is lower left of axes and 1,1 is upper right
ax.transData # same coordinates as the data in the axes
You have a additional choices with custom transforms. One approach
would be to set the coordinates of the polygon in points such that the
arrow tip is 0,0 and the width and height are both 1. You could then
use a scaling and rotation affine where sx, sy are the x and y scales,
and theta is the angle. If you apply this affine to the arrow, the
width of the arrow would be sx points, the height sy points, and the
angle would be theta and the sucker would still be pointing at 0,0.
One nice feature of transformations is that the let you combine two
coordinate systems by applying a an offset transformation. In this
case you'd want to apply and offset in data coords and then the arrow
would be pointing at some data location x,y but would still have a
width and height specified in points.
This is basically how the ticks work. An x tick is located at an x
location in data coords, a y location in axes coords (eg 0 for bottom
ticks and 1 for top ticks) and a length in points.
Here's an example. I'm not sure this is the best design. It might be
more useful to specify a point for the base and a point for the arrowhead,
and draw the arrow between them. But I am not sure what the best way
to specify the arrow width if you use that design. In any case, this
will serve as an example you can study to get an idea of how the
transforms work, and you can go from there. It would also be nice to
have some intelligent labeling built it, eg at the arrow base
from pylab import *
from matplotlib.patches import Polygon
from matplotlib.transforms import Affine, Value, zero
import math
class Arrow(Polygon):
zorder = 4 # these should generally above the things they mark
def __init__(self, x, y, xytrans, width, height, theta,
tipx=2, tipy=0.2):
"""
Create an arrow pointing at x,y with a base width and total
height in points
theta is the arrow rotation - 0 degrees is point up, 90 is
pointing to the right, 180 is pointing down, 270 is pointing
left.
tipx is the tip width and is expressed as fraction of the base width.
tipy is the tip height expressed as a fraction of the total
height
xytrans is the transformation of the x,y coordinate, eg
ax.transData for data coords and ax.transAxes for axes coords
"""
# p0
# / \
# / \
# / \
# p6--p5 p2--p1
# | |
# | |
# | |
# | |
# | |
# p4--p3
p0 = 0,0
p1 = tipx*0.5, -tipy
p2 = 0.5, -tipy
p3 = 0.5, -1
p4 = -0.5, -1
p5 = -0.5, -tipy
p6 = -tipx*0.5, -tipy
verts = p0, p1, p2, p3, p4, p5, p6
Polygon.__init__(self, verts)
theta = math.pi*theta/180.
a = width*math.cos(theta)
b = -width*math.sin(theta)
c = height*math.sin(theta)
d = height*math.cos(theta)
a,b,c,d = [Value(val) for val in (a,b,c,d)]
trans = Affine(a, b, c, d, zero(), zero())
trans.set_offset((x,y), xytrans)
self.set_transform(trans)
plot([0,1,2], [1,2,3], 'bo', ms=15)
axis([0,3, 0, 4])
ax = gca()
arrow = Arrow(1,2, ax.transData, 10, 100, 135)
set(arrow, fc='g', ec='r', lw=1)
ax.add_patch(arrow)
show()