Skip to content

Commit f021912

Browse files
committed
Add scatterquiver trace type
1 parent 322c251 commit f021912

File tree

14 files changed

+1636
-0
lines changed

14 files changed

+1636
-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: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
66+
// Line styling for arrows
67+
line: {
68+
color: {
69+
valType: 'color',
70+
dflt: '#000',
71+
editType: 'style',
72+
description: 'Sets the color of the arrow lines.'
73+
},
74+
width: {
75+
valType: 'number',
76+
min: 0,
77+
dflt: 1,
78+
editType: 'style',
79+
description: 'Sets the width (in px) of the arrow lines.'
80+
},
81+
dash: dash,
82+
shape: {
83+
valType: 'enumerated',
84+
values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
85+
dflt: 'linear',
86+
editType: 'plot',
87+
description: 'Determines the line shape.'
88+
},
89+
smoothing: {
90+
valType: 'number',
91+
min: 0,
92+
max: 1.3,
93+
dflt: 1,
94+
editType: 'plot',
95+
description: 'Has an effect only if `shape` is set to *spline*. Sets the amount of smoothing.'
96+
},
97+
simplify: {
98+
valType: 'boolean',
99+
dflt: true,
100+
editType: 'plot',
101+
description: 'Simplifies lines by removing nearly-overlapping points.'
102+
}
103+
},
104+
105+
// Text and labels
106+
text: {
107+
valType: 'data_array',
108+
editType: 'calc',
109+
anim: true,
110+
description: 'Sets text elements associated with each (x,y) pair.'
111+
},
112+
textposition: {
113+
valType: 'enumerated',
114+
values: [
115+
'top left', 'top center', 'top right',
116+
'middle left', 'middle center', 'middle right',
117+
'bottom left', 'bottom center', 'bottom right'
118+
],
119+
dflt: 'middle center',
120+
editType: 'calc',
121+
description: 'Sets the positions of the `text` elements with respects to the (x,y) coordinates.'
122+
},
123+
textfont: fontAttrs({
124+
editType: 'calc',
125+
colorEditType: 'style'
126+
}),
127+
128+
// Selection and styling
129+
selected: {
130+
line: {
131+
color: {
132+
valType: 'color',
133+
editType: 'style',
134+
description: 'Sets the line color of selected points.'
135+
},
136+
width: {
137+
valType: 'number',
138+
min: 0,
139+
editType: 'style',
140+
description: 'Sets the line width of selected points.'
141+
}
142+
}
143+
},
144+
unselected: {
145+
line: {
146+
color: {
147+
valType: 'color',
148+
editType: 'style',
149+
description: 'Sets the line color of unselected points.'
150+
},
151+
width: {
152+
valType: 'number',
153+
min: 0,
154+
editType: 'style',
155+
description: 'Sets the line width of unselected points.'
156+
}
157+
}
158+
}
159+
};
160+
161+
// Extend with base attributes (includes hoverinfo, etc.)
162+
extendFlat(attrs, baseAttrs);
163+
164+
// Add hoverinfo with proper flags for quiver
165+
attrs.hoverinfo.flags = ['x', 'y', 'u', 'v', 'text', 'name'];
166+
attrs.hoverinfo.dflt = 'x+y+u+v+name';
167+
168+
// Add hovertemplate
169+
attrs.hovertemplate = hovertemplateAttrs({}, {
170+
keys: ['x', 'y', 'u', 'v', 'text', 'name']
171+
});
172+
173+
module.exports = attrs;

src/traces/scatterquiver/calc.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
5+
/**
6+
* Calculate arrow positions and orientations for quiver plot
7+
* Ported from plotly.py _quiver.py
8+
*/
9+
function calculateArrows(x, y, u, v, scale, arrowScale, angle, scaleRatio) {
10+
var len = x.length;
11+
var barbX = [];
12+
var barbY = [];
13+
var arrowX = [];
14+
var arrowY = [];
15+
16+
// Scale u and v to avoid overlap
17+
var scaledU = u.map(function(val) { return val * scale * (scaleRatio || 1); });
18+
var scaledV = v.map(function(val) { return val * scale; });
19+
20+
// Calculate arrow endpoints
21+
var endX = x.map(function(val, i) { return val + scaledU[i]; });
22+
var endY = y.map(function(val, i) { return val + scaledV[i]; });
23+
24+
// Create barb lines (main arrow shafts)
25+
for(var i = 0; i < len; i++) {
26+
barbX.push(x[i], endX[i], null);
27+
barbY.push(y[i], endY[i], null);
28+
}
29+
30+
// Calculate arrow heads
31+
for(var i = 0; i < len; i++) {
32+
var dx = endX[i] - x[i];
33+
var dy = endY[i] - y[i];
34+
35+
// Calculate barb length
36+
var barbLen = Math.sqrt(dx * dx / (scaleRatio || 1) + dy * dy);
37+
38+
// Calculate arrow head length
39+
var arrowLen = barbLen * arrowScale;
40+
41+
// Calculate barb angle
42+
var barbAng = Math.atan2(dy, dx / (scaleRatio || 1));
43+
44+
// Calculate arrow head angles
45+
var ang1 = barbAng + angle;
46+
var ang2 = barbAng - angle;
47+
48+
// Calculate arrow head points
49+
var point1X = endX[i] - arrowLen * Math.cos(ang1) * (scaleRatio || 1);
50+
var point1Y = endY[i] - arrowLen * Math.sin(ang1);
51+
var point2X = endX[i] - arrowLen * Math.cos(ang2) * (scaleRatio || 1);
52+
var point2Y = endY[i] - arrowLen * Math.sin(ang2);
53+
54+
// Add arrow head lines
55+
arrowX.push(point1X, endX[i], point2X, null);
56+
arrowY.push(point1Y, endY[i], point2Y, null);
57+
}
58+
59+
return {
60+
barbX: barbX,
61+
barbY: barbY,
62+
arrowX: arrowX,
63+
arrowY: arrowY
64+
};
65+
}
66+
67+
/**
68+
* Main calculation function for scatterquiver trace
69+
*/
70+
module.exports = function calc(gd, trace) {
71+
var x = trace.x;
72+
var y = trace.y;
73+
var u = trace.u;
74+
var v = trace.v;
75+
var scale = trace.scale;
76+
var arrowScale = trace.arrow_scale;
77+
var angle = trace.angle;
78+
var scaleRatio = trace.scaleratio;
79+
80+
// Calculate arrow positions
81+
var arrowData = calculateArrows(x, y, u, v, scale, arrowScale, angle, scaleRatio);
82+
83+
// Create calcdata - one complete arrow per entry
84+
var calcdata = [];
85+
var len = x.length;
86+
87+
for(var i = 0; i < len; i++) {
88+
// Calculate arrow components
89+
var dx = u[i] * scale * (scaleRatio || 1);
90+
var dy = v[i] * scale;
91+
var barbLen = Math.sqrt(dx * dx / (scaleRatio || 1) + dy * dy);
92+
var arrowLen = barbLen * arrowScale;
93+
var barbAng = Math.atan2(dy, dx / (scaleRatio || 1));
94+
95+
var ang1 = barbAng + angle;
96+
var ang2 = barbAng - angle;
97+
98+
var endX = x[i] + dx;
99+
var endY = y[i] + dy;
100+
101+
var point1X = endX - arrowLen * Math.cos(ang1) * (scaleRatio || 1);
102+
var point1Y = endY - arrowLen * Math.sin(ang1);
103+
var point2X = endX - arrowLen * Math.cos(ang2) * (scaleRatio || 1);
104+
var point2Y = endY - arrowLen * Math.sin(ang2);
105+
106+
// Create complete arrow as one path: shaft + arrow head
107+
var arrowPath = [
108+
{ x: x[i], y: y[i], i: i }, // Start point
109+
{ x: endX, y: endY, i: i }, // End of shaft
110+
{ x: point1X, y: point1Y, i: i }, // Arrow head point 1
111+
{ x: endX, y: endY, i: i }, // Back to end
112+
{ x: point2X, y: point2Y, i: i } // Arrow head point 2
113+
];
114+
115+
calcdata.push(arrowPath);
116+
}
117+
118+
return calcdata;
119+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
4+
// Simple validation - just check if we have the required arrays
5+
if(!traceIn.x || !Array.isArray(traceIn.x) || traceIn.x.length === 0 ||
6+
!traceIn.y || !Array.isArray(traceIn.y) || traceIn.y.length === 0 ||
7+
!traceIn.u || !Array.isArray(traceIn.u) || traceIn.u.length === 0 ||
8+
!traceIn.v || !Array.isArray(traceIn.v) || traceIn.v.length === 0) {
9+
traceOut.visible = false;
10+
return;
11+
}
12+
13+
// Set basic properties
14+
traceOut.type = 'scatterquiver';
15+
traceOut.visible = true;
16+
17+
// Copy the data arrays
18+
traceOut.x = traceIn.x;
19+
traceOut.y = traceIn.y;
20+
traceOut.u = traceIn.u;
21+
traceOut.v = traceIn.v;
22+
23+
// Set default values
24+
traceOut.scale = traceIn.scale !== undefined ? traceIn.scale : 0.1;
25+
traceOut.arrow_scale = traceIn.arrow_scale !== undefined ? traceIn.arrow_scale : 0.3;
26+
traceOut.angle = traceIn.angle !== undefined ? traceIn.angle : Math.PI / 9;
27+
traceOut.scaleratio = traceIn.scaleratio;
28+
29+
// Line styling
30+
traceOut.line = {
31+
color: traceIn.line && traceIn.line.color ? traceIn.line.color : defaultColor,
32+
width: traceIn.line && traceIn.line.width ? traceIn.line.width : 1,
33+
dash: traceIn.line && traceIn.line.dash ? traceIn.line.dash : 'solid',
34+
shape: traceIn.line && traceIn.line.shape ? traceIn.line.shape : 'linear',
35+
smoothing: traceIn.line && traceIn.line.smoothing ? traceIn.line.smoothing : 1,
36+
simplify: traceIn.line && traceIn.line.simplify !== undefined ? traceIn.line.simplify : true
37+
};
38+
39+
// Hover and interaction - fix the hoverinfo issue
40+
traceOut.hoverinfo = traceIn.hoverinfo || 'x+y+u+v+name';
41+
traceOut.hovertemplate = traceIn.hovertemplate;
42+
43+
// Text
44+
traceOut.text = traceIn.text;
45+
traceOut.textposition = traceIn.textposition || 'middle center';
46+
traceOut.textfont = traceIn.textfont || {};
47+
48+
// Selection styling
49+
traceOut.selected = traceIn.selected || {};
50+
traceOut.unselected = traceIn.unselected || {};
51+
52+
// Set the data length
53+
traceOut._length = traceIn.x.length;
54+
};

0 commit comments

Comments
 (0)