@@ -59,6 +59,7 @@ function handleAnnotationDefaults(annIn, fullLayout) {
59
59
coerce ( 'arrowwidth' , ( ( borderOpacity && borderWidth ) || 1 ) * 2 ) ;
60
60
coerce ( 'ax' ) ;
61
61
coerce ( 'ay' ) ;
62
+ coerce ( 'absoluteArrowTail' ) ;
62
63
63
64
// if you have one part of arrow length you should have both
64
65
Lib . noneOrAll ( annIn , annOut , [ 'ax' , 'ay' ] ) ;
@@ -89,6 +90,11 @@ function handleAnnotationDefaults(annIn, fullLayout) {
89
90
if ( ax . type === 'date' ) {
90
91
newval = Lib . dateTime2ms ( annIn [ axLetter ] ) ;
91
92
if ( newval !== false ) annIn [ axLetter ] = newval ;
93
+
94
+ if ( annIn . absoluteArrowTail ) {
95
+ var newvalB = Lib . dateTime2ms ( annIn [ 'a' + axLetter ] ) ;
96
+ if ( newvalB !== false ) annIn [ 'a' + axLetter ] = newvalB ;
97
+ }
92
98
}
93
99
else if ( ( ax . _categories || [ ] ) . length ) {
94
100
newval = ax . _categories . indexOf ( annIn [ axLetter ] ) ;
@@ -450,13 +456,17 @@ annotations.draw = function(gd, index, opt, value) {
450
456
}
451
457
452
458
var alignShift = 0 ;
453
- if ( options . showarrow ) {
454
- alignShift = options [ 'a' + axLetter ] ;
455
- }
456
- else {
457
- alignShift = annSize * shiftFraction ( alignPosition , anchor ) ;
459
+ if ( options . absoluteArrowTail ) {
460
+ annPosPx [ 'aa' + axLetter ] = ax . _offset + ax . l2p ( options [ 'a' + axLetter ] ) ;
461
+ } else {
462
+ if ( options . showarrow ) {
463
+ alignShift = options [ 'a' + axLetter ] ;
464
+ }
465
+ else {
466
+ alignShift = annSize * shiftFraction ( alignPosition , anchor ) ;
467
+ }
468
+ annPosPx [ axLetter ] += alignShift ;
458
469
}
459
- annPosPx [ axLetter ] += alignShift ;
460
470
461
471
// save the current axis type for later log/linear changes
462
472
options [ '_' + axLetter + 'type' ] = ax && ax . type ;
@@ -473,11 +483,16 @@ annotations.draw = function(gd, index, opt, value) {
473
483
474
484
var arrowX , arrowY ;
475
485
476
- // make sure the arrowhead (if there is one)
477
- // and the annotation center are visible
478
- if ( options . showarrow ) {
479
- arrowX = Lib . constrain ( annPosPx . x - options . ax , 1 , fullLayout . width - 1 ) ;
480
- arrowY = Lib . constrain ( annPosPx . y - options . ay , 1 , fullLayout . height - 1 ) ;
486
+ if ( options . absoluteArrowTail ) {
487
+ arrowX = Lib . constrain ( annPosPx . x , 1 , fullLayout . width - 1 ) ;
488
+ arrowY = Lib . constrain ( annPosPx . y , 1 , fullLayout . height - 1 ) ;
489
+ } else {
490
+ // make sure the arrowhead (if there is one)
491
+ // and the annotation center are visible
492
+ if ( options . showarrow ) {
493
+ arrowX = Lib . constrain ( annPosPx . x - options . ax , 1 , fullLayout . width - 1 ) ;
494
+ arrowY = Lib . constrain ( annPosPx . y - options . ay , 1 , fullLayout . height - 1 ) ;
495
+ }
481
496
}
482
497
annPosPx . x = Lib . constrain ( annPosPx . x , 1 , fullLayout . width - 1 ) ;
483
498
annPosPx . y = Lib . constrain ( annPosPx . y , 1 , fullLayout . height - 1 ) ;
@@ -534,27 +549,32 @@ annotations.draw = function(gd, index, opt, value) {
534
549
[ arrowX0 + xHalf , arrowY0 - yHalf , arrowX0 - xHalf , arrowY0 - yHalf ]
535
550
] . map ( applyTransform2 ) ;
536
551
537
- // Remove the line if it ends inside the box. Use ray
538
- // casting for rotated boxes: see which edges intersect a
539
- // line from the arrowhead to far away and reduce with xor
540
- // to get the parity of the number of intersections.
541
- if ( edges . reduce ( function ( a , x ) {
542
- return a ^
543
- ! ! lineIntersect ( arrowX , arrowY , arrowX + 1e6 , arrowY + 1e6 ,
544
- x [ 0 ] , x [ 1 ] , x [ 2 ] , x [ 3 ] ) ;
545
- } , false ) ) {
546
- // no line or arrow - so quit drawArrow now
547
- return ;
548
- }
549
-
550
- edges . forEach ( function ( x ) {
551
- var p = lineIntersect ( arrowX0 , arrowY0 , arrowX , arrowY ,
552
- x [ 0 ] , x [ 1 ] , x [ 2 ] , x [ 3 ] ) ;
553
- if ( p ) {
554
- arrowX0 = p . x ;
555
- arrowY0 = p . y ;
552
+ if ( options . absoluteArrowTail ) {
553
+ arrowX0 = annPosPx . aax ;
554
+ arrowY0 = annPosPx . aay ;
555
+ } else {
556
+ // Remove the line if it ends inside the box. Use ray
557
+ // casting for rotated boxes: see which edges intersect a
558
+ // line from the arrowhead to far away and reduce with xor
559
+ // to get the parity of the number of intersections.
560
+ if ( edges . reduce ( function ( a , x ) {
561
+ return a ^
562
+ ! ! lineIntersect ( arrowX , arrowY , arrowX + 1e6 , arrowY + 1e6 ,
563
+ x [ 0 ] , x [ 1 ] , x [ 2 ] , x [ 3 ] ) ;
564
+ } , false ) ) {
565
+ // no line or arrow - so quit drawArrow now
566
+ return ;
556
567
}
557
- } ) ;
568
+
569
+ edges . forEach ( function ( x ) {
570
+ var p = lineIntersect ( arrowX0 , arrowY0 , arrowX , arrowY ,
571
+ x [ 0 ] , x [ 1 ] , x [ 2 ] , x [ 3 ] ) ;
572
+ if ( p ) {
573
+ arrowX0 = p . x ;
574
+ arrowY0 = p . y ;
575
+ }
576
+ } ) ;
577
+ }
558
578
559
579
var strokewidth = options . arrowwidth ,
560
580
arrowColor = options . arrowcolor ;
@@ -618,10 +638,19 @@ annotations.draw = function(gd, index, opt, value) {
618
638
( options . y + dy / ya . _m ) :
619
639
( 1 - ( ( arrowY + dy - gs . t ) / gs . h ) ) ;
620
640
621
- anng . attr ( {
622
- transform : 'rotate(' + textangle + ',' +
623
- xcenter + ',' + ycenter + ')'
624
- } ) ;
641
+ if ( options . absoluteArrowTail ) {
642
+ update [ annbase + '.ax' ] = xa ?
643
+ ( options . ax + dx / xa . _m ) :
644
+ ( ( arrowX + dx - gs . l ) / gs . w ) ;
645
+ update [ annbase + '.ay' ] = ya ?
646
+ ( options . ay + dy / ya . _m ) :
647
+ ( 1 - ( ( arrowY + dy - gs . t ) / gs . h ) ) ;
648
+ } else {
649
+ anng . attr ( {
650
+ transform : 'rotate(' + textangle + ',' +
651
+ xcenter + ',' + ycenter + ')'
652
+ } ) ;
653
+ }
625
654
} ,
626
655
doneFn : function ( dragged ) {
627
656
if ( dragged ) {
0 commit comments