diff --git a/src/components/annotations/annotation_defaults.js b/src/components/annotations/annotation_defaults.js
index b59c0aacd59..f81436159da 100644
--- a/src/components/annotations/annotation_defaults.js
+++ b/src/components/annotations/annotation_defaults.js
@@ -83,6 +83,9 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
// xanchor, yanchor
coerce(axLetter + 'anchor');
+
+ // xshift, yshift
+ coerce(axLetter + 'shift');
}
// if you have one coordinate you should have both
diff --git a/src/components/annotations/attributes.js b/src/components/annotations/attributes.js
index dd07be6ce28..779f4a899e4 100644
--- a/src/components/annotations/attributes.js
+++ b/src/components/annotations/attributes.js
@@ -180,7 +180,9 @@ module.exports = {
description: [
'Sets a distance, in pixels, to move the arrowhead away from the',
'position it is pointing at, for example to point at the edge of',
- 'a marker independent of zoom.'
+ 'a marker independent of zoom. Note that this shortens the arrow',
+ 'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`',
+ 'which moves everything by this amount.'
].join(' ')
},
ax: {
@@ -292,6 +294,15 @@ module.exports = {
'corresponds to the closest side.'
].join(' ')
},
+ xshift: {
+ valType: 'number',
+ dflt: 0,
+ role: 'style',
+ description: [
+ 'Shifts the position of the whole annotation and arrow to the',
+ 'right (positive) or left (negative) by this many pixels.'
+ ].join(' ')
+ },
yref: {
valType: 'enumerated',
values: [
@@ -342,6 +353,15 @@ module.exports = {
'corresponds to the closest side.'
].join(' ')
},
+ yshift: {
+ valType: 'number',
+ dflt: 0,
+ role: 'style',
+ description: [
+ 'Shifts the position of the whole annotation and arrow up',
+ '(positive) or down (negative) by this many pixels.'
+ ].join(' ')
+ },
clicktoshow: {
valType: 'enumerated',
values: [false, 'onoff', 'onout'],
diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js
index f68ea537c63..37646b3993a 100644
--- a/src/components/annotations/calc_autorange.js
+++ b/src/components/annotations/calc_autorange.js
@@ -50,12 +50,17 @@ function annAutorange(gd) {
ya = Axes.getFromId(gd, ann.yref),
headSize = 3 * ann.arrowsize * ann.arrowwidth || 0;
+ var headPlus, headMinus;
+
if(xa && xa.autorange) {
+ headPlus = headSize + ann.xshift;
+ headMinus = headSize - ann.xshift;
+
if(ann.axref === ann.xref) {
// expand for the arrowhead (padded by arrowhead)
Axes.expand(xa, [xa.r2c(ann.x)], {
- ppadplus: headSize,
- ppadminus: headSize
+ ppadplus: headPlus,
+ ppadminus: headMinus
});
// again for the textbox (padded by textbox)
Axes.expand(xa, [xa.r2c(ann.ax)], {
@@ -65,17 +70,20 @@ function annAutorange(gd) {
}
else {
Axes.expand(xa, [xa.r2c(ann.x)], {
- ppadplus: Math.max(ann._xpadplus, headSize),
- ppadminus: Math.max(ann._xpadminus, headSize)
+ ppadplus: Math.max(ann._xpadplus, headPlus),
+ ppadminus: Math.max(ann._xpadminus, headMinus)
});
}
}
if(ya && ya.autorange) {
+ headPlus = headSize - ann.yshift;
+ headMinus = headSize + ann.yshift;
+
if(ann.ayref === ann.yref) {
Axes.expand(ya, [ya.r2c(ann.y)], {
- ppadplus: headSize,
- ppadminus: headSize
+ ppadplus: headPlus,
+ ppadminus: headMinus
});
Axes.expand(ya, [ya.r2c(ann.ay)], {
ppadplus: ann._ypadplus,
@@ -84,8 +92,8 @@ function annAutorange(gd) {
}
else {
Axes.expand(ya, [ya.r2c(ann.y)], {
- ppadplus: Math.max(ann._ypadplus, headSize),
- ppadminus: Math.max(ann._ypadminus, headSize)
+ ppadplus: Math.max(ann._ypadplus, headPlus),
+ ppadminus: Math.max(ann._ypadminus, headMinus)
});
}
}
diff --git a/src/components/annotations/draw.js b/src/components/annotations/draw.js
index 80c5c76a19a..371af5ef0da 100644
--- a/src/components/annotations/draw.js
+++ b/src/components/annotations/draw.js
@@ -236,6 +236,7 @@ function drawOne(gd, index) {
// but this one is the positive total size
annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight),
anchor = options[axLetter + 'anchor'],
+ overallShift = options[axLetter + 'shift'] * (axLetter === 'x' ? 1 : -1),
posPx = annPosPx[axLetter],
basePx,
textPadShift,
@@ -326,6 +327,9 @@ function drawOne(gd, index) {
posPx.text -= shiftMinus;
}
}
+
+ posPx.tail += overallShift;
+ posPx.head += overallShift;
}
else {
// with no arrow, the text rotates and *then* we put the anchor
@@ -335,6 +339,10 @@ function drawOne(gd, index) {
posPx.text = basePx + textShift;
}
+ posPx.text += overallShift;
+ textShift += overallShift;
+ textPadShift += overallShift;
+
// padplus/minus are used by autorange
options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift;
options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift;
@@ -524,21 +532,17 @@ function drawOne(gd, index) {
update[annbase + '.x'] = xa ?
xa.p2r(xa.r2p(options.x) + dx) :
- ((headX + dx - gs.l) / gs.w);
+ (options.x + (dx / gs.w));
update[annbase + '.y'] = ya ?
ya.p2r(ya.r2p(options.y) + dy) :
- (1 - ((headY + dy - gs.t) / gs.h));
+ (options.y - (dy / gs.h));
if(options.axref === options.xref) {
- update[annbase + '.ax'] = xa ?
- xa.p2r(xa.r2p(options.ax) + dx) :
- ((headX + dx - gs.l) / gs.w);
+ update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx);
}
if(options.ayref === options.yref) {
- update[annbase + '.ay'] = ya ?
- ya.p2r(ya.r2p(options.ay) + dy) :
- (1 - ((headY + dy - gs.t) / gs.h));
+ update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy);
}
arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
@@ -594,7 +598,8 @@ function drawOne(gd, index) {
if(xa) update[annbase + '.x'] = options.x + dx / xa._m;
else {
var widthFraction = options._xsize / gs.w,
- xLeft = options.x + options._xshift / gs.w - widthFraction / 2;
+ xLeft = options.x + (options._xshift - options.xshift) / gs.w -
+ widthFraction / 2;
update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w,
widthFraction, 0, 1, options.xanchor);
@@ -603,7 +608,8 @@ function drawOne(gd, index) {
if(ya) update[annbase + '.y'] = options.y + dy / ya._m;
else {
var heightFraction = options._ysize / gs.h,
- yBottom = options.y - options._yshift / gs.h - heightFraction / 2;
+ yBottom = options.y - (options._yshift + options.yshift) / gs.h -
+ heightFraction / 2;
update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h,
heightFraction, 0, 1, options.yanchor);
diff --git a/test/image/baselines/annotations-autorange.png b/test/image/baselines/annotations-autorange.png
index fae1b98ca22..da920e7ed1c 100644
Binary files a/test/image/baselines/annotations-autorange.png and b/test/image/baselines/annotations-autorange.png differ
diff --git a/test/image/mocks/annotations-autorange.json b/test/image/mocks/annotations-autorange.json
index 3fa5453db48..ded30e807e6 100644
--- a/test/image/mocks/annotations-autorange.json
+++ b/test/image/mocks/annotations-autorange.json
@@ -52,49 +52,61 @@
"width":800,
"margin":{"r":40,"b":100,"l":40,"t":40},
"annotations":[
- {"ay":0,"ax":50,"x":1,"y":1.5,"text":"Left"},
- {"ay":0,"ax":-50,"x":2,"y":1.5,"text":"Right"},
- {"x":1.5,"y":2,"text":"Top","ay":50,"ax":0},
- {"x":1.5,"y":1,"text":"Bottom","ay":-50,"ax":0},
+ {"ay":0,"ax":50,"x":1,"y":1.5,"text":"Left", "xshift": -10, "yshift": -10},
+ {"ay":0,"ax":-50,"x":2,"y":1.5,"text":"Right", "xshift": 10, "yshift": 10},
+ {"x":1.5,"y":2,"text":"Top","ay":50,"ax":0, "xshift": -10, "yshift": 10},
+ {"x":1.5,"y":1,"text":"Bottom","ay":-50,"ax":0, "xshift": 10, "yshift": -10},
{
"xref":"x2","yref":"y2","text":"From left","y":2,"ax":-17,"ay":0,"x":"2001-01-01",
- "xanchor": "right", "yanchor": "top", "textangle": 35, "bordercolor": "#444"
+ "xanchor": "right", "yanchor": "top", "textangle": 35, "bordercolor": "#444",
+ "xshift": 10
},
{
"xref":"x2","yref":"y2","text":"From
right","y":2,"x":"2001-03-01","ay":0,"ax":50,
- "bgcolor": "#eee", "width": 50, "height": 40, "textangle": 70
+ "bgcolor": "#eee", "width": 50, "height": 40, "textangle": 70,
+ "xshift": -10
},
{
"xref":"x2","yref":"y2","text":"From top","y":3,"ax":0,"ay":-27,"x":"2001-02-01",
- "xanchor": "left", "yanchor": "bottom", "textangle": -15, "bordercolor": "#444"
+ "xanchor": "left", "yanchor": "bottom", "textangle": -15, "bordercolor": "#444",
+ "yshift": -10
},
{
"xref":"x2","yref":"y2","text":"From Bottom","y":1,"ax":0,"ay":50,"x":"2001-02-01",
- "bordercolor": "#444", "borderwidth": 3, "height": 30
+ "bordercolor": "#444", "borderwidth": 3, "height": 30,
+ "yshift": 10
},
{
"xref":"x3","yref":"y3","text":"Left
no
arrow","y":1.5,"x":1,"showarrow":false,
"bordercolor": "#444", "bgcolor": "#eee", "width": 50, "height": 60, "textangle": 10,
- "align": "right", "valign": "bottom"
+ "align": "right", "valign": "bottom", "xshift": 10
+ },
+ {
+ "xref":"x3","yref":"y3","text":"Right
no
arrow","y":1.5,"x":2,"showarrow":false,
+ "xshift": -10
},
- {"xref":"x3","yref":"y3","text":"Right
no
arrow","y":1.5,"x":2,"showarrow":false},
{
"xref":"x3","yref":"y3","text":"Bottom
no
arrow","y":1,"x":1.5,"showarrow":false,
"bgcolor": "#eee", "width": 30, "height": 40, "textangle":-10,
- "align": "left", "valign": "top"
+ "align": "left", "valign": "top", "yshift": 10
+ },
+ {
+ "xref":"x3","yref":"y3","text":"Top
no
arrow","y":2,"x":1.5,"showarrow":false,
+ "yshift": -10
},
- {"xref":"x3","yref":"y3","text":"Top
no
arrow","y":2,"x":1.5,"showarrow":false},
{
"xref": "paper", "yref": "paper", "text": "On the
bottom of the plot",
"x": 0.3, "y": -0.1, "showarrow": false,
"xanchor": "right", "yanchor": "top", "width": 200, "height": 60,
- "bgcolor": "#eee", "bordercolor": "#444"
+ "bgcolor": "#eee", "bordercolor": "#444",
+ "xshift": -5, "yshift": 5
},
{
"xref": "paper", "yref": "paper", "text": "blah blah blah blah
blah
blah
blah
blah
blah",
"x": 0.3, "y": -0.25, "ax": 100, "ay": 0, "textangle": 40, "borderpad": 4,
"xanchor": "left", "yanchor": "bottom", "align": "left", "valign": "top",
- "width": 60, "height": 40, "bgcolor": "#eee", "bordercolor": "#444"
+ "width": 60, "height": 40, "bgcolor": "#eee", "bordercolor": "#444",
+ "xshift": -5, "yshift": 5
}
]
}
diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js
index 9a24aa600e5..3a1711daabb 100644
--- a/test/jasmine/tests/annotations_test.js
+++ b/test/jasmine/tests/annotations_test.js
@@ -554,9 +554,9 @@ describe('annotations autorange', function() {
it('should adapt to relayout calls', function(done) {
Plotly.plot(gd, mock).then(function() {
assertRanges(
- [0.97, 2.03], [0.97, 2.03],
- ['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
- [0.9, 2.1], [0.86, 2.14]
+ [0.91, 2.09], [0.91, 2.09],
+ ['2000-11-13', '2001-04-21'], [-0.069, 3.917],
+ [0.88, 2.05], [0.92, 2.08]
);
return Plotly.relayout(gd, {
@@ -567,9 +567,9 @@ describe('annotations autorange', function() {
})
.then(function() {
assertRanges(
- [1.44, 2.02], [0.97, 2.03],
- ['2001-01-18 15:06:04.0449', '2001-03-27 14:01:20.8989'], [-0.245, 4.245],
- [1.44, 2.1], [0.86, 2.14]
+ [1.44, 2.02], [0.91, 2.09],
+ ['2001-01-18', '2001-03-27'], [-0.069, 3.917],
+ [1.44, 2.1], [0.92, 2.08]
);
return Plotly.relayout(gd, {
@@ -581,8 +581,8 @@ describe('annotations autorange', function() {
.then(function() {
assertRanges(
[1.44, 2.02], [0.99, 1.52],
- ['2001-01-31 23:59:59.999', '2001-02-01 00:00:00.001'], [-0.245, 4.245],
- [0.5, 2.5], [0.86, 2.14]
+ ['2001-01-31 23:59:59.999', '2001-02-01 00:00:00.001'], [-0.069, 3.917],
+ [0.5, 2.5], [0.92, 2.08]
);
return Plotly.relayout(gd, {
@@ -596,9 +596,9 @@ describe('annotations autorange', function() {
})
.then(function() {
assertRanges(
- [0.97, 2.03], [0.97, 2.03],
- ['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
- [0.9, 2.1], [0.86, 2.14]
+ [0.91, 2.09], [0.91, 2.09],
+ ['2000-11-13', '2001-04-21'], [-0.069, 3.917],
+ [0.88, 2.05], [0.92, 2.08]
);
})
.catch(failTest)
@@ -619,10 +619,10 @@ describe('annotations autorange', function() {
})
.then(function() {
assertRanges(
- [-1.09, 2.09], [0.94, 3.06],
+ [-1.09, 2.25], [0.84, 3.06],
// the other axes shouldn't change
- ['2000-10-01 08:23:18.0583', '2001-06-05 19:20:23.301'], [-0.245, 4.245],
- [0.9, 2.1], [0.86, 2.14]
+ ['2000-11-13', '2001-04-21'], [-0.069, 3.917],
+ [0.88, 2.05], [0.92, 2.08]
);
})
.catch(failTest)
@@ -961,7 +961,8 @@ describe('annotation effects', function() {
showarrow: false,
text: 'blah
blah blah',
xref: 'paper',
- yref: 'paper'
+ yref: 'paper',
+ xshift: 5, yshift: 5
}])
.then(function() {
var bbox = textBox().getBoundingClientRect();
@@ -981,7 +982,8 @@ describe('annotation effects', function() {
xref: 'paper',
yref: 'paper',
xanchor: 'left',
- yanchor: 'top'
+ yanchor: 'top',
+ xshift: 5, yshift: 5
}])
.then(function() {
// with offsets 0, 0 because the anchor doesn't change now
@@ -999,7 +1001,8 @@ describe('annotation effects', function() {
xref: 'paper',
yref: 'paper',
ax: 30,
- ay: 30
+ ay: 30,
+ xshift: 5, yshift: 5
}])
.then(function() {
return checkDragging(arrowDrag, 0, 0, 1);
@@ -1014,7 +1017,8 @@ describe('annotation effects', function() {
x: 0,
y: 0,
showarrow: false,
- text: 'blah
blah blah'
+ text: 'blah
blah blah',
+ xshift: 5, yshift: 5
}])
.then(function() {
return checkDragging(textDrag, 0, 0, 100);
@@ -1029,7 +1033,8 @@ describe('annotation effects', function() {
y: 0,
text: 'blah
blah blah',
ax: 30,
- ay: -30
+ ay: -30,
+ xshift: 5, yshift: 5
}])
.then(function() {
return checkDragging(arrowDrag, 0, 0, 100);
@@ -1127,7 +1132,7 @@ describe('annotation effects', function() {
}
makePlot([
- {x: 50, y: 50, text: 'hi', width: 50, ax: 0, ay: -40},
+ {x: 50, y: 50, text: 'hi', width: 50, ax: 0, ay: -40, xshift: -50, yshift: 50},
{x: 20, y: 20, text: 'bye', height: 40, showarrow: false},
{x: 80, y: 80, text: 'why?', ax: 0, ay: -40}
], {}) // turn off the default editable: true
@@ -1136,7 +1141,7 @@ describe('annotation effects', function() {
gd.on('plotly_clickannotation', function(evt) { clickData.push(evt); });
gdBB = gd.getBoundingClientRect();
- pos0Head = [gdBB.left + 250, gdBB.top + 250];
+ pos0Head = [gdBB.left + 200, gdBB.top + 200];
pos0 = [pos0Head[0], pos0Head[1] - 40];
pos1 = [gdBB.left + 160, gdBB.top + 340];
pos2Head = [gdBB.left + 340, gdBB.top + 160];