Skip to content

Commit ed8295a

Browse files
MFedMFed
authored andcommitted
Adding the ability to specify the tail of an annotation arrow in absolute point in grid terms rather than relative pixel offset terms.
1 parent ef6efd7 commit ed8295a

File tree

2 files changed

+72
-35
lines changed

2 files changed

+72
-35
lines changed

src/components/annotations/attributes.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,29 @@ module.exports = {
131131
role: 'style',
132132
description: 'Sets the width (in px) of annotation arrow.'
133133
},
134+
absoluteArrowTail: {
135+
valType: 'boolean',
136+
dflt: false,
137+
role: 'style',
138+
description: [
139+
'Indicates if the tail of this arrow is a point in ',
140+
'the coordinate system vs a relative offset in pixels.',
141+
'If *true*, `ax` is a value on the x axis and `ay` is ',
142+
'a value on the y axis.',
143+
'If *false*, `ax` and `ay` assume their normal offset ',
144+
'roles.'
145+
].join(' ')
146+
},
134147
ax: {
135148
valType: 'number',
136149
dflt: -10,
137150
role: 'info',
138151
description: [
139152
'Sets the x component of the arrow tail about the arrow head.',
140-
'A positive (negative) component corresponds to an arrow pointing',
141-
'from right to left (left to right)'
153+
'If `absoluteArrowTail` is false, a positive (negative) ',
154+
'component corresponds to an arrow pointing',
155+
'from right to left (left to right).',
156+
'If `absoluteArrowTail` is true, this is a value on the x axis.'
142157
].join(' ')
143158
},
144159
ay: {
@@ -147,8 +162,10 @@ module.exports = {
147162
role: 'info',
148163
description: [
149164
'Sets the y component of the arrow tail about the arrow head.',
150-
'A positive (negative) component corresponds to an arrow pointing',
151-
'from bottom to top (top to bottom)'
165+
'If `absoluteArrowTail` is false, a positive (negative) ',
166+
'component corresponds to an arrow pointing',
167+
'from bottom to top (top to bottom).',
168+
'If `absoluteArrowTail` is true, this is a value on the y axis.'
152169
].join(' ')
153170
},
154171
// positioning

src/components/annotations/index.js

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)