From: <lee...@us...> - 2009-07-14 19:22:01
|
Revision: 7259 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7259&view=rev Author: leejjoon Date: 2009-07-14 19:21:47 +0000 (Tue, 14 Jul 2009) Log Message: ----------- Fix a few bugs in ConnectionStyle classes. Add ConnectionPatch. Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/lib/matplotlib/patches.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2009-07-12 03:20:53 UTC (rev 7258) +++ trunk/matplotlib/CHANGELOG 2009-07-14 19:21:47 UTC (rev 7259) @@ -1,3 +1,6 @@ +2009-07-14 Fix a few bugs in ConnectionStyle algorithms. Add + ConnectionPatch class. -JJL + 2009-07-11 Added a fillstyle Line2D property for half filled markers -- see examples/pylab_examples/fillstyle_demo.py JDH Modified: trunk/matplotlib/lib/matplotlib/patches.py =================================================================== --- trunk/matplotlib/lib/matplotlib/patches.py 2009-07-12 03:20:53 UTC (rev 7258) +++ trunk/matplotlib/lib/matplotlib/patches.py 2009-07-14 19:21:47 UTC (rev 7259) @@ -1729,8 +1729,8 @@ """ def __init__(self, pad=0.3): - self.pad = pad - super(BoxStyle.RArrow, self).__init__() + #self.pad = pad + super(BoxStyle.RArrow, self).__init__(pad) def transmute(self, x0, y0, width, height, mutation_size): @@ -2466,7 +2466,7 @@ cosA, sinA = math.cos(self.angleA/180.*math.pi),\ math.sin(self.angleA/180.*math.pi), cosB, sinB = math.cos(self.angleB/180.*math.pi),\ - -math.sin(self.angleB/180.*math.pi), + math.sin(self.angleB/180.*math.pi), cx, cy = get_intersection(x1, y1, cosA, sinA, x2, y2, cosB, sinB) @@ -2478,9 +2478,15 @@ vertices.append((cx, cy)) codes.append(Path.LINETO) else: - vertices.extend([(cx - self.rad * cosA, cy - self.rad * sinA), + dx1, dy1 = x1-cx, y1-cy + d1 = (dx1**2 + dy1**2)**.5 + f1 = self.rad/d1 + dx2, dy2 = x2-cx, y2-cy + d2 = (dx2**2 + dy2**2)**.5 + f2 = self.rad/d2 + vertices.extend([(cx + dx1*f1, cy + dy1*f1), (cx, cy), - (cx + self.rad * cosB, cy + self.rad * sinB)]) + (cx + dx2*f2, cy + dy2*f2)]) codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3]) vertices.append((x2, y2)) @@ -2623,7 +2629,8 @@ #angle = self.angle % 180. #if angle < 0. or angle > 180.: # angle - theta0 = (self.angle%180.)/180.*math.pi + #theta0 = (self.angle%180.)/180.*math.pi + theta0 = self.angle/180.*math.pi #theta0 = (((self.angle+90)%180.) - 90.)/180.*math.pi dtheta = theta1 - theta0 dl = dd*math.sin(dtheta) @@ -3744,3 +3751,302 @@ gc.restore() renderer.close_group('patch') + + +class ConnectionPatch(FancyArrowPatch): + """ + A :class:`~matplotlib.patches.ConnectionPatch` class is to make + connecting lines between two points (possibly in different axes). + """ + def __str__(self): + return "ConnectionPatch((%g,%g),(%g,%g))" % \ + (self.xy1[0],self.xy1[1],self.xy2[0],self.xy2[1]) + + def __init__(self, xyA, xyB, coordsA, coordsB=None, + axesA=None, axesB=None, + arrowstyle="-", + arrow_transmuter=None, + connectionstyle="arc3", + connector=None, + patchA=None, + patchB=None, + shrinkA=0., + shrinkB=0., + mutation_scale=10., + mutation_aspect=None, + clip_on=False, + **kwargs): + """ + Connect point *xyA* in *coordsA* with point *xyB* in *coordsB* + + + Valid keys are + + + =============== ====================================================== + Key Description + =============== ====================================================== + arrowstyle the arrow style + connectionstyle the connection style + relpos default is (0.5, 0.5) + patchA default is bounding box of the text + patchB default is None + shrinkA default is 2 points + shrinkB default is 2 points + mutation_scale default is text size (in points) + mutation_aspect default is 1. + ? any key for :class:`matplotlib.patches.PathPatch` + =============== ====================================================== + + + *coordsA* and *coordsB* are strings that indicate the + coordinates of *xyA* and *xyB*. + + ================= =================================================== + Property Description + ================= =================================================== + 'figure points' points from the lower left corner of the figure + 'figure pixels' pixels from the lower left corner of the figure + 'figure fraction' 0,0 is lower left of figure and 1,1 is upper, right + 'axes points' points from lower left corner of axes + 'axes pixels' pixels from lower left corner of axes + 'axes fraction' 0,1 is lower left of axes and 1,1 is upper right + 'data' use the coordinate system of the object being + annotated (default) + 'offset points' Specify an offset (in points) from the *xy* value + + 'polar' you can specify *theta*, *r* for the annotation, + even in cartesian plots. Note that if you + are using a polar axes, you do not need + to specify polar for the coordinate + system since that is the native "data" coordinate + system. + ================= =================================================== + + """ + if coordsB is None: + coordsB = coordsA + # we'll draw ourself after the artist we annotate by default + self.xy1 = xyA + self.xy2 = xyB + self.coords1 = coordsA + self.coords2 = coordsB + + self.axesA = axesA + self.axesB = axesB + + FancyArrowPatch.__init__(self, + posA=(0,0), posB=(1,1), + arrowstyle=arrowstyle, + arrow_transmuter=arrow_transmuter, + connectionstyle=connectionstyle, + connector=connector, + patchA=patchA, + patchB=patchB, + shrinkA=shrinkA, + shrinkB=shrinkB, + mutation_scale=mutation_scale, + mutation_aspect=mutation_aspect, + clip_on=clip_on, + **kwargs) + + # if True, draw annotation only if self.xy is inside the axes + self._annotation_clip = None + + __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd + + + def _get_xy(self, x, y, s, axes=None): + """ + caculate the pixel position of given point + """ + + if axes is None: + axes = self.axes + + if s=='data': + trans = axes.transData + x = float(self.convert_xunits(x)) + y = float(self.convert_yunits(y)) + return trans.transform_point((x, y)) + elif s=='offset points': + # convert the data point + dx, dy = self.xy + + # prevent recursion + if self.xycoords == 'offset points': + return self._get_xy(dx, dy, 'data') + + dx, dy = self._get_xy(dx, dy, self.xycoords) + + # convert the offset + dpi = self.figure.get_dpi() + x *= dpi/72. + y *= dpi/72. + + # add the offset to the data point + x += dx + y += dy + + return x, y + elif s=='polar': + theta, r = x, y + x = r*np.cos(theta) + y = r*np.sin(theta) + trans = axes.transData + return trans.transform_point((x,y)) + elif s=='figure points': + #points from the lower left corner of the figure + dpi = self.figure.dpi + l,b,w,h = self.figure.bbox.bounds + r = l+w + t = b+h + + x *= dpi/72. + y *= dpi/72. + if x<0: + x = r + x + if y<0: + y = t + y + return x,y + elif s=='figure pixels': + #pixels from the lower left corner of the figure + l,b,w,h = self.figure.bbox.bounds + r = l+w + t = b+h + if x<0: + x = r + x + if y<0: + y = t + y + return x, y + elif s=='figure fraction': + #(0,0) is lower left, (1,1) is upper right of figure + trans = self.figure.transFigure + return trans.transform_point((x,y)) + elif s=='axes points': + #points from the lower left corner of the axes + dpi = self.figure.dpi + l,b,w,h = axes.bbox.bounds + r = l+w + t = b+h + if x<0: + x = r + x*dpi/72. + else: + x = l + x*dpi/72. + if y<0: + y = t + y*dpi/72. + else: + y = b + y*dpi/72. + return x, y + elif s=='axes pixels': + #pixels from the lower left corner of the axes + + l,b,w,h = axes.bbox.bounds + r = l+w + t = b+h + if x<0: + x = r + x + else: + x = l + x + if y<0: + y = t + y + else: + y = b + y + return x, y + elif s=='axes fraction': + #(0,0) is lower left, (1,1) is upper right of axes + trans = axes.transAxes + return trans.transform_point((x, y)) + + def set_annotation_clip(self, b): + """ + set *annotation_clip* attribute. + + * True : the annotation will only be drawn when self.xy is inside the axes. + * False : the annotation will always be drawn regardless of its position. + * None : the self.xy will be checked only if *xycoords* is "data" + """ + self._annotation_clip = b + + def get_annotation_clip(self): + """ + Return *annotation_clip* attribute. + See :meth:`set_annotation_clip` for the meaning of return values. + """ + return self._annotation_clip + + + def get_path_in_displaycoord(self): + """ + Return the mutated path of the arrow in the display coord + """ + + x, y = self.xy1 + posA = self._get_xy(x, y, self.coords1, self.axesA) + + x, y = self.xy2 + posB = self._get_xy(x, y, self.coords1, self.axesB) + + _path = self.get_connectionstyle()(posA, posB, + patchA=self.patchA, + patchB=self.patchB, + shrinkA=self.shrinkA, + shrinkB=self.shrinkB + ) + + + + _path, fillable = self.get_arrowstyle()(_path, + self.get_mutation_scale(), + self.get_linewidth(), + self.get_mutation_aspect() + ) + + return _path, fillable + + + + def _check_xy(self, renderer): + """ + check if the annotation need to + be drawn. + """ + + b = self.get_annotation_clip() + + + if b or (b is None and self.coords1 == "data"): + x, y = self.xy1 + xy_pixel = self._get_xy(x, y, self.coords1, self.axesA) + if not self.axes.contains_point(xy_pixel): + return False + + if b or (b is None and self.coords2 == "data"): + x, y = self.xy2 + xy_pixel = self._get_xy(x, y, self.coords2, self.axesB) + if self.axesB is None: + axes = self.axes + else: + axes = self.axesB + if not axes.contains_point(xy_pixel): + return False + + return True + + + def draw(self, renderer): + """ + Draw. + """ + + if renderer is not None: + self._renderer = renderer + if not self.get_visible(): return + + if not self._check_xy(renderer): + return + + FancyArrowPatch.draw(self, renderer) + + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |