Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions lib/index-strict.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Plotly.register([
require('../src/traces/scatterpolargl/strict'),
require('./barpolar'),
require('./scattersmith'),
require('./scatterquiver'),

// components
require('./calendars'),
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Plotly.register([
require('./scatterpolargl'),
require('./barpolar'),
require('./scattersmith'),
require('./scatterquiver'),

// components
require('./calendars'),
Expand Down
3 changes: 3 additions & 0 deletions lib/scatterquiver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('../src/traces/scatterquiver');
207 changes: 207 additions & 0 deletions src/traces/scatterquiver/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
'use strict';

var baseAttrs = require('../../plots/attributes');
var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var fontAttrs = require('../../plots/font_attributes');
var dash = require('../../components/drawing/attributes').dash;

var extendFlat = require('../../lib/extend').extendFlat;

var attrs = {
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
anim: true,
description: 'Sets the x coordinates of the arrow locations.'
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
anim: true,
description: 'Sets the y coordinates of the arrow locations.'
},
u: {
valType: 'data_array',
editType: 'calc',
anim: true,
description: 'Sets the x components of the arrow vectors.'
},
v: {
valType: 'data_array',
editType: 'calc',
anim: true,
description: 'Sets the y components of the arrow vectors.'
},
scale: {
valType: 'number',
dflt: 0.1,
Copy link

Choose a reason for hiding this comment

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

This is my only concern not already mentioned in others' comments; it's not clear that a default of 0.1 doesn't violate the "principle of least surprise".

Again I think this was copied verbatim from Plotly.py's figure_factory.create_quiver but it may be worth reviewing whether it's really the right choice.

min: 0,
max: 1,
editType: 'calc',
description: 'Scales size of the arrows (ideally to avoid overlap). Default = 0.1'
},
arrow_scale: {
valType: 'number',
dflt: 0.3,
min: 0,
max: 1,
editType: 'calc',
description: 'Value multiplied to length of barb to get length of arrowhead. Default = 0.3'
},
angle: {
valType: 'number',
dflt: Math.PI / 9,
min: 0,
max: Math.PI / 2,
editType: 'calc',
description: 'Angle of arrowhead in radians. Default = π/9'
},
scaleratio: {
valType: 'number',
min: 0,
editType: 'calc',
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.'
},
hoverdistance: {
valType: 'number',
min: -1,
dflt: 20,
editType: 'calc',
description: 'Maximum distance (in pixels) to look for nearby arrows on hover.'
},

// Line styling for arrows
line: {
color: {
valType: 'color',
dflt: '#000',
editType: 'style',
description: 'Sets the color of the arrow lines.'
},
width: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'style',
description: 'Sets the width (in px) of the arrow lines.'
},
dash: dash,
shape: {
valType: 'enumerated',
values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
dflt: 'linear',
editType: 'plot',
description: 'Determines the line shape.'
},
smoothing: {
valType: 'number',
min: 0,
max: 1.3,
dflt: 1,
editType: 'plot',
description: 'Has an effect only if `shape` is set to *spline*. Sets the amount of smoothing.'
},
simplify: {
valType: 'boolean',
dflt: true,
editType: 'plot',
description: 'Simplifies lines by removing nearly-overlapping points.'
},
editType: 'style'
},

// Text and labels
text: {
valType: 'data_array',
editType: 'calc',
anim: true,
description: 'Sets text elements associated with each (x,y) pair.'
},
textposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right'
],
dflt: 'middle center',
editType: 'calc',
description: 'Sets the positions of the `text` elements with respects to the (x,y) coordinates.'
},
// Text font
textfont: fontAttrs({
editType: 'calc',
colorEditType: 'style',
arrayOk: true,
description: 'Sets the text font.'
}),

// Selection and styling
selected: {
line: {
color: {
valType: 'color',
editType: 'style',
description: 'Sets the line color of selected points.'
},
width: {
valType: 'number',
min: 0,
editType: 'style',
description: 'Sets the line width of selected points.'
},
editType: 'style'
},
textfont: {
color: {
valType: 'color',
editType: 'style',
description: 'Sets the text font color of selected points, applied only when a selection exists.'
},
editType: 'style'
},
editType: 'style'
},
unselected: {
line: {
color: {
valType: 'color',
editType: 'style',
description: 'Sets the line color of unselected points.'
},
width: {
valType: 'number',
min: 0,
editType: 'style',
description: 'Sets the line width of unselected points.'
},
editType: 'style'
},
textfont: {
color: {
valType: 'color',
editType: 'style',
description: 'Sets the text font color of unselected points, applied only when a selection exists.'
},
editType: 'style'
},
editType: 'style'
}
};

// Extend with base attributes (includes hoverinfo, etc.)
extendFlat(attrs, baseAttrs);

// Add hoverinfo with proper flags for quiver
// We need to create a new object to avoid mutating the shared base attributes
attrs.hoverinfo = extendFlat({}, baseAttrs.hoverinfo, {
flags: ['x', 'y', 'u', 'v', 'text', 'name'],
dflt: 'all'
});

// Add hovertemplate
attrs.hovertemplate = extendFlat({}, hovertemplateAttrs({}, {
keys: ['x', 'y', 'u', 'v', 'text', 'name']
}));

module.exports = attrs;
47 changes: 47 additions & 0 deletions src/traces/scatterquiver/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

var Lib = require('../../lib');
var Axes = require('../../plots/cartesian/axes');
var isNumeric = require('fast-isnumeric');
var BADNUM = require('../../constants/numerical').BADNUM;
var scatterCalc = require('../scatter/calc');

/**
* Main calculation function for scatterquiver trace
* Creates calcdata with arrow path data for each vector
*/
module.exports = function calc(gd, trace) {
// Map x/y through axes so category/date values become numeric calcdata
var xa = trace._xA = Axes.getFromId(gd, trace.xaxis || 'x', 'x');
var ya = trace._yA = Axes.getFromId(gd, trace.yaxis || 'y', 'y');

var xVals = xa.makeCalcdata(trace, 'x');
var yVals = ya.makeCalcdata(trace, 'y');

// u/v are read in plot using the original trace arrays via cdi.i

var len = Math.min(xVals.length, yVals.length);
trace._length = len;
var cd = new Array(len);

for(var i = 0; i < len; i++) {
var cdi = cd[i] = { i: i };
var xValid = isNumeric(xVals[i]);
var yValid = isNumeric(yVals[i]);

if(xValid && yValid) {
cdi.x = xVals[i];
cdi.y = yVals[i];
} else {
cdi.x = BADNUM;
cdi.y = BADNUM;
}

// No additional props; keep minimal to avoid collisions with generic fields (e.g. `v`)
}

// Ensure axes are expanded and categories registered like scatter traces do
scatterCalc.calcAxisExpansion(gd, trace, xa, ya, xVals, yVals);

return cd;
};
76 changes: 76 additions & 0 deletions src/traces/scatterquiver/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

var Lib = require('../../lib');
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
// Selection styling - use coerce to set proper defaults
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}

// Coerce x and y data arrays (this ensures proper data structure for category ordering)
var x = coerce('x');
var y = coerce('y');
var u = coerce('u');
var v = coerce('v');

// Simple validation - check if we have the required arrays
if(!x || !Array.isArray(x) || x.length === 0 ||
!y || !Array.isArray(y) || y.length === 0) {
traceOut.visible = false;
return;
}

// If u/v are missing, default to zeros so the trace participates in calc/category logic
var len = Math.min(x.length, y.length);
if(!Array.isArray(u) || u.length === 0) {
traceOut.u = new Array(len);
for(var i = 0; i < len; i++) traceOut.u[i] = 0;
}
if(!Array.isArray(v) || v.length === 0) {
traceOut.v = new Array(len);
for(var j = 0; j < len; j++) traceOut.v[j] = 0;
}

// Set basic properties
traceOut.type = 'scatterquiver';

// Set default values using coerce
coerce('scale', 0.1);
coerce('arrow_scale', 0.3);
coerce('angle', Math.PI / 9);
coerce('scaleratio');
coerce('hoverdistance', 20);

// Line styling
traceOut.line = {
color: traceIn.line && traceIn.line.color ? traceIn.line.color : defaultColor,
width: traceIn.line && traceIn.line.width ? traceIn.line.width : 1,
dash: traceIn.line && traceIn.line.dash ? traceIn.line.dash : 'solid',
shape: traceIn.line && traceIn.line.shape ? traceIn.line.shape : 'linear',
smoothing: traceIn.line && traceIn.line.smoothing ? traceIn.line.smoothing : 1,
simplify: traceIn.line && traceIn.line.simplify !== undefined ? traceIn.line.simplify : true
};

// Hover and interaction - let the plots module handle hoverinfo defaults
// traceOut.hoverinfo will be set by Lib.coerceHoverinfo in plots.js
traceOut.hovertemplate = traceIn.hovertemplate;

// Text
traceOut.text = traceIn.text;
traceOut.textposition = traceIn.textposition || 'middle center';

// Use Lib.coerceFont to set textfont properly
Lib.coerceFont(coerce, 'textfont', layout.font);

coerce('selected.line.color');
coerce('selected.line.width');
coerce('selected.textfont.color');
coerce('unselected.line.color');
coerce('unselected.line.width');
coerce('unselected.textfont.color');

// Set the data length
traceOut._length = len;
};
10 changes: 10 additions & 0 deletions src/traces/scatterquiver/event_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

module.exports = function eventData(out, pt, trace, cd, pointNumber) {
out.x = pt.x;
out.y = pt.y;
out.u = trace.u[pointNumber];
out.v = trace.v[pointNumber];
out.pointNumber = pointNumber;
out.trace = trace;
};
Loading