diff --git a/draftlogs/7710_add.md b/draftlogs/7710_add.md index 5a98635a261..d87ec33ad18 100644 --- a/draftlogs/7710_add.md +++ b/draftlogs/7710_add.md @@ -1 +1 @@ -- Add `quiver` trace type to visualize vector fields using arrows [[#7710](https://github.com/plotly/plotly.js/pull/7710)], with thanks to @degzhaus for the contribution! +- Add `quiver` trace type to visualize vector fields using arrows [[#7710](https://github.com/plotly/plotly.js/pull/7710), [#7945](https://github.com/plotly/plotly.js/issues/7945)], with thanks to @degzhaus for the contribution! diff --git a/src/traces/quiver/attributes.js b/src/traces/quiver/attributes.js index 27d9e269935..d6fcdda809d 100644 --- a/src/traces/quiver/attributes.js +++ b/src/traces/quiver/attributes.js @@ -14,7 +14,7 @@ var attrs = { valType: 'data_array', editType: 'calc+clearAxisTypes', anim: true, - description: 'Sets the x coordinates of the arrow locations.' + description: 'Sets the x coordinates of the vector arrow locations.' }, x0: scatterAttrs.x0, dx: scatterAttrs.dx, @@ -22,7 +22,7 @@ var attrs = { valType: 'data_array', editType: 'calc+clearAxisTypes', anim: true, - description: 'Sets the y coordinates of the arrow locations.' + description: 'Sets the y coordinates of the vector arrow locations.' }, y0: scatterAttrs.y0, dy: scatterAttrs.dy, @@ -30,46 +30,47 @@ var attrs = { valType: 'data_array', editType: 'calc', anim: true, - description: 'Sets the x components of the arrow vectors.' + description: 'Sets the x components of the vector arrows.' }, v: { valType: 'data_array', editType: 'calc', anim: true, - description: 'Sets the y components of the arrow vectors.' + description: 'Sets the y components of the vector arrows.' }, - anglemode: { + arrowref: { valType: 'enumerated', values: ['paper', 'data'], - dflt: 'axis', + dflt: 'data', editType: 'calc', description: [ - 'Sets the mode used to determine the angle of the arrow vectors.', + 'Determines how the u/v vector components are interpreted.', 'If *paper*, u/v are interpreted in pixel coordinates and the rendered vector angle', - 'does not change regardless of the axes scales.', + 'does not change regardless of the axis scales.', 'If *data*, u/v are interpreted in data coordinates and the rendered vector angle', 'may change, e.g. if zooming in along a single axis' ].join(' ') }, - sizemode: { + lengthmode: { valType: 'enumerated', values: ['scaled', 'raw'], editType: 'calc', dflt: 'scaled', description: [ - 'Determines whether arrows are drawn according to their raw lengths,', - 'or scaled based on the maximum vector length and point density. Note: When `anglemode` is *data*', - 'arrows are alwyas scaled and `sizemode` *raw* is ignored.', + 'Determines whether vector arrows are drawn according to their raw lengths,', + 'or scaled based on the maximum vector length and point density. Note: When `arrowref` is *paper*', + 'vectors are always scaled and `lengthmode` *raw* is ignored.' ].join(' ') }, - sizeref: { + lengthfactor: { valType: 'number', min: 0, editType: 'calc', dflt: 1, description: [ - 'Adjusts the arrow size scaling. The arrow length is determined by the vector norm multiplied by `sizeref`,', - 'optionally normalized when `sizemode` is *scaled* (`sizeref` is applied after scaling).' + 'Adjusts the drawn length of the vector arrows. The arrow length is determined by', + 'the values of u and v, then optionally rescaled when `lengthmode` is *scaled*,', + 'then multiplied by `lengthfactor`.', ].join(' ') }, anchor: { @@ -78,9 +79,9 @@ var attrs = { dflt: 'tail', editType: 'calc', description: [ - 'Sets the arrows\' anchor with respect to their (x,y) positions.', + 'Sets the vector arrows\' anchor with respect to their (x,y) positions.', 'Use *tail* to place (x,y) at the base, *tip* to place (x,y) at the head,', - 'or *center* to center the arrow on (x,y).' + 'or *center* to center the vector arrow on (x,y).' ].join(' ') }, xhoverformat: axisHoverFormat('x'), @@ -107,7 +108,7 @@ var attrs = { arrowsize: extendFlat({}, annotationAttrs.arrowsize, { editType: 'calc', description: [ - 'Sets the size of the arrow head relative to `marker.line.width`.', + 'Sets the size of the vector arrowhead relative to `marker.line.width`.', 'A value of 1 (default) gives a head about 3x as wide as the line.' ].join(' ') }), @@ -117,7 +118,7 @@ var attrs = { min: 0, dflt: 2, editType: 'style', - description: 'Sets the width (in px) of the arrow lines.' + description: 'Sets the width (in px) of the vector arrow lines.' }, dash: dash, editType: 'style' diff --git a/src/traces/quiver/calc.js b/src/traces/quiver/calc.js index 2b7e6b7253a..4a9019e050b 100644 --- a/src/traces/quiver/calc.js +++ b/src/traces/quiver/calc.js @@ -7,10 +7,12 @@ var BADNUM = require('../../constants/numerical').BADNUM; var colorscaleCalc = require('../../components/colorscale/calc'); var calcSelection = require('../scatter/calc_selection'); -/** - * Main calculation function for quiver trace - * Creates calcdata with arrow path data for each vector - */ +// For scaled lengthmode: Constant to multiply by the computed distance between +// neighboring points, such that the arrows are _just slightly shorter_ than +// that distance +const SHRINK_FACTOR = 0.97; +// const SHRINK_FACTOR = 1; + module.exports = function calc(gd, trace) { // Map x/y through axes so category/date values become numeric calcdata const xa = trace._xA = Axes.getFromId(gd, trace.xaxis || 'x', 'x'); @@ -33,9 +35,7 @@ module.exports = function calc(gd, trace) { const uArr = trace.u || []; const vArr = trace.v || []; - const anglemode = trace.anglemode; - const sizemode = trace.sizemode; - const anchor = trace.anchor; + const { anchor, lengthmode, arrowref } = trace; const isTip = anchor === 'tip'; const isCenter = anchor === 'center'; @@ -54,7 +54,7 @@ module.exports = function calc(gd, trace) { var nValid = 0; // First pass: build calcdata, and keep track of the maximum and minimum vector norm in the trace, - // to be used for sizemode 'scaled' (max norm only) and for magnitude-based colorscale range + // to be used for lengthmode 'scaled' (max norm only) and for magnitude-based colorscale range for(var i = 0; i < len; i++) { var cdi = cd[i] = { i: i }; var xValid = isNumeric(xVals[i]); @@ -109,37 +109,59 @@ module.exports = function calc(gd, trace) { // Store maxNorm for use by plot step trace._maxNorm = normMax; - if (sizemode === 'scaled' || anglemode === 'paper') { - // Ignore sizemode 'raw' if anglemode is set to 'paper': always scale - - // Compute point density of the entire trace: Area of bounding box - // divided by number of points. This is used to scale arrows in - // 'scaled' sizemode. - // TODO: How to handle the case where there is just one point in a trace, - // or all points have the same x or y value? This will give a boxArea of 0. - // For now I'm going to just normalize to a vector of unit length (1) in that case, - // but that's not a great solution - const boxArea = (xMax - xMin) * (yMax - yMin); - const pointDensity = boxArea / len; - // Now, compute the scale factor for scaled size mode - // The scale factor should be such that - // _maxNorm * _scaleFactor = Math.sqrt(_pointDensity) - // Therefore: _scaleFactor = Math.sqrt(_pointDensity) / _maxNorm - if (pointDensity === 0) { - trace._scaleFactor = 1 / trace._maxNorm + // Ignore lengthmode 'raw' if arrowref is set to 'paper': always scale + if (lengthmode === 'scaled' || arrowref === 'paper') { + /** + * Compute the maximum arrow length we should allow, using a heuristic + * to estimate the distance between neighboring points. + * + * Let: + * - D be the distance between neighboring points (the value we want to compute) + * - N be the number of points in the trace + * - dX be the x-width of the bounding box of all the points + * - dY be the y-width of the bounding box + * + * We want to satisfy this equation: D = sqrt((dX + D) * (dY + D) / N) + * + * This is basically the square root of the point density, with an additional + * adjustment to account for the points on the edges (we add D to each dimension + * of the bounding box). This equation gives us the _exact_ correct distance when + * the points are arranged in a perfect grid; otherwise, it's just an estimate. + * + * Solving for D gives us: + * D = (dX + dY + sqrt((dX - dY)^2 + 4N * dX * dY)) / (2 * (N - 1)) + * which is the forumla we'll use below. + * + * Note: this formula was derived and documented by a human ;) + */ + + const dX = xMax - xMin; + const dY = yMax - yMin; + var pointDist; + if (dX === 0 && dY === 0) { + // If all points share the same x and y value, we can't estimate pointDist. + // Default to an arbitrary value of 1. + pointDist = 1; } else { - trace._scaleFactor = Math.sqrt(pointDensity) / trace._maxNorm; + // Use the formula derived above + pointDist = (dX + dY + Math.sqrt((dX - dY) * (dX - dY) + 4 * nValid * dX * dY)) / (2 * (nValid - 1)); } - // Note: If anglemode === 'paper', this scale factor must be + pointDist *= SHRINK_FACTOR; // Adjust to slightly less than the computed distance + + // Set the trace scale factor such that the longest vector will have + // a length equal to the computed pointDist + trace._scaleFactor = pointDist / trace._maxNorm; + + // Note: If arrowref === 'paper', this scale factor must be // multiplied by Math.sqrt(xa._m * ya._m), but we can't do that quite yet // since the axis scales are not fully determined. Do it in plot step instead. - } else { // sizemode === 'raw' - // For raw sizemode, scale factor is always 1 + } else { + // lengthmode === 'raw' trace._scaleFactor = 1; } - // Multiply scale factor by sizeref - trace._scaleFactor *= trace.sizeref; + // Multiply computed scale factor by lengthfactor attr + trace._scaleFactor *= trace.lengthfactor; // Now we need to compute the arrow geometry for axis autorange const xTipPositions = new Array(len); @@ -148,7 +170,7 @@ module.exports = function calc(gd, trace) { const yTailPositions = new Array(len); var arrowLenX, arrowLenY; // Compute the x- and y-positions of the tip of each arrow, - // assuming anglemode === 'data' (i.e. u/v are in data coordinates) + // assuming arrowref === 'data' (i.e. u/v are in data coordinates) for(var i = 0; i < len; i++) { var cdi = cd[i]; arrowLenX = cdi._u * trace._scaleFactor; @@ -171,12 +193,12 @@ module.exports = function calc(gd, trace) { } } - if (anglemode === 'data') { - // If anglemode is 'data', we can use the arrow tip positions directly to expand the axes ranges + if (arrowref === 'data') { + // If arrowref is 'data', we can use the arrow tip positions directly to expand the axes ranges trace._extremes[xa._id] = Axes.findExtremes(xa, xTipPositions.concat(xTailPositions), {padded: true}); trace._extremes[ya._id] = Axes.findExtremes(ya, yTipPositions.concat(yTailPositions), {padded: true}); - } else { // anglemode === 'paper' - // TODO: For now, just do the same thing as for anglemode === 'data', but this is not correct. + } else { // arrowref === 'paper' + // TODO: For now, just do the same thing as for arrowref === 'data', but this is not correct. // We actually need more sophisticated logic here, since this will give a bad result // if the data aspect ratio is very different from the plot aspect ratio. trace._extremes[xa._id] = Axes.findExtremes(xa, xTipPositions.concat(xTailPositions), {padded: true}); diff --git a/src/traces/quiver/defaults.js b/src/traces/quiver/defaults.js index 43ba6973803..1b66e06422b 100644 --- a/src/traces/quiver/defaults.js +++ b/src/traces/quiver/defaults.js @@ -30,11 +30,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout for(var j = 0; j < len; j++) traceOut.v[j] = 0; } - coerce('anglemode'); + coerce('arrowref'); // Sizing API inspired by cone, but not identical - coerce('sizemode'); - coerce('sizeref'); + coerce('lengthmode'); + coerce('lengthfactor'); coerce('anchor'); // Arrow styling diff --git a/src/traces/quiver/plot.js b/src/traces/quiver/plot.js index 55ff64b43b6..f9771a4ef23 100644 --- a/src/traces/quiver/plot.js +++ b/src/traces/quiver/plot.js @@ -102,14 +102,10 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition lineSegments.exit().remove(); // Use maxNorm precomputed in calc - const maxNorm = trace._maxNorm || 0; - const anglemode = trace.anglemode; - const sizemode = trace.sizemode; - const sizeref = trace.sizeref; - const anchor = trace.anchor; - - // Adjust scale factor if anglemode is 'paper' - const scaleFactor = (anglemode === 'paper') ? trace._scaleFactor * Math.sqrt(Math.abs(xa._m * ya._m)) : trace._scaleFactor; + const { anchor, maxNorm = 0, arrowref } = trace; + + // Adjust scale factor if arrowref is 'paper' + const scaleFactor = (arrowref === 'paper') ? trace._scaleFactor * Math.sqrt(Math.abs(xa._m * ya._m)) : trace._scaleFactor; const markerArrowsize = trace.marker.arrowsize; // Update line segments @@ -123,12 +119,12 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition } // Compute pixel location of vector tip, *relative to* vector base (before scaling). - // If anglemode is 'paper', then u/v are interpreted in pixel coordinates, so we can use them directly. - // If anglemode is 'data', then u/v are interpreted in data coordinates, so we need to convert them to pixel coordinates. + // If arrowref is 'paper', then u/v are interpreted in pixel coordinates, so we can use them directly. + // If arrowref is 'data', then u/v are interpreted in data coordinates, so we need to convert them to pixel coordinates. // TODO: This probably doesn't work for log axes, but let's ignore log axes for now // since I'm not sure they make sense for quiver plots anyway - const pu = ((anglemode === 'paper') ? cdi._u * Math.sign(xa._m) : d3.round(xa._m * cdi._u)) * scaleFactor; - const pv = ((anglemode === 'paper') ? cdi._v * Math.sign(ya._m) : d3.round(ya._m * cdi._v)) * scaleFactor; + const pu = ((arrowref === 'paper') ? cdi._u * Math.sign(xa._m) : d3.round(xa._m * cdi._u)) * scaleFactor; + const pv = ((arrowref === 'paper') ? cdi._v * Math.sign(ya._m) : d3.round(ya._m * cdi._v)) * scaleFactor; // Compute arrow in data space // Check whether arrowsize was set explicitly in the input trace diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index a0fe3258807..d80d94dd602 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -6680,15 +6680,15 @@ export interface PieData { export interface QuiverData { /** - * Sets the arrows' anchor with respect to their (x,y) positions. Use *tail* to place (x,y) at the base, *tip* to place (x,y) at the head, or *center* to center the arrow on (x,y). + * Sets the vector arrows' anchor with respect to their (x,y) positions. Use *tail* to place (x,y) at the base, *tip* to place (x,y) at the head, or *center* to center the vector arrow on (x,y). * @default 'tail' */ anchor?: 'tip' | 'tail' | 'center'; /** - * Sets the mode used to determine the angle of the arrow vectors. If *paper*, u/v are interpreted in pixel coordinates and the rendered vector angle does not change regardless of the axes scales. If *data*, u/v are interpreted in data coordinates and the rendered vector angle may change, e.g. if zooming in along a single axis - * @default 'axis' + * Determines how the u/v vector components are interpreted. If *paper*, u/v are interpreted in pixel coordinates and the rendered vector angle does not change regardless of the axis scales. If *data*, u/v are interpreted in data coordinates and the rendered vector angle may change, e.g. if zooming in along a single axis + * @default 'data' */ - anglemode?: 'paper' | 'data'; + arrowref?: 'paper' | 'data'; /** Assigns extra data each datum. This may be useful when listening to hover, click and selection events. Note that, *scatter* traces also appends customdata items in the markers DOM elements */ customdata?: Datum[] | Datum[][] | TypedArray; /** @@ -6729,9 +6729,20 @@ export interface QuiverData { * Minimum: 0 */ legendwidth?: number; + /** + * Adjusts the drawn length of the vector arrows. The arrow length is determined by the values of u and v, then optionally rescaled when `lengthmode` is *scaled*, then multiplied by `lengthfactor`. + * @default 1 + * Minimum: 0 + */ + lengthfactor?: number; + /** + * Determines whether vector arrows are drawn according to their raw lengths, or scaled based on the maximum vector length and point density. Note: When `arrowref` is *paper* vectors are always scaled and `lengthmode` *raw* is ignored. + * @default 'scaled' + */ + lengthmode?: 'scaled' | 'raw'; marker?: { /** - * Sets the size of the arrow head relative to `marker.line.width`. A value of 1 (default) gives a head about 3x as wide as the line. + * Sets the size of the vector arrowhead relative to `marker.line.width`. A value of 1 (default) gives a head about 3x as wide as the line. * @default 1 * Minimum: 0.3 */ @@ -6775,7 +6786,7 @@ export interface QuiverData { */ dash?: Dash; /** - * Sets the width (in px) of the arrow lines. + * Sets the width (in px) of the vector arrow lines. * @default 2 * Minimum: 0 */ @@ -6817,17 +6828,6 @@ export interface QuiverData { * @default true */ showlegend?: boolean; - /** - * Determines whether arrows are drawn according to their raw lengths, or scaled based on the maximum vector length and point density. Note: When `anglemode` is *data* arrows are alwyas scaled and `sizemode` *raw* is ignored. - * @default 'scaled' - */ - sizemode?: 'scaled' | 'raw'; - /** - * Adjusts the arrow size scaling. The arrow length is determined by the vector norm multiplied by `sizeref`, optionally normalized when `sizemode` is *scaled* (`sizeref` is applied after scaling). - * @default 1 - * Minimum: 0 - */ - sizeref?: number; /** Sets text elements associated with each (x,y) pair. If a single string, the same string appears over all the data points. If an array of string, the items are mapped in order to the this trace's (x,y) coordinates. If trace `hoverinfo` contains a *text* flag and *hovertext* is not set, these elements will be seen in the hover labels. */ text?: string | string[]; /** Sets the text font. */ @@ -6838,7 +6838,7 @@ export interface QuiverData { */ textposition?: 'top left' | 'top center' | 'top right' | 'middle left' | 'middle center' | 'middle right' | 'bottom left' | 'bottom center' | 'bottom right' | ('top left' | 'top center' | 'top right' | 'middle left' | 'middle center' | 'middle right' | 'bottom left' | 'bottom center' | 'bottom right')[]; type?: 'quiver'; - /** Sets the x components of the arrow vectors. */ + /** Sets the x components of the vector arrows. */ u?: Datum[] | Datum[][] | TypedArray; /** Sets the hover text formatting rule for `u` using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format.By default the values are formatted using generic number format. */ uhoverformat?: string; @@ -6854,7 +6854,7 @@ export interface QuiverData { }; textfont?: Font; }; - /** Sets the y components of the arrow vectors. */ + /** Sets the y components of the vector arrows. */ v?: Datum[] | Datum[][] | TypedArray; /** Sets the hover text formatting rule for `v` using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format.By default the values are formatted using generic number format. */ vhoverformat?: string; @@ -6863,7 +6863,7 @@ export interface QuiverData { * @default true */ visible?: true | false | 'legendonly'; - /** Sets the x coordinates of the arrow locations. */ + /** Sets the x coordinates of the vector arrow locations. */ x?: Datum[] | Datum[][] | TypedArray; /** * Alternate to `x`. Builds a linear space of x coordinates. Use with `dx` where `x0` is the starting coordinate and `dx` the step. @@ -6877,7 +6877,7 @@ export interface QuiverData { xaxis?: string; /** Sets the hover text formatting rule for `x` using d3 formatting mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date formatter: *%h* for half of the year as a decimal number as well as *%{n}f* for fractional seconds with n digits. For example, *2016-10-13 09:15:23.456* with tickformat *%H~%M~%S.%2f* would display *09~15~23.46*By default the values are formatted using `xaxis.hoverformat`. */ xhoverformat?: string; - /** Sets the y coordinates of the arrow locations. */ + /** Sets the y coordinates of the vector arrow locations. */ y?: Datum[] | Datum[][] | TypedArray; /** * Alternate to `y`. Builds a linear space of y coordinates. Use with `dy` where `y0` is the starting coordinate and `dy` the step. diff --git a/test/image/baselines/quiver_lengthmode.png b/test/image/baselines/quiver_lengthmode.png new file mode 100644 index 00000000000..633c59bff7e Binary files /dev/null and b/test/image/baselines/quiver_lengthmode.png differ diff --git a/test/image/baselines/quiver_sizemode.png b/test/image/baselines/quiver_sizemode.png deleted file mode 100644 index 35a1160b3cd..00000000000 Binary files a/test/image/baselines/quiver_sizemode.png and /dev/null differ diff --git a/test/image/mocks/quiver_anchor.json b/test/image/mocks/quiver_anchor.json index ff1eb3bd12b..d858c740434 100644 --- a/test/image/mocks/quiver_anchor.json +++ b/test/image/mocks/quiver_anchor.json @@ -7,7 +7,7 @@ "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 1, 1, 1, 1, 1, 1, 1, 1], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", "marker": { "color": "#4466ee", @@ -23,7 +23,7 @@ "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 1, 1, 1, 1, 1, 1, 1, 1], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tip", "marker": { "color": "#ee3344", @@ -39,7 +39,7 @@ "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 1, 1, 1, 1, 1, 1, 1, 1], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "center", "marker": { "color": "#8811cc", diff --git a/test/image/mocks/quiver_categorical.json b/test/image/mocks/quiver_categorical.json index f5b9b629c7a..fd5b3ce90a6 100644 --- a/test/image/mocks/quiver_categorical.json +++ b/test/image/mocks/quiver_categorical.json @@ -9,7 +9,7 @@ "text": ["A", "B", "C", "D", "E", "F", "G", "H", "I"], "textposition": "top center", "textfont": { "size": 12 }, - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", "marker": { "line": {"width": 2} diff --git a/test/image/mocks/quiver_colorscale.json b/test/image/mocks/quiver_colorscale.json index bfa3e432279..a5eb131e06b 100644 --- a/test/image/mocks/quiver_colorscale.json +++ b/test/image/mocks/quiver_colorscale.json @@ -6,9 +6,9 @@ "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 2, 3, 1, 2, 3, 1, 2, 3], "v": [1, 1, 1, 2, 2, 2, 3, 3, 3], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", - "anglemode": "paper", + "arrowref": "paper", "marker": { "showscale": true, "colorbar": { diff --git a/test/image/mocks/quiver_custom-colorscale.json b/test/image/mocks/quiver_custom-colorscale.json index 30f407331da..68f740b6aea 100644 --- a/test/image/mocks/quiver_custom-colorscale.json +++ b/test/image/mocks/quiver_custom-colorscale.json @@ -20,7 +20,7 @@ }, "line": {"width": 2} }, - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail" } ], diff --git a/test/image/mocks/quiver_sizemode.json b/test/image/mocks/quiver_lengthmode.json similarity index 82% rename from test/image/mocks/quiver_sizemode.json rename to test/image/mocks/quiver_lengthmode.json index f328399af18..ff809635027 100644 --- a/test/image/mocks/quiver_sizemode.json +++ b/test/image/mocks/quiver_lengthmode.json @@ -7,7 +7,7 @@ "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 2, 3, 1, 2, 3, 1, 2, 3], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", "marker": { "color": "red", @@ -18,13 +18,13 @@ }, { "type": "quiver", - "name": "raw (sizeref=1.2)", + "name": "raw (lengthfactor=1.2)", "x": [0, 1, 2, 0, 1, 2, 0, 1, 2], "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 2, 3, 1, 2, 3, 1, 2, 3], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "raw", - "sizeref": 1.2, + "lengthmode": "raw", + "lengthfactor": 1.2, "anchor": "tail", "marker": { "color": "blue", @@ -35,13 +35,13 @@ }, { "type": "quiver", - "name": "raw (sizeref=0.6)", + "name": "raw (lengthfactor=0.6)", "x": [0, 1, 2, 0, 1, 2, 0, 1, 2], "y": [0, 0, 0, 1, 1, 1, 2, 2, 2], "u": [1, 2, 3, 1, 2, 3, 1, 2, 3], "v": [1, 1, 1, 1, 1, 1, 1, 1, 1], - "sizemode": "raw", - "sizeref": 0.6, + "lengthmode": "raw", + "lengthfactor": 0.6, "anchor": "tail", "marker": { "color": "green", @@ -64,7 +64,7 @@ }, "xaxis2": { "domain": [0.35, 0.65], - "title": {"text": "raw (sizeref=1.2)"} + "title": {"text": "raw (lengthfactor=1.2)"} }, "yaxis2": { "domain": [0, 1], @@ -72,7 +72,7 @@ }, "xaxis3": { "domain": [0.7, 1], - "title": {"text": "raw (sizeref=0.6)"} + "title": {"text": "raw (lengthfactor=0.6)"} }, "yaxis3": { "domain": [0, 1], diff --git a/test/image/mocks/quiver_multi-trace.json b/test/image/mocks/quiver_multi-trace.json index 08fb36bf386..b711d4c5e46 100644 --- a/test/image/mocks/quiver_multi-trace.json +++ b/test/image/mocks/quiver_multi-trace.json @@ -7,7 +7,7 @@ "y": [0, 0, 0, 1, 1, 1], "u": [1, 1.5, 2, 1, 1.5, 2], "v": [0.5, 1, 0.5, 1, 1.5, 1], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", "marker": { "color": "red", @@ -21,7 +21,7 @@ "y": [0, 0, 0, 1, 1, 1], "u": [-1, -1.5, -2, -1, -1.5, -2], "v": [1, 0.5, 1, 0.5, 1, 0.5], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "tail", "marker": { "color": "blue", diff --git a/test/image/mocks/quiver_wind.json b/test/image/mocks/quiver_wind.json index 71bb062a139..d3474b38eea 100644 --- a/test/image/mocks/quiver_wind.json +++ b/test/image/mocks/quiver_wind.json @@ -23,7 +23,7 @@ 0.8, 1.2, 1.5, 1.8, 2, 1.8, 1.5, 0.5, 0.8, 1.2, 1.5, 1.8, 1.5, 1.2, 0.2, 0.5, 0.8, 1.2, 1.5, 1.2, 0.8], - "sizemode": "scaled", + "lengthmode": "scaled", "anchor": "center", "marker": { "colorscale": "Jet", diff --git a/test/jasmine/tests/quiver_test.js b/test/jasmine/tests/quiver_test.js index 7acffc73e12..2a544383338 100644 --- a/test/jasmine/tests/quiver_test.js +++ b/test/jasmine/tests/quiver_test.js @@ -95,11 +95,17 @@ describe('Test quiver defaults', function() { expect(gd._fullData[0].v).toEqual([0, 0, 0]); }); - it('should set sizemode and sizeref defaults correctly', function() { + it('should set lengthmode and lengthfactor defaults correctly', function() { gd = makeGD(); supplyAllDefaults(gd); - expect(gd._fullData[0].sizemode).toBe('scaled'); - expect(gd._fullData[0].sizeref).toBe(1); + expect(gd._fullData[0].lengthmode).toBe('scaled'); + expect(gd._fullData[0].lengthfactor).toBe(1); + }); + + it('should set arrowref default to data', function() { + gd = makeGD(); + supplyAllDefaults(gd); + expect(gd._fullData[0].arrowref).toBe('data'); }); it('should set anchor default to tail', function() { @@ -220,13 +226,13 @@ describe('Test quiver interactions', function() { y: [1, 2], u: [1, 0], v: [0, 1], - sizeref: 0.5 + lengthfactor: 0.5 }]).then(function() { - expect(gd._fullData[0].sizeref).toBe(0.5); - return Plotly.restyle(gd, 'sizeref', 1.5); + expect(gd._fullData[0].lengthfactor).toBe(0.5); + return Plotly.restyle(gd, 'lengthfactor', 1.5); }) .then(function() { - expect(gd._fullData[0].sizeref).toBe(1.5); + expect(gd._fullData[0].lengthfactor).toBe(1.5); return Plotly.restyle(gd, 'anchor', 'tip'); }) .then(function() { @@ -293,25 +299,25 @@ describe('Test quiver interactions', function() { .then(done, done.fail); }); - it('should handle sizemode changes', function(done) { + it('should handle lengthmode changes', function(done) { Plotly.newPlot(gd, [{ type: 'quiver', x: [0, 1, 2], y: [0, 1, 2], u: [1, 2, 3], v: [1, 2, 3], - sizemode: 'scaled', - sizeref: 0.5 + lengthmode: 'scaled', + lengthfactor: 0.5 }]).then(function() { - expect(gd._fullData[0].sizemode).toBe('scaled'); - return Plotly.restyle(gd, 'sizemode', 'raw'); + expect(gd._fullData[0].lengthmode).toBe('scaled'); + return Plotly.restyle(gd, 'lengthmode', 'raw'); }) .then(function() { - expect(gd._fullData[0].sizemode).toBe('raw'); - return Plotly.restyle(gd, 'sizemode', 'scaled'); + expect(gd._fullData[0].lengthmode).toBe('raw'); + return Plotly.restyle(gd, 'lengthmode', 'scaled'); }) .then(function() { - expect(gd._fullData[0].sizemode).toBe('scaled'); + expect(gd._fullData[0].lengthmode).toBe('scaled'); }) .then(done, done.fail); }); diff --git a/test/plot-schema.json b/test/plot-schema.json index f5623e929be..89a48f82fbb 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -49859,7 +49859,7 @@ "animatable": true, "attributes": { "anchor": { - "description": "Sets the arrows' anchor with respect to their (x,y) positions. Use *tail* to place (x,y) at the base, *tip* to place (x,y) at the head, or *center* to center the arrow on (x,y).", + "description": "Sets the vector arrows' anchor with respect to their (x,y) positions. Use *tail* to place (x,y) at the base, *tip* to place (x,y) at the head, or *center* to center the vector arrow on (x,y).", "dflt": "tail", "editType": "calc", "valType": "enumerated", @@ -49869,16 +49869,6 @@ "center" ] }, - "anglemode": { - "description": "Sets the mode used to determine the angle of the arrow vectors. If *paper*, u/v are interpreted in pixel coordinates and the rendered vector angle does not change regardless of the axes scales. If *data*, u/v are interpreted in data coordinates and the rendered vector angle may change, e.g. if zooming in along a single axis", - "dflt": "axis", - "editType": "calc", - "valType": "enumerated", - "values": [ - "paper", - "data" - ] - }, "customdata": { "description": "Assigns extra data each datum. This may be useful when listening to hover, click and selection events. Note that, *scatter* traces also appends customdata items in the markers DOM elements", "editType": "calc", @@ -50194,9 +50184,26 @@ "min": 0, "valType": "number" }, + "lengthfactor": { + "description": "Adjusts the drawn length of the vector arrows. The arrow length is determined by the values of u and v, then optionally rescaled when `lengthmode` is *scaled*, then multiplied by `lengthfactor`.", + "dflt": 1, + "editType": "calc", + "min": 0, + "valType": "number" + }, + "lengthmode": { + "description": "Determines whether vector arrows are drawn according to their raw lengths, or scaled based on the maximum vector length and point density. Note: When `uvref` is *paper* vectors are always scaled and `lengthmode` *raw* is ignored.", + "dflt": "scaled", + "editType": "calc", + "valType": "enumerated", + "values": [ + "scaled", + "raw" + ] + }, "marker": { "arrowsize": { - "description": "Sets the size of the arrow head relative to `marker.line.width`. A value of 1 (default) gives a head about 3x as wide as the line.", + "description": "Sets the size of the vector arrowhead relative to `marker.line.width`. A value of 1 (default) gives a head about 3x as wide as the line.", "dflt": 1, "editType": "calc", "min": 0.3, @@ -50879,7 +50886,7 @@ "editType": "style", "role": "object", "width": { - "description": "Sets the width (in px) of the arrow lines.", + "description": "Sets the width (in px) of the vector arrow lines.", "dflt": 2, "editType": "style", "min": 0, @@ -50962,23 +50969,6 @@ "editType": "style", "valType": "boolean" }, - "sizemode": { - "description": "Determines whether arrows are drawn according to their raw lengths, or scaled based on the maximum vector length and point density. Note: When `anglemode` is *data* arrows are alwyas scaled and `sizemode` *raw* is ignored.", - "dflt": "scaled", - "editType": "calc", - "valType": "enumerated", - "values": [ - "scaled", - "raw" - ] - }, - "sizeref": { - "description": "Adjusts the arrow size scaling. The arrow length is determined by the vector norm multiplied by `sizeref`, optionally normalized when `sizemode` is *scaled* (`sizeref` is applied after scaling).", - "dflt": 1, - "editType": "calc", - "min": 0, - "valType": "number" - }, "text": { "arrayOk": true, "description": "Sets text elements associated with each (x,y) pair. If a single string, the same string appears over all the data points. If an array of string, the items are mapped in order to the this trace's (x,y) coordinates. If trace `hoverinfo` contains a *text* flag and *hovertext* is not set, these elements will be seen in the hover labels.", @@ -51105,7 +51095,7 @@ "type": "quiver", "u": { "anim": true, - "description": "Sets the x components of the arrow vectors.", + "description": "Sets the x components of the vector arrows.", "editType": "calc", "valType": "data_array" }, @@ -51158,9 +51148,19 @@ "role": "object" } }, + "uvref": { + "description": "Determines how the u/v vector components are interpreted. If *paper*, u/v are interpreted in pixel coordinates and the rendered vector angle does not change regardless of the axis scales. If *data*, u/v are interpreted in data coordinates and the rendered vector angle may change, e.g. if zooming in along a single axis", + "dflt": "data", + "editType": "calc", + "valType": "enumerated", + "values": [ + "paper", + "data" + ] + }, "v": { "anim": true, - "description": "Sets the y components of the arrow vectors.", + "description": "Sets the y components of the vector arrows.", "editType": "calc", "valType": "data_array" }, @@ -51183,7 +51183,7 @@ }, "x": { "anim": true, - "description": "Sets the x coordinates of the arrow locations.", + "description": "Sets the x coordinates of the vector arrow locations.", "editType": "calc+clearAxisTypes", "valType": "data_array" }, @@ -51208,7 +51208,7 @@ }, "y": { "anim": true, - "description": "Sets the y coordinates of the arrow locations.", + "description": "Sets the y coordinates of the vector arrow locations.", "editType": "calc+clearAxisTypes", "valType": "data_array" },