27
27
from matplotlib .transforms import Affine2D , Bbox , Transform
28
28
from matplotlib .transforms import BboxBase , BboxTransformTo
29
29
from matplotlib .lines import Line2D
30
-
30
+ from matplotlib . path import Path
31
31
from matplotlib .artist import allow_rasterization
32
32
33
33
from matplotlib .backend_bases import RendererBase
@@ -539,7 +539,7 @@ def update_bbox_position_size(self, renderer):
539
539
self ._bbox_patch .set_transform (tr )
540
540
fontsize_in_pixel = renderer .points_to_pixels (self .get_size ())
541
541
self ._bbox_patch .set_mutation_scale (fontsize_in_pixel )
542
- # self._bbox_patch.draw(renderer)
542
+
543
543
544
544
def _draw_bbox (self , renderer , posx , posy ):
545
545
@@ -558,6 +558,7 @@ def _draw_bbox(self, renderer, posx, posy):
558
558
self ._bbox_patch .set_mutation_scale (fontsize_in_pixel )
559
559
self ._bbox_patch .draw (renderer )
560
560
561
+
561
562
def _update_clip_properties (self ):
562
563
clipprops = dict (clip_box = self .clipbox ,
563
564
clip_path = self ._clippath ,
@@ -2046,9 +2047,18 @@ def __init__(self, s, xy,
2046
2047
2047
2048
self .arrow = None
2048
2049
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
2052
2062
self .arrow_patch = FancyArrowPatch ((0 , 0 ), (1 , 1 ),
2053
2063
** arrowprops )
2054
2064
else :
@@ -2059,7 +2069,9 @@ def contains(self, event):
2059
2069
if self .arrow is not None :
2060
2070
in_arrow , _ = self .arrow .contains (event )
2061
2071
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
2063
2075
2064
2076
return contains , tinfo
2065
2077
@@ -2102,6 +2114,7 @@ def _update_position_xytext(self, renderer, xy_pixel):
2102
2114
self .set_transform (self ._get_xy_transform (
2103
2115
renderer , self .xy , self .anncoords ))
2104
2116
2117
+
2105
2118
ox0 , oy0 = self ._get_xy_display ()
2106
2119
ox1 , oy1 = xy_pixel
2107
2120
@@ -2114,111 +2127,94 @@ def _update_position_xytext(self, renderer, xy_pixel):
2114
2127
yc = 0.5 * (b + t )
2115
2128
2116
2129
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 )
2117
2133
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 )
2168
2147
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:
2171
2155
# pick the x,y corner of the text bbox closest to point
2172
2156
# 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 ]
2174
2161
dsu .sort ()
2175
- _ , x = dsu [0 ]
2162
+ _ , ( x , relposx ) = dsu [0 ]
2176
2163
2177
- dsu = [(abs (val - y0 ), val ) for val in ( b , t , yc ) ]
2164
+ dsu = [(abs (val [ 0 ] - y0 ), val ) for val in ypos ]
2178
2165
dsu .sort ()
2179
- _ , y = dsu [0 ]
2166
+ _ , ( y , relposy ) = dsu [0 ]
2180
2167
2181
- shrink = d . pop ( 'shrink' , 0.0 )
2168
+ self . _arrow_relpos = ( relposx , relposy )
2182
2169
2183
- theta = math .atan2 (y - y0 , x - x0 )
2184
2170
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
2187
2174
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 )
2196
2175
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 ]
2198
2183
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.
2205
2189
2206
- # For arrow_patch, use textbox as patchA by default.
2190
+ self . arrow_patch . set_positions (( ox0 , oy0 ), ( ox1 , oy1 ))
2207
2191
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 )
2210
2217
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 )
2222
2218
2223
2219
@allow_rasterization
2224
2220
def draw (self , renderer ):
@@ -2238,20 +2234,16 @@ def draw(self, renderer):
2238
2234
self ._update_position_xytext (renderer , xy_pixel )
2239
2235
self .update_bbox_position_size (renderer )
2240
2236
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
2251
2238
if self .arrow_patch .figure is None and self .figure is not None :
2252
2239
self .arrow_patch .figure = self .figure
2253
2240
self .arrow_patch .draw (renderer )
2254
2241
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
+
2255
2247
def get_window_extent (self , renderer = None ):
2256
2248
'''
2257
2249
Return a :class:`~matplotlib.transforms.Bbox` object bounding
0 commit comments