Skip to content

Commit eafb46d

Browse files
committed
add attributes xshift, yshift to annotations
1 parent 00355fb commit eafb46d

File tree

7 files changed

+108
-54
lines changed

7 files changed

+108
-54
lines changed

src/components/annotations/annotation_defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
8383

8484
// xanchor, yanchor
8585
coerce(axLetter + 'anchor');
86+
87+
// xshift, yshift
88+
coerce(axLetter + 'shift');
8689
}
8790

8891
// if you have one coordinate you should have both

src/components/annotations/attributes.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ module.exports = {
180180
description: [
181181
'Sets a distance, in pixels, to move the arrowhead away from the',
182182
'position it is pointing at, for example to point at the edge of',
183-
'a marker independent of zoom.'
183+
'a marker independent of zoom. Note that this shortens the arrow',
184+
'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`',
185+
'which moves everything by this amount.'
184186
].join(' ')
185187
},
186188
ax: {
@@ -292,6 +294,15 @@ module.exports = {
292294
'corresponds to the closest side.'
293295
].join(' ')
294296
},
297+
xshift: {
298+
valType: 'number',
299+
dflt: 0,
300+
role: 'style',
301+
description: [
302+
'Shifts the position of the whole annotation and arrow to the',
303+
'right (positive) or left (negative) by this many pixels.'
304+
].join(' ')
305+
},
295306
yref: {
296307
valType: 'enumerated',
297308
values: [
@@ -342,6 +353,15 @@ module.exports = {
342353
'corresponds to the closest side.'
343354
].join(' ')
344355
},
356+
yshift: {
357+
valType: 'number',
358+
dflt: 0,
359+
role: 'style',
360+
description: [
361+
'Shifts the position of the whole annotation and arrow up',
362+
'(positive) or down (negative) by this many pixels.'
363+
].join(' ')
364+
},
345365
clicktoshow: {
346366
valType: 'enumerated',
347367
values: [false, 'onoff', 'onout'],

src/components/annotations/calc_autorange.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,17 @@ function annAutorange(gd) {
5050
ya = Axes.getFromId(gd, ann.yref),
5151
headSize = 3 * ann.arrowsize * ann.arrowwidth || 0;
5252

53+
var headPlus, headMinus;
54+
5355
if(xa && xa.autorange) {
56+
headPlus = headSize + ann.xshift;
57+
headMinus = headSize - ann.xshift;
58+
5459
if(ann.axref === ann.xref) {
5560
// expand for the arrowhead (padded by arrowhead)
5661
Axes.expand(xa, [xa.r2c(ann.x)], {
57-
ppadplus: headSize,
58-
ppadminus: headSize
62+
ppadplus: headPlus,
63+
ppadminus: headMinus
5964
});
6065
// again for the textbox (padded by textbox)
6166
Axes.expand(xa, [xa.r2c(ann.ax)], {
@@ -65,17 +70,20 @@ function annAutorange(gd) {
6570
}
6671
else {
6772
Axes.expand(xa, [xa.r2c(ann.x)], {
68-
ppadplus: Math.max(ann._xpadplus, headSize),
69-
ppadminus: Math.max(ann._xpadminus, headSize)
73+
ppadplus: Math.max(ann._xpadplus, headPlus),
74+
ppadminus: Math.max(ann._xpadminus, headMinus)
7075
});
7176
}
7277
}
7378

7479
if(ya && ya.autorange) {
80+
headPlus = headSize - ann.yshift;
81+
headMinus = headSize + ann.yshift;
82+
7583
if(ann.ayref === ann.yref) {
7684
Axes.expand(ya, [ya.r2c(ann.y)], {
77-
ppadplus: headSize,
78-
ppadminus: headSize
85+
ppadplus: headPlus,
86+
ppadminus: headMinus
7987
});
8088
Axes.expand(ya, [ya.r2c(ann.ay)], {
8189
ppadplus: ann._ypadplus,
@@ -84,8 +92,8 @@ function annAutorange(gd) {
8492
}
8593
else {
8694
Axes.expand(ya, [ya.r2c(ann.y)], {
87-
ppadplus: Math.max(ann._ypadplus, headSize),
88-
ppadminus: Math.max(ann._ypadminus, headSize)
95+
ppadplus: Math.max(ann._ypadplus, headPlus),
96+
ppadminus: Math.max(ann._ypadminus, headMinus)
8997
});
9098
}
9199
}

src/components/annotations/draw.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ function drawOne(gd, index) {
236236
// but this one is the positive total size
237237
annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight),
238238
anchor = options[axLetter + 'anchor'],
239+
overallShift = options[axLetter + 'shift'] * (axLetter === 'x' ? 1 : -1),
239240
posPx = annPosPx[axLetter],
240241
basePx,
241242
textPadShift,
@@ -326,6 +327,9 @@ function drawOne(gd, index) {
326327
posPx.text -= shiftMinus;
327328
}
328329
}
330+
331+
posPx.tail += overallShift;
332+
posPx.head += overallShift;
329333
}
330334
else {
331335
// with no arrow, the text rotates and *then* we put the anchor
@@ -335,6 +339,10 @@ function drawOne(gd, index) {
335339
posPx.text = basePx + textShift;
336340
}
337341

342+
posPx.text += overallShift;
343+
textShift += overallShift;
344+
textPadShift += overallShift;
345+
338346
// padplus/minus are used by autorange
339347
options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift;
340348
options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift;
@@ -524,21 +532,17 @@ function drawOne(gd, index) {
524532

525533
update[annbase + '.x'] = xa ?
526534
xa.p2r(xa.r2p(options.x) + dx) :
527-
((headX + dx - gs.l) / gs.w);
535+
(options.x + (dx / gs.w));
528536
update[annbase + '.y'] = ya ?
529537
ya.p2r(ya.r2p(options.y) + dy) :
530-
(1 - ((headY + dy - gs.t) / gs.h));
538+
(options.y - (dy / gs.h));
531539

532540
if(options.axref === options.xref) {
533-
update[annbase + '.ax'] = xa ?
534-
xa.p2r(xa.r2p(options.ax) + dx) :
535-
((headX + dx - gs.l) / gs.w);
541+
update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx);
536542
}
537543

538544
if(options.ayref === options.yref) {
539-
update[annbase + '.ay'] = ya ?
540-
ya.p2r(ya.r2p(options.ay) + dy) :
541-
(1 - ((headY + dy - gs.t) / gs.h));
545+
update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy);
542546
}
543547

544548
arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
@@ -594,7 +598,8 @@ function drawOne(gd, index) {
594598
if(xa) update[annbase + '.x'] = options.x + dx / xa._m;
595599
else {
596600
var widthFraction = options._xsize / gs.w,
597-
xLeft = options.x + options._xshift / gs.w - widthFraction / 2;
601+
xLeft = options.x + (options._xshift - options.xshift) / gs.w -
602+
widthFraction / 2;
598603

599604
update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w,
600605
widthFraction, 0, 1, options.xanchor);
@@ -603,7 +608,8 @@ function drawOne(gd, index) {
603608
if(ya) update[annbase + '.y'] = options.y + dy / ya._m;
604609
else {
605610
var heightFraction = options._ysize / gs.h,
606-
yBottom = options.y - options._yshift / gs.h - heightFraction / 2;
611+
yBottom = options.y - (options._yshift + options.yshift) / gs.h -
612+
heightFraction / 2;
607613

608614
update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h,
609615
heightFraction, 0, 1, options.yanchor);
-220 Bytes
Loading

test/image/mocks/annotations-autorange.json

+26-14
Original file line numberDiff line numberDiff line change
@@ -52,49 +52,61 @@
5252
"width":800,
5353
"margin":{"r":40,"b":100,"l":40,"t":40},
5454
"annotations":[
55-
{"ay":0,"ax":50,"x":1,"y":1.5,"text":"Left"},
56-
{"ay":0,"ax":-50,"x":2,"y":1.5,"text":"Right"},
57-
{"x":1.5,"y":2,"text":"Top","ay":50,"ax":0},
58-
{"x":1.5,"y":1,"text":"Bottom","ay":-50,"ax":0},
55+
{"ay":0,"ax":50,"x":1,"y":1.5,"text":"Left", "xshift": -10, "yshift": -10},
56+
{"ay":0,"ax":-50,"x":2,"y":1.5,"text":"Right", "xshift": 10, "yshift": 10},
57+
{"x":1.5,"y":2,"text":"Top","ay":50,"ax":0, "xshift": -10, "yshift": 10},
58+
{"x":1.5,"y":1,"text":"Bottom","ay":-50,"ax":0, "xshift": 10, "yshift": -10},
5959
{
6060
"xref":"x2","yref":"y2","text":"From left","y":2,"ax":-17,"ay":0,"x":"2001-01-01",
61-
"xanchor": "right", "yanchor": "top", "textangle": 35, "bordercolor": "#444"
61+
"xanchor": "right", "yanchor": "top", "textangle": 35, "bordercolor": "#444",
62+
"xshift": 10
6263
},
6364
{
6465
"xref":"x2","yref":"y2","text":"From<br>right","y":2,"x":"2001-03-01","ay":0,"ax":50,
65-
"bgcolor": "#eee", "width": 50, "height": 40, "textangle": 70
66+
"bgcolor": "#eee", "width": 50, "height": 40, "textangle": 70,
67+
"xshift": -10
6668
},
6769
{
6870
"xref":"x2","yref":"y2","text":"From top","y":3,"ax":0,"ay":-27,"x":"2001-02-01",
69-
"xanchor": "left", "yanchor": "bottom", "textangle": -15, "bordercolor": "#444"
71+
"xanchor": "left", "yanchor": "bottom", "textangle": -15, "bordercolor": "#444",
72+
"yshift": -10
7073
},
7174
{
7275
"xref":"x2","yref":"y2","text":"From Bottom","y":1,"ax":0,"ay":50,"x":"2001-02-01",
73-
"bordercolor": "#444", "borderwidth": 3, "height": 30
76+
"bordercolor": "#444", "borderwidth": 3, "height": 30,
77+
"yshift": 10
7478
},
7579
{
7680
"xref":"x3","yref":"y3","text":"Left<br>no<br>arrow","y":1.5,"x":1,"showarrow":false,
7781
"bordercolor": "#444", "bgcolor": "#eee", "width": 50, "height": 60, "textangle": 10,
78-
"align": "right", "valign": "bottom"
82+
"align": "right", "valign": "bottom", "xshift": 10
83+
},
84+
{
85+
"xref":"x3","yref":"y3","text":"Right<br>no<br>arrow","y":1.5,"x":2,"showarrow":false,
86+
"xshift": -10
7987
},
80-
{"xref":"x3","yref":"y3","text":"Right<br>no<br>arrow","y":1.5,"x":2,"showarrow":false},
8188
{
8289
"xref":"x3","yref":"y3","text":"Bottom<br>no<br>arrow","y":1,"x":1.5,"showarrow":false,
8390
"bgcolor": "#eee", "width": 30, "height": 40, "textangle":-10,
84-
"align": "left", "valign": "top"
91+
"align": "left", "valign": "top", "yshift": 10
92+
},
93+
{
94+
"xref":"x3","yref":"y3","text":"Top<br>no<br>arrow","y":2,"x":1.5,"showarrow":false,
95+
"yshift": -10
8596
},
86-
{"xref":"x3","yref":"y3","text":"Top<br>no<br>arrow","y":2,"x":1.5,"showarrow":false},
8797
{
8898
"xref": "paper", "yref": "paper", "text": "On the<br>bottom of the plot",
8999
"x": 0.3, "y": -0.1, "showarrow": false,
90100
"xanchor": "right", "yanchor": "top", "width": 200, "height": 60,
91-
"bgcolor": "#eee", "bordercolor": "#444"
101+
"bgcolor": "#eee", "bordercolor": "#444",
102+
"xshift": -5, "yshift": 5
92103
},
93104
{
94105
"xref": "paper", "yref": "paper", "text": "blah blah blah blah<br>blah<br>blah<br>blah<br>blah<br>blah",
95106
"x": 0.3, "y": -0.25, "ax": 100, "ay": 0, "textangle": 40, "borderpad": 4,
96107
"xanchor": "left", "yanchor": "bottom", "align": "left", "valign": "top",
97-
"width": 60, "height": 40, "bgcolor": "#eee", "bordercolor": "#444"
108+
"width": 60, "height": 40, "bgcolor": "#eee", "bordercolor": "#444",
109+
"xshift": -5, "yshift": 5
98110
}
99111
]
100112
}

test/jasmine/tests/annotations_test.js

+26-21
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,9 @@ describe('annotations autorange', function() {
554554
it('should adapt to relayout calls', function(done) {
555555
Plotly.plot(gd, mock).then(function() {
556556
assertRanges(
557-
[0.97, 2.03], [0.97, 2.03],
558-
['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
559-
[0.9, 2.1], [0.86, 2.14]
557+
[0.91, 2.09], [0.91, 2.09],
558+
['2000-11-13', '2001-04-21'], [-0.069, 3.917],
559+
[0.88, 2.05], [0.92, 2.08]
560560
);
561561

562562
return Plotly.relayout(gd, {
@@ -567,9 +567,9 @@ describe('annotations autorange', function() {
567567
})
568568
.then(function() {
569569
assertRanges(
570-
[1.44, 2.02], [0.97, 2.03],
571-
['2001-01-18 15:06:04.0449', '2001-03-27 14:01:20.8989'], [-0.245, 4.245],
572-
[1.44, 2.1], [0.86, 2.14]
570+
[1.44, 2.02], [0.91, 2.09],
571+
['2001-01-18', '2001-03-27'], [-0.069, 3.917],
572+
[1.44, 2.1], [0.92, 2.08]
573573
);
574574

575575
return Plotly.relayout(gd, {
@@ -581,8 +581,8 @@ describe('annotations autorange', function() {
581581
.then(function() {
582582
assertRanges(
583583
[1.44, 2.02], [0.99, 1.52],
584-
['2001-01-31 23:59:59.999', '2001-02-01 00:00:00.001'], [-0.245, 4.245],
585-
[0.5, 2.5], [0.86, 2.14]
584+
['2001-01-31 23:59:59.999', '2001-02-01 00:00:00.001'], [-0.069, 3.917],
585+
[0.5, 2.5], [0.92, 2.08]
586586
);
587587

588588
return Plotly.relayout(gd, {
@@ -596,9 +596,9 @@ describe('annotations autorange', function() {
596596
})
597597
.then(function() {
598598
assertRanges(
599-
[0.97, 2.03], [0.97, 2.03],
600-
['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
601-
[0.9, 2.1], [0.86, 2.14]
599+
[0.91, 2.09], [0.91, 2.09],
600+
['2000-11-13', '2001-04-21'], [-0.069, 3.917],
601+
[0.88, 2.05], [0.92, 2.08]
602602
);
603603
})
604604
.catch(failTest)
@@ -619,10 +619,10 @@ describe('annotations autorange', function() {
619619
})
620620
.then(function() {
621621
assertRanges(
622-
[-1.09, 2.09], [0.94, 3.06],
622+
[-1.09, 2.25], [0.84, 3.06],
623623
// the other axes shouldn't change
624-
['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
625-
[0.9, 2.1], [0.86, 2.14]
624+
['2000-11-13', '2001-04-21'], [-0.069, 3.917],
625+
[0.88, 2.05], [0.92, 2.08]
626626
);
627627
})
628628
.catch(failTest)
@@ -961,7 +961,8 @@ describe('annotation effects', function() {
961961
showarrow: false,
962962
text: 'blah<br>blah blah',
963963
xref: 'paper',
964-
yref: 'paper'
964+
yref: 'paper',
965+
xshift: 5, yshift: 5
965966
}])
966967
.then(function() {
967968
var bbox = textBox().getBoundingClientRect();
@@ -981,7 +982,8 @@ describe('annotation effects', function() {
981982
xref: 'paper',
982983
yref: 'paper',
983984
xanchor: 'left',
984-
yanchor: 'top'
985+
yanchor: 'top',
986+
xshift: 5, yshift: 5
985987
}])
986988
.then(function() {
987989
// with offsets 0, 0 because the anchor doesn't change now
@@ -999,7 +1001,8 @@ describe('annotation effects', function() {
9991001
xref: 'paper',
10001002
yref: 'paper',
10011003
ax: 30,
1002-
ay: 30
1004+
ay: 30,
1005+
xshift: 5, yshift: 5
10031006
}])
10041007
.then(function() {
10051008
return checkDragging(arrowDrag, 0, 0, 1);
@@ -1014,7 +1017,8 @@ describe('annotation effects', function() {
10141017
x: 0,
10151018
y: 0,
10161019
showarrow: false,
1017-
text: 'blah<br>blah blah'
1020+
text: 'blah<br>blah blah',
1021+
xshift: 5, yshift: 5
10181022
}])
10191023
.then(function() {
10201024
return checkDragging(textDrag, 0, 0, 100);
@@ -1029,7 +1033,8 @@ describe('annotation effects', function() {
10291033
y: 0,
10301034
text: 'blah<br>blah blah',
10311035
ax: 30,
1032-
ay: -30
1036+
ay: -30,
1037+
xshift: 5, yshift: 5
10331038
}])
10341039
.then(function() {
10351040
return checkDragging(arrowDrag, 0, 0, 100);
@@ -1127,7 +1132,7 @@ describe('annotation effects', function() {
11271132
}
11281133

11291134
makePlot([
1130-
{x: 50, y: 50, text: 'hi', width: 50, ax: 0, ay: -40},
1135+
{x: 50, y: 50, text: 'hi', width: 50, ax: 0, ay: -40, xshift: -50, yshift: 50},
11311136
{x: 20, y: 20, text: 'bye', height: 40, showarrow: false},
11321137
{x: 80, y: 80, text: 'why?', ax: 0, ay: -40}
11331138
], {}) // turn off the default editable: true
@@ -1136,7 +1141,7 @@ describe('annotation effects', function() {
11361141
gd.on('plotly_clickannotation', function(evt) { clickData.push(evt); });
11371142

11381143
gdBB = gd.getBoundingClientRect();
1139-
pos0Head = [gdBB.left + 250, gdBB.top + 250];
1144+
pos0Head = [gdBB.left + 200, gdBB.top + 200];
11401145
pos0 = [pos0Head[0], pos0Head[1] - 40];
11411146
pos1 = [gdBB.left + 160, gdBB.top + 340];
11421147
pos2Head = [gdBB.left + 340, gdBB.top + 160];

0 commit comments

Comments
 (0)