Skip to content

Commit 19e65b6

Browse files
committed
Add scatterquiver trace type
1 parent fa48511 commit 19e65b6

File tree

13 files changed

+1484
-0
lines changed

13 files changed

+1484
-0
lines changed

lib/index-strict.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Plotly.register([
5252
require('../src/traces/scatterpolargl/strict'),
5353
require('./barpolar'),
5454
require('./scattersmith'),
55+
require('./scatterquiver'),
5556

5657
// components
5758
require('./calendars'),

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Plotly.register([
5252
require('./scatterpolargl'),
5353
require('./barpolar'),
5454
require('./scattersmith'),
55+
require('./scatterquiver'),
5556

5657
// components
5758
require('./calendars'),

lib/scatterquiver.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('../src/traces/scatterquiver');
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
'use strict';
2+
3+
var baseAttrs = require('../../plots/attributes');
4+
var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
5+
var fontAttrs = require('../../plots/font_attributes');
6+
var dash = require('../../components/drawing/attributes').dash;
7+
8+
var extendFlat = require('../../lib/extend').extendFlat;
9+
10+
var attrs = {
11+
x: {
12+
valType: 'data_array',
13+
editType: 'calc+clearAxisTypes',
14+
anim: true,
15+
description: 'Sets the x coordinates of the arrow locations.'
16+
},
17+
y: {
18+
valType: 'data_array',
19+
editType: 'calc+clearAxisTypes',
20+
anim: true,
21+
description: 'Sets the y coordinates of the arrow locations.'
22+
},
23+
u: {
24+
valType: 'data_array',
25+
editType: 'calc',
26+
anim: true,
27+
description: 'Sets the x components of the arrow vectors.'
28+
},
29+
v: {
30+
valType: 'data_array',
31+
editType: 'calc',
32+
anim: true,
33+
description: 'Sets the y components of the arrow vectors.'
34+
},
35+
scale: {
36+
valType: 'number',
37+
dflt: 0.1,
38+
min: 0,
39+
max: 1,
40+
editType: 'calc',
41+
description: 'Scales size of the arrows (ideally to avoid overlap). Default = 0.1'
42+
},
43+
arrow_scale: {
44+
valType: 'number',
45+
dflt: 0.3,
46+
min: 0,
47+
max: 1,
48+
editType: 'calc',
49+
description: 'Value multiplied to length of barb to get length of arrowhead. Default = 0.3'
50+
},
51+
angle: {
52+
valType: 'number',
53+
dflt: Math.PI / 9,
54+
min: 0,
55+
max: Math.PI / 2,
56+
editType: 'calc',
57+
description: 'Angle of arrowhead in radians. Default = π/9'
58+
},
59+
scaleratio: {
60+
valType: 'number',
61+
min: 0,
62+
editType: 'calc',
63+
description: 'The ratio between the scale of the y-axis and the scale of the x-axis (scale_y / scale_x). Default = null, the scale ratio is not fixed.'
64+
},
65+
hoverdistance: {
66+
valType: 'number',
67+
min: -1,
68+
dflt: 20,
69+
editType: 'calc',
70+
description: 'Maximum distance (in pixels) to look for nearby arrows on hover.'
71+
},
72+
73+
// Line styling for arrows
74+
line: {
75+
color: {
76+
valType: 'color',
77+
dflt: '#000',
78+
editType: 'style',
79+
description: 'Sets the color of the arrow lines.'
80+
},
81+
width: {
82+
valType: 'number',
83+
min: 0,
84+
dflt: 1,
85+
editType: 'style',
86+
description: 'Sets the width (in px) of the arrow lines.'
87+
},
88+
dash: dash,
89+
shape: {
90+
valType: 'enumerated',
91+
values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
92+
dflt: 'linear',
93+
editType: 'plot',
94+
description: 'Determines the line shape.'
95+
},
96+
smoothing: {
97+
valType: 'number',
98+
min: 0,
99+
max: 1.3,
100+
dflt: 1,
101+
editType: 'plot',
102+
description: 'Has an effect only if `shape` is set to *spline*. Sets the amount of smoothing.'
103+
},
104+
simplify: {
105+
valType: 'boolean',
106+
dflt: true,
107+
editType: 'plot',
108+
description: 'Simplifies lines by removing nearly-overlapping points.'
109+
},
110+
editType: 'style'
111+
},
112+
113+
// Text and labels
114+
text: {
115+
valType: 'data_array',
116+
editType: 'calc',
117+
anim: true,
118+
description: 'Sets text elements associated with each (x,y) pair.'
119+
},
120+
textposition: {
121+
valType: 'enumerated',
122+
values: [
123+
'top left', 'top center', 'top right',
124+
'middle left', 'middle center', 'middle right',
125+
'bottom left', 'bottom center', 'bottom right'
126+
],
127+
dflt: 'middle center',
128+
editType: 'calc',
129+
description: 'Sets the positions of the `text` elements with respects to the (x,y) coordinates.'
130+
},
131+
// Text font
132+
textfont: fontAttrs({
133+
editType: 'calc',
134+
colorEditType: 'style',
135+
arrayOk: true,
136+
description: 'Sets the text font.'
137+
}),
138+
139+
// Selection and styling
140+
selected: {
141+
line: {
142+
color: {
143+
valType: 'color',
144+
editType: 'style',
145+
description: 'Sets the line color of selected points.'
146+
},
147+
width: {
148+
valType: 'number',
149+
min: 0,
150+
editType: 'style',
151+
description: 'Sets the line width of selected points.'
152+
},
153+
editType: 'style'
154+
},
155+
textfont: {
156+
color: {
157+
valType: 'color',
158+
editType: 'style',
159+
description: 'Sets the text font color of selected points, applied only when a selection exists.'
160+
},
161+
editType: 'style'
162+
},
163+
editType: 'style'
164+
},
165+
unselected: {
166+
line: {
167+
color: {
168+
valType: 'color',
169+
editType: 'style',
170+
description: 'Sets the line color of unselected points.'
171+
},
172+
width: {
173+
valType: 'number',
174+
min: 0,
175+
editType: 'style',
176+
description: 'Sets the line width of unselected points.'
177+
},
178+
editType: 'style'
179+
},
180+
textfont: {
181+
color: {
182+
valType: 'color',
183+
editType: 'style',
184+
description: 'Sets the text font color of unselected points, applied only when a selection exists.'
185+
},
186+
editType: 'style'
187+
},
188+
editType: 'style'
189+
}
190+
};
191+
192+
// Extend with base attributes (includes hoverinfo, etc.)
193+
extendFlat(attrs, baseAttrs);
194+
195+
// Add hoverinfo with proper flags for quiver
196+
// We need to create a new object to avoid mutating the shared base attributes
197+
attrs.hoverinfo = extendFlat({}, baseAttrs.hoverinfo, {
198+
flags: ['x', 'y', 'u', 'v', 'text', 'name'],
199+
dflt: 'all'
200+
});
201+
202+
// Add hovertemplate
203+
attrs.hovertemplate = extendFlat({}, hovertemplateAttrs({}, {
204+
keys: ['x', 'y', 'u', 'v', 'text', 'name']
205+
}));
206+
207+
module.exports = attrs;

src/traces/scatterquiver/calc.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
5+
/**
6+
* Main calculation function for scatterquiver trace
7+
* Creates calcdata with arrow path data for each vector
8+
*/
9+
module.exports = function calc(gd, trace) {
10+
var x = trace.x;
11+
var y = trace.y;
12+
var u = trace.u;
13+
var v = trace.v;
14+
var scale = trace.scale;
15+
var arrowScale = trace.arrow_scale;
16+
var angle = trace.angle;
17+
var scaleRatio = trace.scaleratio;
18+
19+
// Create calcdata - one complete arrow per entry
20+
var calcdata = [];
21+
var len = x.length;
22+
23+
for(var i = 0; i < len; i++) {
24+
// Calculate arrow components
25+
var dx = u[i] * scale * (scaleRatio || 1);
26+
var dy = v[i] * scale;
27+
var barbLen = Math.sqrt(dx * dx / (scaleRatio || 1) + dy * dy);
28+
var arrowLen = barbLen * arrowScale;
29+
var barbAng = Math.atan2(dy, dx / (scaleRatio || 1));
30+
31+
var ang1 = barbAng + angle;
32+
var ang2 = barbAng - angle;
33+
34+
var endX = x[i] + dx;
35+
var endY = y[i] + dy;
36+
37+
var point1X = endX - arrowLen * Math.cos(ang1) * (scaleRatio || 1);
38+
var point1Y = endY - arrowLen * Math.sin(ang1);
39+
var point2X = endX - arrowLen * Math.cos(ang2) * (scaleRatio || 1);
40+
var point2Y = endY - arrowLen * Math.sin(ang2);
41+
42+
// Create complete arrow as one path: shaft + arrow head
43+
var arrowPath = [
44+
{ x: x[i], y: y[i], i: i }, // Start point
45+
{ x: endX, y: endY, i: i }, // End of shaft
46+
{ x: point1X, y: point1Y, i: i }, // Arrow head point 1
47+
{ x: endX, y: endY, i: i }, // Back to end
48+
{ x: point2X, y: point2Y, i: i } // Arrow head point 2
49+
];
50+
51+
calcdata.push(arrowPath);
52+
}
53+
54+
return calcdata;
55+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
var attributes = require('./attributes');
5+
6+
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
7+
// Selection styling - use coerce to set proper defaults
8+
function coerce(attr, dflt) {
9+
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
10+
}
11+
12+
// Coerce x and y data arrays (this ensures proper data structure for category ordering)
13+
var x = coerce('x');
14+
var y = coerce('y');
15+
var u = coerce('u');
16+
var v = coerce('v');
17+
18+
// Simple validation - check if we have the required arrays
19+
if(!x || !Array.isArray(x) || x.length === 0 ||
20+
!y || !Array.isArray(y) || y.length === 0 ||
21+
!u || !Array.isArray(u) || u.length === 0 ||
22+
!v || !Array.isArray(v) || v.length === 0) {
23+
traceOut.visible = false;
24+
return;
25+
}
26+
27+
// Set basic properties
28+
traceOut.type = 'scatterquiver';
29+
traceOut.visible = true;
30+
31+
// Set default values using coerce
32+
coerce('scale', 0.1);
33+
coerce('arrow_scale', 0.3);
34+
coerce('angle', Math.PI / 9);
35+
coerce('scaleratio');
36+
coerce('hoverdistance', 20);
37+
38+
// Line styling
39+
traceOut.line = {
40+
color: traceIn.line && traceIn.line.color ? traceIn.line.color : defaultColor,
41+
width: traceIn.line && traceIn.line.width ? traceIn.line.width : 1,
42+
dash: traceIn.line && traceIn.line.dash ? traceIn.line.dash : 'solid',
43+
shape: traceIn.line && traceIn.line.shape ? traceIn.line.shape : 'linear',
44+
smoothing: traceIn.line && traceIn.line.smoothing ? traceIn.line.smoothing : 1,
45+
simplify: traceIn.line && traceIn.line.simplify !== undefined ? traceIn.line.simplify : true
46+
};
47+
48+
// Hover and interaction - let the plots module handle hoverinfo defaults
49+
// traceOut.hoverinfo will be set by Lib.coerceHoverinfo in plots.js
50+
traceOut.hovertemplate = traceIn.hovertemplate;
51+
52+
// Text
53+
traceOut.text = traceIn.text;
54+
traceOut.textposition = traceIn.textposition || 'middle center';
55+
56+
// Use Lib.coerceFont to set textfont properly
57+
Lib.coerceFont(coerce, 'textfont', layout.font);
58+
59+
coerce('selected.line.color');
60+
coerce('selected.line.width');
61+
coerce('selected.textfont.color');
62+
coerce('unselected.line.color');
63+
coerce('unselected.line.width');
64+
coerce('unselected.textfont.color');
65+
66+
// Set the data length
67+
traceOut._length = x.length;
68+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
4+
out.x = pt.x;
5+
out.y = pt.y;
6+
out.u = trace.u[pointNumber];
7+
out.v = trace.v[pointNumber];
8+
out.pointNumber = pointNumber;
9+
out.trace = trace;
10+
};

0 commit comments

Comments
 (0)