2727from matplotlib .transforms import Affine2D , Bbox , Transform
2828from matplotlib .transforms import BboxBase , BboxTransformTo
2929from matplotlib .lines import Line2D
30-
30+ from matplotlib . path import Path
3131from matplotlib .artist import allow_rasterization
3232
3333from matplotlib .backend_bases import RendererBase
@@ -539,7 +539,7 @@ def update_bbox_position_size(self, renderer):
539539 self ._bbox_patch .set_transform (tr )
540540 fontsize_in_pixel = renderer .points_to_pixels (self .get_size ())
541541 self ._bbox_patch .set_mutation_scale (fontsize_in_pixel )
542- # self._bbox_patch.draw(renderer)
542+
543543
544544 def _draw_bbox (self , renderer , posx , posy ):
545545
@@ -558,6 +558,7 @@ def _draw_bbox(self, renderer, posx, posy):
558558 self ._bbox_patch .set_mutation_scale (fontsize_in_pixel )
559559 self ._bbox_patch .draw (renderer )
560560
561+
561562 def _update_clip_properties (self ):
562563 clipprops = dict (clip_box = self .clipbox ,
563564 clip_path = self ._clippath ,
@@ -2046,9 +2047,18 @@ def __init__(self, s, xy,
20462047
20472048 self .arrow = None
20482049
2049- if arrowprops and "arrowstyle" in arrowprops :
2050- arrowprops = self .arrowprops .copy ()
2051- self ._arrow_relpos = arrowprops .pop ("relpos" , (0.5 , 0.5 ))
2050+ if arrowprops :
2051+ if "arrowstyle" in arrowprops :
2052+ arrowprops = self .arrowprops .copy ()
2053+ self ._arrow_relpos = arrowprops .pop ("relpos" , (0.5 , 0.5 ))
2054+ else :
2055+ # modified YAArrow API to be used with FancyArrowPatch
2056+ shapekeys = ('width' , 'headwidth' , 'headlength' ,
2057+ 'shrink' , 'frac' )
2058+ arrowprops = dict ()
2059+ for key , val in self .arrowprops .items ():
2060+ if key not in shapekeys :
2061+ arrowprops [key ] = val # basic Patch properties
20522062 self .arrow_patch = FancyArrowPatch ((0 , 0 ), (1 , 1 ),
20532063 ** arrowprops )
20542064 else :
@@ -2059,7 +2069,9 @@ def contains(self, event):
20592069 if self .arrow is not None :
20602070 in_arrow , _ = self .arrow .contains (event )
20612071 contains = contains or in_arrow
2062- # self.arrow_patch is currently not checked as this can be a line - J
2072+ if self .arrow_patch is not None :
2073+ in_patch , _ = self .arrow_patch .contains (event )
2074+ contains = contains or in_patch
20632075
20642076 return contains , tinfo
20652077
@@ -2102,6 +2114,7 @@ def _update_position_xytext(self, renderer, xy_pixel):
21022114 self .set_transform (self ._get_xy_transform (
21032115 renderer , self .xy , self .anncoords ))
21042116
2117+
21052118 ox0 , oy0 = self ._get_xy_display ()
21062119 ox1 , oy1 = xy_pixel
21072120
@@ -2114,111 +2127,94 @@ def _update_position_xytext(self, renderer, xy_pixel):
21142127 yc = 0.5 * (b + t )
21152128
21162129 d = self .arrowprops .copy ()
2130+ ms = d .pop ("mutation_scale" , self .get_size ())
2131+ ms = renderer .points_to_pixels (ms )
2132+ self .arrow_patch .set_mutation_scale (ms )
21172133
2118- # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key.
2119- # Otherwise, fallback to YAArrow.
2120-
2121- #if d.has_key("arrowstyle"):
2122- if self .arrow_patch :
2123-
2124- # adjust the starting point of the arrow relative to
2125- # the textbox.
2126- # TODO : Rotation needs to be accounted.
2127- relpos = self ._arrow_relpos
2128- bbox = Text .get_window_extent (self , renderer )
2129- ox0 = bbox .x0 + bbox .width * relpos [0 ]
2130- oy0 = bbox .y0 + bbox .height * relpos [1 ]
2131-
2132- # The arrow will be drawn from (ox0, oy0) to (ox1,
2133- # oy1). It will be first clipped by patchA and patchB.
2134- # Then it will be shrinked by shirnkA and shrinkB
2135- # (in points). If patch A is not set, self.bbox_patch
2136- # is used.
2137-
2138- self .arrow_patch .set_positions ((ox0 , oy0 ), (ox1 , oy1 ))
2139- mutation_scale = d .pop ("mutation_scale" , self .get_size ())
2140- mutation_scale = renderer .points_to_pixels (mutation_scale )
2141- self .arrow_patch .set_mutation_scale (mutation_scale )
2142-
2143- if "patchA" in d :
2144- self .arrow_patch .set_patchA (d .pop ("patchA" ))
2145- else :
2146- if self ._bbox_patch :
2147- self .arrow_patch .set_patchA (self ._bbox_patch )
2148- else :
2149- pad = renderer .points_to_pixels (4 )
2150- if self .get_text ().strip () == "" :
2151- self .arrow_patch .set_patchA (None )
2152- return
2153-
2154- bbox = Text .get_window_extent (self , renderer )
2155- l , b , w , h = bbox .bounds
2156- l -= pad / 2.
2157- b -= pad / 2.
2158- w += pad
2159- h += pad
2160- r = Rectangle (xy = (l , b ),
2161- width = w ,
2162- height = h ,
2163- )
2164- r .set_transform (mtransforms .IdentityTransform ())
2165- r .set_clip_on (False )
2166-
2167- self .arrow_patch .set_patchA (r )
2134+ if "arrowstyle" not in d :
2135+ # Approximately simulate the YAArrow.
2136+ # Pop its kwargs:
2137+ shrink = d .pop ('shrink' , 0.0 )
2138+ width = d .pop ('width' , 4 )
2139+ headwidth = d .pop ('headwidth' , 12 )
2140+ # Ignore frac--it is useless.
2141+ frac = d .pop ('frac' , None )
2142+ if frac is not None :
2143+ warnings .warn (
2144+ "'frac' option in 'arrowstyle' is no longer supported;"
2145+ " use 'headlength' to set the head length in points." )
2146+ headlength = d .pop ('headlength' , 12 )
21682147
2169- else :
2170- # using YAArrow
2148+ stylekw = dict (head_length = headlength / ms ,
2149+ head_width = headwidth / ms ,
2150+ tail_width = width / ms )
2151+
2152+ self .arrow_patch .set_arrowstyle ('simple' , ** stylekw )
2153+
2154+ # using YAArrow style:
21712155 # pick the x,y corner of the text bbox closest to point
21722156 # annotated
2173- dsu = [(abs (val - x0 ), val ) for val in (l , r , xc )]
2157+ xpos = ((l , 0 ), (xc , 0.5 ), (r , 1 ))
2158+ ypos = ((b , 0 ), (yc , 0.5 ), (t , 1 ))
2159+
2160+ dsu = [(abs (val [0 ] - x0 ), val ) for val in xpos ]
21742161 dsu .sort ()
2175- _ , x = dsu [0 ]
2162+ _ , ( x , relposx ) = dsu [0 ]
21762163
2177- dsu = [(abs (val - y0 ), val ) for val in ( b , t , yc ) ]
2164+ dsu = [(abs (val [ 0 ] - y0 ), val ) for val in ypos ]
21782165 dsu .sort ()
2179- _ , y = dsu [0 ]
2166+ _ , ( y , relposy ) = dsu [0 ]
21802167
2181- shrink = d . pop ( 'shrink' , 0.0 )
2168+ self . _arrow_relpos = ( relposx , relposy )
21822169
2183- theta = math .atan2 (y - y0 , x - x0 )
21842170 r = np .hypot ((y - y0 ), (x - x0 ))
2185- dx = shrink * r * math .cos (theta )
2186- dy = shrink * r * math .sin (theta )
2171+ shrink_pts = shrink * r / renderer .points_to_pixels (1 )
2172+ self .arrow_patch .shrinkA = shrink_pts
2173+ self .arrow_patch .shrinkB = shrink_pts
21872174
2188- width = d .pop ('width' , 4 )
2189- headwidth = d .pop ('headwidth' , 12 )
2190- frac = d .pop ('frac' , 0.1 )
2191- self .arrow = YAArrow (self .figure ,
2192- (x0 + dx , y0 + dy ), (x - dx , y - dy ),
2193- width = width , headwidth = headwidth ,
2194- frac = frac ,
2195- ** d )
21962175
2197- self .arrow .set_clip_box (self .get_clip_box ())
2176+ # adjust the starting point of the arrow relative to
2177+ # the textbox.
2178+ # TODO : Rotation needs to be accounted.
2179+ relpos = self ._arrow_relpos
2180+ bbox = Text .get_window_extent (self , renderer )
2181+ ox0 = bbox .x0 + bbox .width * relpos [0 ]
2182+ oy0 = bbox .y0 + bbox .height * relpos [1 ]
21982183
2199- def update_bbox_position_size (self , renderer ):
2200- """
2201- Update the location and the size of the bbox. This method
2202- should be used when the position and size of the bbox needs to
2203- be updated before actually drawing the bbox.
2204- """
2184+ # The arrow will be drawn from (ox0, oy0) to (ox1,
2185+ # oy1). It will be first clipped by patchA and patchB.
2186+ # Then it will be shrunk by shirnkA and shrinkB
2187+ # (in points). If patch A is not set, self.bbox_patch
2188+ # is used.
22052189
2206- # For arrow_patch, use textbox as patchA by default.
2190+ self . arrow_patch . set_positions (( ox0 , oy0 ), ( ox1 , oy1 ))
22072191
2208- if not isinstance (self .arrow_patch , FancyArrowPatch ):
2209- return
2192+ if "patchA" in d :
2193+ self .arrow_patch .set_patchA (d .pop ("patchA" ))
2194+ else :
2195+ if self ._bbox_patch :
2196+ self .arrow_patch .set_patchA (self ._bbox_patch )
2197+ else :
2198+ pad = renderer .points_to_pixels (4 )
2199+ if self .get_text ().strip () == "" :
2200+ self .arrow_patch .set_patchA (None )
2201+ return
2202+
2203+ bbox = Text .get_window_extent (self , renderer )
2204+ l , b , w , h = bbox .bounds
2205+ l -= pad / 2.
2206+ b -= pad / 2.
2207+ w += pad
2208+ h += pad
2209+ r = Rectangle (xy = (l , b ),
2210+ width = w ,
2211+ height = h ,
2212+ )
2213+ r .set_transform (mtransforms .IdentityTransform ())
2214+ r .set_clip_on (False )
2215+
2216+ self .arrow_patch .set_patchA (r )
22102217
2211- if self ._bbox_patch :
2212- posx , posy = self ._x , self ._y
2213-
2214- x_box , y_box , w_box , h_box = _get_textbox (self , renderer )
2215- self ._bbox_patch .set_bounds (0. , 0. , w_box , h_box )
2216- theta = np .deg2rad (self .get_rotation ())
2217- tr = mtransforms .Affine2D ().rotate (theta )
2218- tr = tr .translate (posx + x_box , posy + y_box )
2219- self ._bbox_patch .set_transform (tr )
2220- fontsize_in_pixel = renderer .points_to_pixels (self .get_size ())
2221- self ._bbox_patch .set_mutation_scale (fontsize_in_pixel )
22222218
22232219 @allow_rasterization
22242220 def draw (self , renderer ):
@@ -2238,20 +2234,16 @@ def draw(self, renderer):
22382234 self ._update_position_xytext (renderer , xy_pixel )
22392235 self .update_bbox_position_size (renderer )
22402236
2241- # Draw text, including FancyBboxPatch, before FancyArrowPatch.
2242- # Otherwise, the transform of the former Patch will be incomplete.
2243- Text .draw (self , renderer )
2244-
2245- if self .arrow is not None :
2246- if self .arrow .figure is None and self .figure is not None :
2247- self .arrow .figure = self .figure
2248- self .arrow .draw (renderer )
2249-
2250- if self .arrow_patch is not None :
2237+ if self .arrow_patch is not None : # FancyArrowPatch
22512238 if self .arrow_patch .figure is None and self .figure is not None :
22522239 self .arrow_patch .figure = self .figure
22532240 self .arrow_patch .draw (renderer )
22542241
2242+ # Draw text, including FancyBboxPatch, after FancyArrowPatch.
2243+ # Otherwise, a wedge arrowstyle can land partly on top of the Bbox.
2244+ Text .draw (self , renderer )
2245+
2246+
22552247 def get_window_extent (self , renderer = None ):
22562248 '''
22572249 Return a :class:`~matplotlib.transforms.Bbox` object bounding
0 commit comments