@@ -59,6 +59,7 @@ function handleAnnotationDefaults(annIn, fullLayout) {
5959 coerce ( 'arrowwidth' , ( ( borderOpacity && borderWidth ) || 1 ) * 2 ) ;
6060 coerce ( 'ax' ) ;
6161 coerce ( 'ay' ) ;
62+ coerce ( 'absoluteArrowTail' ) ;
6263
6364 // if you have one part of arrow length you should have both
6465 Lib . noneOrAll ( annIn , annOut , [ 'ax' , 'ay' ] ) ;
@@ -89,6 +90,11 @@ function handleAnnotationDefaults(annIn, fullLayout) {
8990 if ( ax . type === 'date' ) {
9091 newval = Lib . dateTime2ms ( annIn [ axLetter ] ) ;
9192 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+ }
9298 }
9399 else if ( ( ax . _categories || [ ] ) . length ) {
94100 newval = ax . _categories . indexOf ( annIn [ axLetter ] ) ;
@@ -450,13 +456,17 @@ annotations.draw = function(gd, index, opt, value) {
450456 }
451457
452458 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 ;
458469 }
459- annPosPx [ axLetter ] += alignShift ;
460470
461471 // save the current axis type for later log/linear changes
462472 options [ '_' + axLetter + 'type' ] = ax && ax . type ;
@@ -473,11 +483,16 @@ annotations.draw = function(gd, index, opt, value) {
473483
474484 var arrowX , arrowY ;
475485
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+ }
481496 }
482497 annPosPx . x = Lib . constrain ( annPosPx . x , 1 , fullLayout . width - 1 ) ;
483498 annPosPx . y = Lib . constrain ( annPosPx . y , 1 , fullLayout . height - 1 ) ;
@@ -534,27 +549,32 @@ annotations.draw = function(gd, index, opt, value) {
534549 [ arrowX0 + xHalf , arrowY0 - yHalf , arrowX0 - xHalf , arrowY0 - yHalf ]
535550 ] . map ( applyTransform2 ) ;
536551
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 ;
556567 }
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+ }
558578
559579 var strokewidth = options . arrowwidth ,
560580 arrowColor = options . arrowcolor ;
0 commit comments