-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add scatterquiver trace type for vector field visualization #7584
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
Open
degzhaus
wants to merge
2
commits into
plotly:master
Choose a base branch
from
degzhaus:degzhaus/add_scatterquiver
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,512
−0
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| 'use strict'; | ||
|
|
||
| module.exports = require('../src/traces/scatterquiver'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| 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; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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_quiverbut it may be worth reviewing whether it's really the right choice.