Skip to content

Commit c4a0f1f

Browse files
committed
add 'startarrowhead', 'startarrowsize' and 'startstandoff' and rename 'arrowanchor' to 'arrowside'
1 parent f598c92 commit c4a0f1f

File tree

8 files changed

+111
-48
lines changed

8 files changed

+111
-48
lines changed

src/components/annotations/attributes.js

+41-6
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,21 @@ module.exports = {
173173
dflt: 1,
174174
role: 'style',
175175
editType: 'arraydraw',
176-
description: 'Sets the annotation arrow head style.'
176+
description: 'Sets the end annotation arrow head style.'
177177
},
178-
arrowanchor: {
179-
valType: 'enumerated',
180-
values: ['end', 'start', 'start+end'],
178+
startarrowhead: {
179+
valType: 'integer',
180+
min: 0,
181+
max: ARROWPATHS.length,
182+
dflt: 1,
183+
role: 'style',
184+
editType: 'arraydraw',
185+
description: 'Sets the start annotation arrow head style.'
186+
},
187+
arrowside: {
188+
valType: 'flaglist',
189+
flags: ['end', 'start'],
190+
extras: ['none'],
181191
dflt: 'end',
182192
role: 'style',
183193
editType: 'arraydraw',
@@ -190,7 +200,18 @@ module.exports = {
190200
role: 'style',
191201
editType: 'calcIfAutorange',
192202
description: [
193-
'Sets the size of the annotation arrow head, relative to `arrowwidth`.',
203+
'Sets the size of the end annotation arrow head, relative to `arrowwidth`.',
204+
'A value of 1 (default) gives a head about 3x as wide as the line.'
205+
].join(' ')
206+
},
207+
startarrowsize: {
208+
valType: 'number',
209+
min: 0.3,
210+
dflt: 1,
211+
role: 'style',
212+
editType: 'calcIfAutorange',
213+
description: [
214+
'Sets the size of the start annotation arrow head, relative to `arrowwidth`.',
194215
'A value of 1 (default) gives a head about 3x as wide as the line.'
195216
].join(' ')
196217
},
@@ -208,7 +229,21 @@ module.exports = {
208229
role: 'style',
209230
editType: 'calcIfAutorange',
210231
description: [
211-
'Sets a distance, in pixels, to move the arrowhead away from the',
232+
'Sets a distance, in pixels, to move the end arrowhead away from the',
233+
'position it is pointing at, for example to point at the edge of',
234+
'a marker independent of zoom. Note that this shortens the arrow',
235+
'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`',
236+
'which moves everything by this amount.'
237+
].join(' ')
238+
},
239+
startstandoff: {
240+
valType: 'number',
241+
min: 0,
242+
dflt: 0,
243+
role: 'style',
244+
editType: 'calcIfAutorange',
245+
description: [
246+
'Sets a distance, in pixels, to move the start arrowhead away from the',
212247
'position it is pointing at, for example to point at the edge of',
213248
'a marker independent of zoom. Note that this shortens the arrow',
214249
'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`',

src/components/annotations/common_defaults.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ module.exports = function handleAnnotationCommonDefaults(annIn, annOut, fullLayo
3535
if(h) coerce('valign');
3636

3737
if(showArrow) {
38+
var arrowside = coerce('arrowside');
39+
var arrowhead = coerce('arrowhead');
40+
var arrowsize = coerce('arrowsize');
41+
42+
if(arrowside.indexOf('start') !== -1) {
43+
coerce('startarrowhead', arrowhead);
44+
}
45+
coerce('startarrowsize', arrowsize);
3846
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
39-
coerce('arrowhead');
40-
coerce('arrowanchor');
41-
coerce('arrowsize');
4247
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
4348
coerce('standoff');
49+
coerce('startstandoff');
4450

4551
}
4652

src/components/annotations/draw.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
510510

511511
var strokewidth = options.arrowwidth,
512512
arrowColor = options.arrowcolor,
513-
arrowAnchor = options.arrowanchor;
513+
arrowSide = options.arrowside;
514514

515515
var arrowGroup = annGroup.append('g')
516516
.style({opacity: Color.opacity(arrowColor)})
@@ -521,7 +521,7 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
521521
.style('stroke-width', strokewidth + 'px')
522522
.call(Color.stroke, Color.rgb(arrowColor));
523523

524-
drawArrowHead(arrow, arrowAnchor, options);
524+
drawArrowHead(arrow, arrowSide, options);
525525

526526
// the arrow dragger is a small square right at the head, then a line to the tail,
527527
// all expanded by a stroke width of 6px plus the arrow line width

src/components/annotations/draw_arrow_head.js

+51-33
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ var ARROWPATHS = require('./arrow_paths');
2020
*
2121
* @param {d3.selection} el3: a d3-selected line or path element
2222
*
23-
* @param {string} ends: 'start', 'end', or 'start+end' for which ends get arrowheads
23+
* @param {string} ends: 'none', 'start', 'end', or 'start+end' for which ends get arrowheads
2424
*
2525
* @param {object} options: style information. Must have all the following:
26-
* @param {number} options.arrowhead: head style - see ./arrow_paths
27-
* @param {number} options.arrowsize: relative size of the head vs line width
28-
* @param {number} options.standoff: distance in px to move the arrow point from its target
26+
* @param {number} options.arrowhead: end head style - see ./arrow_paths
27+
* @param {number} options.startarrowhead: start head style - see ./arrow_paths
28+
* @param {number} options.arrowsize: relative size of the end head vs line width
29+
* @param {number} options.startarrowsize: relative size of the start head vs line width
30+
* @param {number} options.standoff: distance in px to move the end arrow point from its target
31+
* @param {number} options.startstandoff: distance in px to move the start arrow point from its target
2932
* @param {number} options.arrowwidth: width of the arrow line
3033
* @param {string} options.arrowcolor: color of the arrow line, for the head to match
3134
* Note that the opacity of this color is ignored, as it's assumed the container
@@ -35,10 +38,14 @@ var ARROWPATHS = require('./arrow_paths');
3538
module.exports = function drawArrowHead(el3, ends, options) {
3639
var el = el3.node();
3740
var headStyle = ARROWPATHS[options.arrowhead || 0];
41+
var startHeadStyle = ARROWPATHS[options.startarrowhead || 0];
3842
var scale = (options.arrowwidth || 1) * options.arrowsize;
43+
var startScale = (options.arrowwidth || 1) * options.startarrowsize;
3944
var doStart = ends.indexOf('start') >= 0;
4045
var doEnd = ends.indexOf('end') >= 0;
46+
var doNone = ends.indexOf('none') >= 0;
4147
var backOff = headStyle.backoff * scale + options.standoff;
48+
var startBackOff = startHeadStyle.backoff * startScale + options.startstandoff;
4249

4350
var start, end, startRot, endRot;
4451

@@ -51,6 +58,13 @@ module.exports = function drawArrowHead(el3, ends, options) {
5158

5259
startRot = Math.atan2(dy, dx);
5360
endRot = startRot + Math.PI;
61+
if(backOff && startBackOff) {
62+
if(backOff * backOff + startBackOff * startBackOff > dx * dx + dy * dy) {
63+
hideLine();
64+
return;
65+
}
66+
}
67+
5468
if(backOff) {
5569
if(backOff * backOff > dx * dx + dy * dy) {
5670
hideLine();
@@ -59,17 +73,27 @@ module.exports = function drawArrowHead(el3, ends, options) {
5973
var backOffX = backOff * Math.cos(startRot),
6074
backOffY = backOff * Math.sin(startRot);
6175

62-
if(doStart) {
63-
start.x -= backOffX;
64-
start.y -= backOffY;
65-
el3.attr({x1: start.x, y1: start.y});
66-
}
67-
if(doEnd) {
76+
if(doEnd || doNone) {
6877
end.x += backOffX;
6978
end.y += backOffY;
7079
el3.attr({x2: end.x, y2: end.y});
7180
}
7281
}
82+
83+
if(startBackOff) {
84+
if(startBackOff * startBackOff > dx * dx + dy * dy) {
85+
hideLine();
86+
return;
87+
}
88+
var startBackOffX = startBackOff * Math.cos(startRot),
89+
startbackOffY = startBackOff * Math.sin(startRot);
90+
91+
if(doStart || doNone) {
92+
start.x -= startBackOffX;
93+
start.y -= startbackOffY;
94+
el3.attr({x1: start.x, y1: start.y});
95+
}
96+
}
7397
}
7498
else if(el.nodeName === 'path') {
7599
var pathlen = el.getTotalLength(),
@@ -79,41 +103,35 @@ module.exports = function drawArrowHead(el3, ends, options) {
79103
// combine the two
80104
dashArray = '';
81105

82-
if(pathlen < backOff) {
106+
if(pathlen < backOff || pathlen < startBackOff || pathlen < backOff + startBackOff) {
83107
hideLine();
84108
return;
85109
}
86110

87-
if(doStart) {
88-
var start0 = el.getPointAtLength(0);
89-
var dstart = el.getPointAtLength(0.1);
90111

91-
startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x);
92-
start = el.getPointAtLength(Math.min(backOff, pathlen));
112+
var start0 = el.getPointAtLength(0);
113+
var dstart = el.getPointAtLength(0.1);
93114

94-
if(backOff) dashArray = '0px,' + backOff + 'px,';
95-
}
115+
startRot = Math.atan2(start0.y - dstart.y, start0.x - dstart.x);
116+
start = el.getPointAtLength(Math.min(startBackOff, pathlen));
96117

97-
if(doEnd) {
98-
var end0 = el.getPointAtLength(pathlen);
99-
var dend = el.getPointAtLength(pathlen - 0.1);
118+
dashArray = '0px,' + startBackOff + 'px,';
100119

101-
endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x);
102-
end = el.getPointAtLength(Math.max(0, pathlen - backOff));
120+
var end0 = el.getPointAtLength(pathlen);
121+
var dend = el.getPointAtLength(pathlen - 0.1);
103122

104-
if(backOff) {
105-
var shortening = dashArray ? 2 * backOff : backOff;
106-
dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px';
107-
}
108-
}
109-
else if(dashArray) dashArray += pathlen + 'px';
123+
endRot = Math.atan2(end0.y - dend.y, end0.x - dend.x);
124+
end = el.getPointAtLength(Math.max(0, pathlen - backOff));
125+
126+
var shortening = dashArray ? startBackOff + backOff : backOff;
127+
dashArray += (pathlen - shortening) + 'px,' + pathlen + 'px';
110128

111-
if(dashArray) el3.style('stroke-dasharray', dashArray);
129+
el3.style('stroke-dasharray', dashArray);
112130
}
113131

114132
function hideLine() { el3.style('stroke-dasharray', '0px,100px'); }
115133

116-
function drawhead(p, rot) {
134+
function drawhead(headStyle, p, rot, scale) {
117135
if(!headStyle.path) return;
118136
if(headStyle.noRotate) rot = 0;
119137

@@ -132,6 +150,6 @@ module.exports = function drawArrowHead(el3, ends, options) {
132150
});
133151
}
134152

135-
if(doStart) drawhead(start, startRot);
136-
if(doEnd) drawhead(end, endRot);
153+
if(doStart) drawhead(startHeadStyle, start, startRot, startScale);
154+
if(doEnd) drawhead(headStyle, end, endRot, scale);
137155
};

src/components/annotations3d/attributes.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ module.exports = overrideAll({
7272
showarrow: annAtts.showarrow,
7373
arrowcolor: annAtts.arrowcolor,
7474
arrowhead: annAtts.arrowhead,
75-
arrowanchor: annAtts.arrowanchor,
75+
startarrowhead: annAtts.startarrowhead,
76+
arrowside: annAtts.arrowside,
7677
arrowsize: annAtts.arrowsize,
78+
startarrowsize: annAtts.startarrowsize,
7779
arrowwidth: annAtts.arrowwidth,
7880
standoff: annAtts.standoff,
81+
startstandoff: annAtts.startstandoff,
7982
hovertext: annAtts.hovertext,
8083
hoverlabel: annAtts.hoverlabel,
8184
captureevents: annAtts.captureevents,

test/image/baselines/annotations.png

1.04 KB
Loading

test/image/mocks/annotations.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"arrowcolor":"rgb(166, 28, 0)","borderpad":3,"textangle":50,"x":5,"y":1
4646
},
4747
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":5,"y":3,"ax":4,"ay":5},
48-
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"arrowanchor":"start+end","axref":"x","ayref":"y","x":2,"y":4,"ax":3,"ay":4},
48+
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":3,"startarrowhead":6,"arrowsize":1.5,"startarrowsize":3,"standoff": 7,"startstandoff": 15,"arrowside":"start+end","axref":"x","ayref":"y","x":2,"y":4,"ax":3,"ay":4},
49+
{"text":"","showarrow":true,"borderwidth":1.2,"startarrowhead":2,"startstandoff": 5,"arrowside":"start","axref":"x","ayref":"y","x":1,"y":5,"ax":2,"ay":4},
4950
{"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3},
5051
{
5152
"text": "arrow ML<br>+standoff", "x": 2, "y": 2, "ax": 20, "ay": 20,

test/jasmine/tests/annotations_test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ describe('Test annotations', function() {
141141
expect(layoutOut.annotations[2]._yclick).toBe('A', 'category');
142142
});
143143

144-
it('should default to end for arrowanchor', function() {
144+
it('should default to end for arrowside', function() {
145145
var layoutIn = {
146146
annotations: [{ showarrow: true, arrowhead: 2 }]
147147
};
148148

149149
var out = _supply(layoutIn);
150150

151-
expect(out[0].arrowanchor).toEqual('end');
151+
expect(out[0].arrowside).toEqual('end');
152152
});
153153
});
154154
});

0 commit comments

Comments
 (0)