Skip to content

add attributes xshift, yshift to annotations #1590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 12, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/annotations/annotation_defaults.js
Original file line number Diff line number Diff line change
@@ -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
22 changes: 21 additions & 1 deletion src/components/annotations/attributes.js
Original file line number Diff line number Diff line change
@@ -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'],
24 changes: 16 additions & 8 deletions src/components/annotations/calc_autorange.js
Original file line number Diff line number Diff line change
@@ -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)
});
}
}
26 changes: 16 additions & 10 deletions src/components/annotations/draw.js
Original file line number Diff line number Diff line change
@@ -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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice simplification 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

motivated by the fact that the old way would have needed to explicitly account for shift - notice though in the showarrow: false case I did need to just include shift, due to the auto-anchor business...

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);
Binary file modified test/image/baselines/annotations-autorange.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 26 additions & 14 deletions test/image/mocks/annotations-autorange.json
Original file line number Diff line number Diff line change
@@ -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<br>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<br>no<br>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<br>no<br>arrow","y":1.5,"x":2,"showarrow":false,
"xshift": -10
},
{"xref":"x3","yref":"y3","text":"Right<br>no<br>arrow","y":1.5,"x":2,"showarrow":false},
{
"xref":"x3","yref":"y3","text":"Bottom<br>no<br>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<br>no<br>arrow","y":2,"x":1.5,"showarrow":false,
"yshift": -10
},
{"xref":"x3","yref":"y3","text":"Top<br>no<br>arrow","y":2,"x":1.5,"showarrow":false},
{
"xref": "paper", "yref": "paper", "text": "On the<br>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<br>blah<br>blah<br>blah<br>blah<br>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
}
]
}
47 changes: 26 additions & 21 deletions test/jasmine/tests/annotations_test.js
Original file line number Diff line number Diff line change
@@ -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],
Copy link
Contributor

@etpinard etpinard Apr 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so these values changed because the annotations-autorange mock changed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so these values changed because the annotations-autorange mock changed?

correct. I guess that's a hazard of reusing mocks for both images and jasmine tests...

['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<br>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<br>blah blah'
text: 'blah<br>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<br>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},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is editable: true tested with xshift / yshift? It's a little hard to tell from the diff.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, all the other tests above this in describe('annotation effects', to which I added xshift: 5, yshift: 5, are editable and failed before I fixed dragging with shifts.

{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];