From: John H. <jdh...@ac...> - 2004-12-22 16:34:04
|
>>>>> "Dominique" == Dominique Orban <Dom...@po...> writes: Dominique> Aha. I just managed to have the stem drawn. My silly Dominique> mistake; i thought that to instantiate a Line2D i Dominique> needed to pass it (x0, y0) and (x1, y1), but it rather Dominique> expects (x0, x1) and (y0, y1). The arrow looks cool Dominique> 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 Dominique> My remaining problem is the coordinates. It seems that Dominique> matplotlib is positioning the arrow using pixels as Dominique> coordinates, from the bottom left corner of the figure Dominique> window. Dominique> 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() |