@@ -2587,83 +2587,124 @@ def __call__(self, x0, y0, width, height, mutation_size):
25872587 return trans .transform_path (Path .unit_circle ())
25882588
25892589 @_register_style (_style_list )
2590- class LArrow :
2591- """A box in the shape of a left -pointing arrow."""
2590+ class RArrow :
2591+ """A box in the shape of a right -pointing arrow."""
25922592
2593- def __init__ (self , pad = 0.3 ):
2593+ def __init__ (self , pad = 0.3 , head_width = 1.5 , head_angle = 90 ):
25942594 """
25952595 Parameters
25962596 ----------
25972597 pad : float, default: 0.3
25982598 The amount of padding around the original box.
2599+ head_width : float, default: 1.5
2600+ The head width, relative to the arrow shaft width; must be
2601+ nonnegative.
2602+ head_angle : float, default: 90
2603+ The angle at the tip of the arrow, in degrees; must be nonzero
2604+ (modulo 360). Negative angles result in arrow heads pointing
2605+ backwards.
25992606 """
26002607 self .pad = pad
2608+ if head_width < 0 :
2609+ raise ValueError ("'head_width' must be nonnegative" )
2610+ self .head_width = head_width
2611+ if head_angle % 360 == 0 :
2612+ raise ValueError ("'head_angle' must be nonzero" )
2613+ self .head_angle = head_angle
26012614
26022615 def __call__ (self , x0 , y0 , width , height , mutation_size ):
2603- # padding
2616+ # padding & padded dimensions
26042617 pad = mutation_size * self .pad
2605- # width and height with padding added.
2606- width , height = width + 2 * pad , height + 2 * pad
2607- # boundary of the padded box
2618+ dx , dy = width + 2 * pad , height + 2 * pad
26082619 x0 , y0 = x0 - pad , y0 - pad ,
2609- x1 , y1 = x0 + width , y0 + height
2610-
2611- dx = (y1 - y0 ) / 2
2612- dxx = dx / 2
2613- x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
2614-
2615- return Path ._create_closed (
2616- [(x0 + dxx , y0 ), (x1 , y0 ), (x1 , y1 ), (x0 + dxx , y1 ),
2617- (x0 + dxx , y1 + dxx ), (x0 - dx , y0 + dx ),
2618- (x0 + dxx , y0 - dxx ), # arrow
2619- (x0 + dxx , y0 )])
2620+ x1 , y1 = x0 + dx , y0 + dy
2621+
2622+ head_dy = self .head_width * dy
2623+ mid_y = (y0 + y1 ) / 2
2624+ shaft_y0 = mid_y - head_dy / 2
2625+ shaft_y1 = mid_y + head_dy / 2
2626+
2627+ cot = 1 / math .tan (math .radians (self .head_angle / 2 ))
2628+
2629+ if cot > 0 :
2630+ # tip_x is chosen s.t. the angled line moving back from the tip hits
2631+ # i) if head_width > 1: the box corner, or ii) if head_width <
2632+ # 1 the box edge at the point giving the correct shaft width.
2633+ tip_x = x1 + cot * min (dy , head_dy ) / 2
2634+ shaft_x = tip_x - cot * head_dy / 2
2635+ return Path ._create_closed ([
2636+ (x0 , y0 ), (shaft_x , y0 ), (shaft_x , shaft_y0 ),
2637+ (tip_x , mid_y ),
2638+ (shaft_x , shaft_y1 ), (shaft_x , y1 ), (x0 , y1 ),
2639+ ])
2640+ else : # Reverse arrowhead.
2641+ # Make the long (outer) side of the arrowhead flush with the
2642+ # original box, and move back accordingly (but clipped to no
2643+ # more than the box length). If this clipping is necessary,
2644+ # the y positions at the short (inner) side of the arrowhead
2645+ # will be thicker than the original box, hence the need to
2646+ # recompute mid_y0 & mid_y1.
2647+ # If head_width < 1 no arrowhead is drawn.
2648+ dx = min (- cot * max (head_dy - dy , 0 ) / 2 , dx ) # cot < 0!
2649+ mid_y0 = min (shaft_y0 , y0 ) - dx / cot
2650+ mid_y1 = max (shaft_y1 , y1 ) + dx / cot
2651+ return Path ._create_closed ([
2652+ (x0 , y0 ), (x1 - dx , mid_y0 ), (x1 , shaft_y0 ),
2653+ (x1 , shaft_y1 ), (x1 - dx , mid_y1 ), (x0 , y1 ),
2654+ ])
26202655
26212656 @_register_style (_style_list )
2622- class RArrow ( LArrow ):
2623- """A box in the shape of a right -pointing arrow."""
2657+ class LArrow ( RArrow ):
2658+ """A box in the shape of a left -pointing arrow."""
26242659
26252660 def __call__ (self , x0 , y0 , width , height , mutation_size ):
2626- p = BoxStyle .LArrow .__call__ (
2627- self , x0 , y0 , width , height , mutation_size )
2661+ p = super ().__call__ (x0 , y0 , width , height , mutation_size )
26282662 p .vertices [:, 0 ] = 2 * x0 + width - p .vertices [:, 0 ]
26292663 return p
26302664
26312665 @_register_style (_style_list )
2632- class DArrow :
2666+ class DArrow ( RArrow ) :
26332667 """A box in the shape of a two-way arrow."""
2634- # Modified from LArrow to add a right arrow to the bbox.
2635-
2636- def __init__ (self , pad = 0.3 ):
2637- """
2638- Parameters
2639- ----------
2640- pad : float, default: 0.3
2641- The amount of padding around the original box.
2642- """
2643- self .pad = pad
2668+ # Modified from RArrow to have arrows on both sides; see comments above.
26442669
26452670 def __call__ (self , x0 , y0 , width , height , mutation_size ):
2646- # padding
2671+ # padding & padded dimensions
26472672 pad = mutation_size * self .pad
2648- # width and height with padding added.
2649- # The width is padded by the arrows, so we don't need to pad it.
2650- height = height + 2 * pad
2651- # boundary of the padded box
2652- x0 , y0 = x0 - pad , y0 - pad
2653- x1 , y1 = x0 + width , y0 + height
2654-
2655- dx = (y1 - y0 ) / 2
2656- dxx = dx / 2
2657- x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
2658-
2659- return Path ._create_closed ([
2660- (x0 + dxx , y0 ), (x1 , y0 ), # bot-segment
2661- (x1 , y0 - dxx ), (x1 + dx + dxx , y0 + dx ),
2662- (x1 , y1 + dxx ), # right-arrow
2663- (x1 , y1 ), (x0 + dxx , y1 ), # top-segment
2664- (x0 + dxx , y1 + dxx ), (x0 - dx , y0 + dx ),
2665- (x0 + dxx , y0 - dxx ), # left-arrow
2666- (x0 + dxx , y0 )])
2673+ dx , dy = width + 2 * pad , height + 2 * pad
2674+ x0 , y0 = x0 - pad , y0 - pad ,
2675+ x1 , y1 = x0 + dx , y0 + dy
2676+
2677+ head_dy = self .head_width * dy
2678+ mid_y = (y0 + y1 ) / 2
2679+ shaft_y0 = mid_y - head_dy / 2
2680+ shaft_y1 = mid_y + head_dy / 2
2681+
2682+ cot = 1 / math .tan (math .radians (self .head_angle / 2 ))
2683+
2684+ if cot > 0 :
2685+ tip_x0 = x0 - cot * min (dy , head_dy ) / 2
2686+ shaft_x0 = tip_x0 + cot * head_dy / 2
2687+ tip_x1 = x1 + cot * min (dy , head_dy ) / 2
2688+ shaft_x1 = tip_x1 - cot * head_dy / 2
2689+ return Path ._create_closed ([
2690+ (shaft_x0 , y1 ), (shaft_x0 , shaft_y1 ),
2691+ (tip_x0 , mid_y ),
2692+ (shaft_x0 , shaft_y0 ), (shaft_x0 , y0 ),
2693+ (shaft_x1 , y0 ), (shaft_x1 , shaft_y0 ),
2694+ (tip_x1 , mid_y ),
2695+ (shaft_x1 , shaft_y1 ), (shaft_x1 , y1 ),
2696+ ])
2697+ else :
2698+ # Don't move back by more than half the box length.
2699+ dx = min (- cot * max (head_dy - dy , 0 ) / 2 , dx / 2 ) # cot < 0!
2700+ mid_y0 = min (shaft_y0 , y0 ) - dx / cot
2701+ mid_y1 = max (shaft_y1 , y1 ) + dx / cot
2702+ return Path ._create_closed ([
2703+ (x0 , shaft_y0 ), (x0 + dx , mid_y0 ),
2704+ (x1 - dx , mid_y0 ), (x1 , shaft_y0 ),
2705+ (x1 , shaft_y1 ), (x1 - dx , mid_y1 ),
2706+ (x0 + dx , mid_y1 ), (x0 , shaft_y1 ),
2707+ ])
26672708
26682709 @_register_style (_style_list )
26692710 class Round :
0 commit comments