diff --git a/.eslintrc b/.eslintrc index aa3ddcb461e..185abda5ef4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,7 @@ "Int16Array": true, "Int32Array": true, "ArrayBuffer": true, + "DataView": true, "SVGElement": false }, "rules": { diff --git a/src/components/colorscale/has_colorscale.js b/src/components/colorscale/has_colorscale.js index 74b623b8db7..85f793a48aa 100644 --- a/src/components/colorscale/has_colorscale.js +++ b/src/components/colorscale/has_colorscale.js @@ -6,24 +6,20 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var isNumeric = require('fast-isnumeric'); - var Lib = require('../../lib'); - var isValidScale = require('./is_valid_scale'); - module.exports = function hasColorscale(trace, containerStr) { var container = containerStr ? - Lib.nestedProperty(trace, containerStr).get() || {} : - trace, - color = container.color, - isArrayWithOneNumber = false; + Lib.nestedProperty(trace, containerStr).get() || {} : + trace; + var color = container.color; - if(Array.isArray(color)) { + var isArrayWithOneNumber = false; + if(Lib.isArrayOrTypedArray(color)) { for(var i = 0; i < color.length; i++) { if(isNumeric(color[i])) { isArrayWithOneNumber = true; diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index bf185066f85..2f1baf0e55a 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -316,10 +316,10 @@ function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerL if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc); // weird case: array wasn't long enough to apply to every point - else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine; + else if(Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine; else lineColor = markerLine.color; - if(Array.isArray(marker.color)) { + if(Lib.isArrayOrTypedArray(marker.color)) { fillColor = Color.defaultLine; perPointGradient = true; } @@ -542,7 +542,7 @@ drawing.tryColorscale = function(marker, prefix) { scl = cont.colorscale, colorArray = cont.color; - if(scl && Array.isArray(colorArray)) { + if(scl && Lib.isArrayOrTypedArray(colorArray)) { return Colorscale.makeColorScaleFunc( Colorscale.extractScale(scl, cont.cmin, cont.cmax) ); diff --git a/src/lib/coerce.js b/src/lib/coerce.js index e6eba28261a..89e2983a702 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -19,18 +19,21 @@ var nestedProperty = require('./nested_property'); var counterRegex = require('./regex').counter; var DESELECTDIM = require('../constants/interactions').DESELECTDIM; var wrap180 = require('./angles').wrap180; +var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; exports.valObjectMeta = { data_array: { // You can use *dflt=[] to force said array to exist though. description: [ 'An {array} of data.', - 'The value MUST be an {array}, or we ignore it.' + 'The value MUST be an {array}, or we ignore it.', + 'Note that typed arrays (e.g. Float32Array) are supported.' ].join(' '), requiredOpts: [], otherOpts: ['dflt'], coerceFunction: function(v, propOut, dflt) { - if(Array.isArray(v)) propOut.set(v); + // TODO maybe `v: {type: 'float32', vals: [/* ... */]}` also + if(isArrayOrTypedArray(v)) propOut.set(v); else if(dflt !== undefined) propOut.set(dflt); } }, @@ -367,7 +370,7 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt * individual form (eg. some array vals can be numbers, even if the * single values must be color strings) */ - if(opts.arrayOk && Array.isArray(v)) { + if(opts.arrayOk && isArrayOrTypedArray(v)) { propOut.set(v); return v; } @@ -464,7 +467,7 @@ exports.coerceSelectionMarkerOpacity = function(traceOut, coerce) { // // Only give [un]selected.marker.opacity a default value if you don't // set any other [un]selected attributes. - if(!Array.isArray(mo) && !traceOut.selected && !traceOut.unselected) { + if(!isArrayOrTypedArray(mo) && !traceOut.selected && !traceOut.unselected) { smoDflt = mo; usmoDflt = DESELECTDIM * mo; } @@ -476,7 +479,7 @@ exports.coerceSelectionMarkerOpacity = function(traceOut, coerce) { exports.validate = function(value, opts) { var valObjectDef = exports.valObjectMeta[opts.valType]; - if(opts.arrayOk && Array.isArray(value)) return true; + if(opts.arrayOk && isArrayOrTypedArray(value)) return true; if(valObjectDef.validateFunction) { return valObjectDef.validateFunction(value, opts); diff --git a/src/lib/extend.js b/src/lib/extend.js index 6139e786a70..3e615e7d04b 100644 --- a/src/lib/extend.js +++ b/src/lib/extend.js @@ -65,6 +65,8 @@ function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) { var input, key, src, copy, copyIsArray, clone, allPrimitives; + // TODO does this do the right thing for typed arrays? + if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) { allPrimitives = primitivesLoopSplice(inputs[1], target); diff --git a/src/lib/index.js b/src/lib/index.js index 56395247b6a..8ca656c82a2 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -22,12 +22,15 @@ lib.nestedProperty = require('./nested_property'); lib.keyedContainer = require('./keyed_container'); lib.relativeAttr = require('./relative_attr'); lib.isPlainObject = require('./is_plain_object'); -lib.isArray = require('./is_array'); lib.mod = require('./mod'); lib.toLogRange = require('./to_log_range'); lib.relinkPrivateKeys = require('./relink_private'); lib.ensureArray = require('./ensure_array'); +var isArrayModule = require('./is_array'); +lib.isTypedArray = isArrayModule.isTypedArray; +lib.isArrayOrTypedArray = isArrayModule.isArrayOrTypedArray; + var coerceModule = require('./coerce'); lib.valObjectMeta = coerceModule.valObjectMeta; lib.coerce = coerceModule.coerce; @@ -389,7 +392,7 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) { * @param {string} cdAttr : calcdata key */ lib.mergeArray = function(traceAttr, cd, cdAttr) { - if(Array.isArray(traceAttr)) { + if(lib.isArrayOrTypedArray(traceAttr)) { var imax = Math.min(traceAttr.length, cd.length); for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i]; } @@ -408,7 +411,7 @@ lib.mergeArray = function(traceAttr, cd, cdAttr) { lib.fillArray = function(traceAttr, cd, cdAttr, fn) { fn = fn || lib.identity; - if(Array.isArray(traceAttr)) { + if(lib.isArrayOrTypedArray(traceAttr)) { for(var i = 0; i < cd.length; i++) { cd[i][cdAttr] = fn(traceAttr[i]); } @@ -429,8 +432,8 @@ lib.castOption = function(trace, ptNumber, astr, fn) { var val = lib.nestedProperty(trace, astr).get(); - if(Array.isArray(val)) { - if(Array.isArray(ptNumber) && Array.isArray(val[ptNumber[0]])) { + if(lib.isArrayOrTypedArray(val)) { + if(Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) { return fn(val[ptNumber[0]][ptNumber[1]]); } else { return fn(val[ptNumber]); diff --git a/src/lib/is_array.js b/src/lib/is_array.js index 3c403a35da1..28b58d0d1a0 100644 --- a/src/lib/is_array.js +++ b/src/lib/is_array.js @@ -8,15 +8,20 @@ 'use strict'; -/** - * Return true for arrays, whether they're untyped or not. - */ +// IE9 fallbacks -// IE9 fallback var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ? {isView: function() { return false; }} : ArrayBuffer; -module.exports = function isArray(a) { - return Array.isArray(a) || ab.isView(a); +var dv = (typeof DataView === 'undefined') ? + function() {} : + DataView; + +exports.isTypedArray = function(a) { + return ab.isView(a) && !(a instanceof dv); +}; + +exports.isArrayOrTypedArray = function(a) { + return Array.isArray(a) || exports.isTypedArray(a); }; diff --git a/src/lib/nested_property.js b/src/lib/nested_property.js index 6e97187d7bd..754609db789 100644 --- a/src/lib/nested_property.js +++ b/src/lib/nested_property.js @@ -10,7 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); -var isArray = require('./is_array'); +var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; var isPlainObject = require('./is_plain_object'); var containerArrayMatch = require('../plot_api/container_array_match'); @@ -96,7 +96,7 @@ function npGet(cont, parts) { } return allSame ? out[0] : out; } - if(typeof curPart === 'number' && !isArray(curCont)) { + if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) { return undefined; } curCont = curCont[curPart]; @@ -144,7 +144,7 @@ function isDeletable(val, propStr) { ) { return false; } - if(!isArray(val)) return true; + if(!isArrayOrTypedArray(val)) return true; if(propStr.match(INFO_PATTERNS)) return true; @@ -167,7 +167,7 @@ function npSet(cont, parts, propStr) { for(i = 0; i < parts.length - 1; i++) { curPart = parts[i]; - if(typeof curPart === 'number' && !isArray(curCont)) { + if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) { throw 'array index but container is not an array'; } @@ -211,7 +211,7 @@ function joinPropStr(propStr, newPart) { // handle special -1 array index function setArrayAll(containerArray, innerParts, val, propStr) { - var arrayVal = isArray(val), + var arrayVal = isArrayOrTypedArray(val), allSet = true, thisVal = val, thisPropStr = propStr.replace('-1', 0), @@ -261,7 +261,7 @@ function pruneContainers(containerLevels) { propPart = containerLevels[i][1]; remainingKeys = false; - if(isArray(curCont)) { + if(isArrayOrTypedArray(curCont)) { for(j = curCont.length - 1; j >= 0; j--) { if(isDeletable(curCont[j], joinPropStr(propPart, j))) { if(remainingKeys) curCont[j] = undefined; @@ -287,7 +287,7 @@ function pruneContainers(containerLevels) { function emptyObj(obj) { if(obj === undefined || obj === null) return true; if(typeof obj !== 'object') return false; // any plain value - if(isArray(obj)) return !obj.length; // [] + if(isArrayOrTypedArray(obj)) return !obj.length; // [] return !Object.keys(obj).length; // {} } diff --git a/src/lib/relink_private.js b/src/lib/relink_private.js index cade381bda5..e0c63b8fef4 100644 --- a/src/lib/relink_private.js +++ b/src/lib/relink_private.js @@ -9,7 +9,7 @@ 'use strict'; -var isArray = require('./is_array'); +var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; var isPlainObject = require('./is_plain_object'); /** @@ -35,7 +35,7 @@ module.exports = function relinkPrivateKeys(toContainer, fromContainer) { toContainer[k] = fromVal; } - else if(isArray(fromVal) && isArray(toVal) && isPlainObject(fromVal[0])) { + else if(isArrayOrTypedArray(fromVal) && isArrayOrTypedArray(toVal) && isPlainObject(fromVal[0])) { // filter out data_array items that can contain user objects // most of the time the toVal === fromVal check will catch these early diff --git a/src/lib/stats.js b/src/lib/stats.js index 9e48bc081b4..a717f7b49bd 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -10,7 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); - +var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray; /** * aggNums() returns the result of an aggregate function applied to an array of @@ -30,7 +30,7 @@ exports.aggNums = function(f, v, a, len) { b; if(!len || len > a.length) len = a.length; if(!isNumeric(v)) v = false; - if(Array.isArray(a[0])) { + if(isArrayOrTypedArray(a[0])) { b = new Array(len); for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]); a = b; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 9aa3f9712ce..c52bdc44cd5 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -890,12 +890,15 @@ function getExtendProperties(gd, update, indices, maxPoints) { target = prop.get(); insert = update[key][j]; - if(!Array.isArray(insert)) { + if(!Lib.isArrayOrTypedArray(insert)) { throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array'); } - if(!Array.isArray(target)) { + if(!Lib.isArrayOrTypedArray(target)) { throw new Error('cannot extend missing or non-array attribute: ' + key); } + if(target.constructor !== insert.constructor) { + throw new Error('cannot extend array with an array of a different type: ' + key); + } /* * maxPoints may be an object map or a scalar. If object select the key:value, else @@ -931,64 +934,43 @@ function getExtendProperties(gd, update, indices, maxPoints) { * @param {Object} update * @param {Number[]} indices * @param {Number||Object} maxPoints - * @param {Function} lengthenArray - * @param {Function} spliceArray + * @param {Function} updateArray * @return {Object} */ -function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray) { - +function spliceTraces(gd, update, indices, maxPoints, updateArray) { assertExtendTracesArgs(gd, update, indices, maxPoints); - var updateProps = getExtendProperties(gd, update, indices, maxPoints), - remainder = [], - undoUpdate = {}, - undoPoints = {}; - var target, prop, maxp; + var updateProps = getExtendProperties(gd, update, indices, maxPoints); + var undoUpdate = {}; + var undoPoints = {}; for(var i = 0; i < updateProps.length; i++) { + var prop = updateProps[i].prop; + var maxp = updateProps[i].maxp; - /* - * prop is the object returned by Lib.nestedProperties - */ - prop = updateProps[i].prop; - maxp = updateProps[i].maxp; - - target = lengthenArray(updateProps[i].target, updateProps[i].insert); - - /* - * If maxp is set within post-extension trace.length, splice to maxp length. - * Otherwise skip function call as splice op will have no effect anyway. - */ - if(maxp >= 0 && maxp < target.length) remainder = spliceArray(target, maxp); - - /* - * to reverse this operation we need the size of the original trace as the reverse - * operation will need to window out any lengthening operation performed in this pass. - */ - maxp = updateProps[i].target.length; - - /* - * Magic happens here! update gd.data.trace[key] with new array data. - */ - prop.set(target); + // return new array and remainder + var out = updateArray(updateProps[i].target, updateProps[i].insert, maxp); + prop.set(out[0]); + // build the inverse update object for the undo operation if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = []; - if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = []; - - /* - * build the inverse update object for the undo operation - */ - undoUpdate[prop.astr].push(remainder); + undoUpdate[prop.astr].push(out[1]); - /* - * build the matching maxPoints undo object containing original trace lengths. - */ - undoPoints[prop.astr].push(maxp); + // build the matching maxPoints undo object containing original trace lengths + if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = []; + undoPoints[prop.astr].push(updateProps[i].target.length); } return {update: undoUpdate, maxPoints: undoPoints}; } +function concatTypedArray(arr0, arr1) { + var arr2 = new arr0.constructor(arr0.length + arr1.length); + arr2.set(arr0); + arr2.set(arr1, arr0.length); + return arr2; +} + /** * extend && prepend traces at indices with update arrays, window trace lengths to maxPoints * @@ -1009,24 +991,55 @@ function spliceTraces(gd, update, indices, maxPoints, lengthenArray, spliceArray Plotly.extendTraces = function extendTraces(gd, update, indices, maxPoints) { gd = Lib.getGraphDiv(gd); - var undo = spliceTraces(gd, update, indices, maxPoints, + function updateArray(target, insert, maxp) { + var newArray, remainder; + + if(Lib.isTypedArray(target)) { + if(maxp < 0) { + var none = new target.constructor(0); + var both = concatTypedArray(target, insert); - /* - * The Lengthen operation extends trace from end with insert - */ - function(target, insert) { - return target.concat(insert); - }, + if(maxp < 0) { + newArray = both; + remainder = none; + } else { + newArray = none; + remainder = both; + } + } else { + newArray = new target.constructor(maxp); + remainder = new target.constructor(target.length + insert.length - maxp); + + if(maxp === insert.length) { + newArray.set(insert); + remainder.set(target); + } else if(maxp < insert.length) { + var numberOfItemsFromInsert = insert.length - maxp; + + newArray.set(insert.subarray(numberOfItemsFromInsert)); + remainder.set(target); + remainder.set(insert.subarray(0, numberOfItemsFromInsert), target.length); + } else { + var numberOfItemsFromTarget = maxp - insert.length; + var targetBegin = target.length - numberOfItemsFromTarget; + + newArray.set(target.subarray(targetBegin)); + newArray.set(insert, numberOfItemsFromTarget); + remainder.set(target.subarray(0, targetBegin)); + } + } + } else { + newArray = target.concat(insert); + remainder = (maxp >= 0 && maxp < newArray.length) ? + newArray.splice(0, newArray.length - maxp) : + []; + } - /* - * Window the trace keeping maxPoints, counting back from the end - */ - function(target, maxPoints) { - return target.splice(0, target.length - maxPoints); - }); + return [newArray, remainder]; + } + var undo = spliceTraces(gd, update, indices, maxPoints, updateArray); var promise = Plotly.redraw(gd); - var undoArgs = [gd, undo.update, indices, undo.maxPoints]; Queue.add(gd, Plotly.prependTraces, undoArgs, extendTraces, arguments); @@ -1036,24 +1049,54 @@ Plotly.extendTraces = function extendTraces(gd, update, indices, maxPoints) { Plotly.prependTraces = function prependTraces(gd, update, indices, maxPoints) { gd = Lib.getGraphDiv(gd); - var undo = spliceTraces(gd, update, indices, maxPoints, + function updateArray(target, insert, maxp) { + var newArray, remainder; + + if(Lib.isTypedArray(target)) { + if(maxp <= 0) { + var none = new target.constructor(0); + var both = concatTypedArray(insert, target); - /* - * The Lengthen operation extends trace by appending insert to start - */ - function(target, insert) { - return insert.concat(target); - }, + if(maxp < 0) { + newArray = both; + remainder = none; + } else { + newArray = none; + remainder = both; + } + } else { + newArray = new target.constructor(maxp); + remainder = new target.constructor(target.length + insert.length - maxp); + + if(maxp === insert.length) { + newArray.set(insert); + remainder.set(target); + } else if(maxp < insert.length) { + var numberOfItemsFromInsert = insert.length - maxp; + + newArray.set(insert.subarray(0, numberOfItemsFromInsert)); + remainder.set(insert.subarray(numberOfItemsFromInsert)); + remainder.set(target, numberOfItemsFromInsert); + } else { + var numberOfItemsFromTarget = maxp - insert.length; + + newArray.set(insert); + newArray.set(target.subarray(0, numberOfItemsFromTarget), insert.length); + remainder.set(target.subarray(numberOfItemsFromTarget)); + } + } + } else { + newArray = insert.concat(target); + remainder = (maxp >= 0 && maxp < newArray.length) ? + newArray.splice(maxp, newArray.length) : + []; + } - /* - * Window the trace keeping maxPoints, counting forward from the start - */ - function(target, maxPoints) { - return target.splice(maxPoints, target.length); - }); + return [newArray, remainder]; + } + var undo = spliceTraces(gd, update, indices, maxPoints, updateArray); var promise = Plotly.redraw(gd); - var undoArgs = [gd, undo.update, indices, undo.maxPoints]; Queue.add(gd, Plotly.extendTraces, undoArgs, prependTraces, arguments); @@ -1568,7 +1611,9 @@ function _restyle(gd, aobj, traces) { else { if(valObject) { // must redo calcdata when restyling array values of arrayOk attributes - if(valObject.arrayOk && (Array.isArray(newVal) || Array.isArray(oldVal))) { + if(valObject.arrayOk && ( + Lib.isArrayOrTypedArray(newVal) || Lib.isArrayOrTypedArray(oldVal)) + ) { flags.calc = true; } else editTypes.update(flags, valObject); @@ -2008,12 +2053,12 @@ function _relayout(gd, aobj) { else flags.plot = true; } else { - if((fullLayout._has('gl2d') || fullLayout._has('regl')) && + if((fullLayout._has('scatter-like') && fullLayout._has('regl')) && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) ) { - flags.calc = true; + flags.plot = true; } else if(valObject) editTypes.update(flags, valObject); else flags.calc = true; diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index ef8261834c9..54994abf457 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -191,7 +191,7 @@ exports.findArrayAttributes = function(trace) { var astr = toAttrString(stack); var val = Lib.nestedProperty(trace, astr).get(); - if(!Array.isArray(val)) return; + if(!Lib.isArrayOrTypedArray(val)) return; arrayAttributes.push(astr); } diff --git a/src/plot_api/validate.js b/src/plot_api/validate.js index 0b77146a4e1..ae789027bf5 100644 --- a/src/plot_api/validate.js +++ b/src/plot_api/validate.js @@ -6,10 +6,8 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; - var Lib = require('../lib'); var Plots = require('../plots/plots'); var PlotSchema = require('./plot_schema'); @@ -17,7 +15,7 @@ var dfltConfig = require('./plot_config'); var isPlainObject = Lib.isPlainObject; var isArray = Array.isArray; - +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; /** * Validate a data array and layout object. @@ -257,7 +255,7 @@ function crawl(objIn, objOut, schema, list, base, path) { else if(!isPlainObject(valIn) && isPlainObject(valOut)) { list.push(format('object', base, p, valIn)); } - else if(!isArray(valIn) && isArray(valOut) && !isInfoArray && !isColorscale) { + else if(!isArrayOrTypedArray(valIn) && isArrayOrTypedArray(valOut) && !isInfoArray && !isColorscale) { list.push(format('array', base, p, valIn)); } else if(!(k in objOut)) { diff --git a/src/plots/array_container_defaults.js b/src/plots/array_container_defaults.js index 05428bb117f..70983dc33d8 100644 --- a/src/plots/array_container_defaults.js +++ b/src/plots/array_container_defaults.js @@ -10,7 +10,6 @@ var Lib = require('../lib'); - /** Convenience wrapper for making array container logic DRY and consistent * * @param {object} parentObjIn @@ -46,7 +45,7 @@ module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut var previousContOut = parentObjOut[name]; - var contIn = Lib.isArray(parentObjIn[name]) ? parentObjIn[name] : [], + var contIn = Lib.isArrayOrTypedArray(parentObjIn[name]) ? parentObjIn[name] : [], contOut = parentObjOut[name] = [], i; @@ -70,7 +69,7 @@ module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut // in case this array gets its defaults rebuilt independent of the whole layout, // relink the private keys just for this array. - if(Lib.isArray(previousContOut)) { + if(Lib.isArrayOrTypedArray(previousContOut)) { var len = Math.min(previousContOut.length, contOut.length); for(i = 0; i < len; i++) { Lib.relinkPrivateKeys(contOut[i], previousContOut[i]); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index ca86b11c053..20faf73d50c 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -423,6 +423,13 @@ axes.saveShowSpikeInitial = function(gd, overwrite) { return hasOneAxisChanged; }; +axes.doesAxisNeedAutoRange = function(ax) { + return ( + ax.autorange || + !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() + ); +}; + // axes.expand: if autoranging, include new data in the outer limits // for this axis // data is an array of numbers (ie already run through ax.d2c) @@ -436,12 +443,7 @@ axes.saveShowSpikeInitial = function(gd, overwrite) { // tozero: (boolean) make sure to include zero if axis is linear, // and make it a tight bound if possible axes.expand = function(ax, data, options) { - var needsAutorange = ( - ax.autorange || - !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() - ); - - if(!needsAutorange || !data) return; + if(!axes.doesAxisNeedAutoRange(ax) || !data) return; if(!ax._min) ax._min = []; if(!ax._max) ax._max = []; diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 342d5900b2e..7a761a00e36 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -400,30 +400,40 @@ module.exports = function setConvert(ax, fullLayout) { ax.makeCalcdata = function(trace, axLetter) { var arrayIn, arrayOut, i, len; - var cal = ax.type === 'date' && trace[axLetter + 'calendar']; + var axType = ax.type; + var cal = axType === 'date' && trace[axLetter + 'calendar']; if(axLetter in trace) { arrayIn = trace[axLetter]; len = trace._length || arrayIn.length; - arrayOut = new Array(len); + if(Lib.isTypedArray(arrayIn) && (axType === 'linear' || axType === 'log')) { + if(len === arrayIn.length) { + return arrayIn; + } else if(arrayIn.subarray) { + return arrayIn.subarray(0, len); + } + } + + arrayOut = new Array(len); for(i = 0; i < len; i++) { arrayOut[i] = ax.d2c(arrayIn[i], 0, cal); } } else { - var v0 = ((axLetter + '0') in trace) ? - ax.d2c(trace[axLetter + '0'], 0, cal) : 0, - dv = (trace['d' + axLetter]) ? - Number(trace['d' + axLetter]) : 1; + var v0 = ((axLetter + '0') in trace) ? ax.d2c(trace[axLetter + '0'], 0, cal) : 0; + var dv = (trace['d' + axLetter]) ? Number(trace['d' + axLetter]) : 1; // the opposing data, for size if we have x and dx etc arrayIn = trace[{x: 'y', y: 'x'}[axLetter]]; len = trace._length || arrayIn.length; arrayOut = new Array(len); - for(i = 0; i < len; i++) arrayOut[i] = v0 + i * dv; + for(i = 0; i < len; i++) { + arrayOut[i] = v0 + i * dv; + } } + return arrayOut; }; diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 8a4a2453cfb..2729b8d72f2 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -308,14 +308,14 @@ var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ]; function coordinateBound(axis, coord, len, d, bounds, calendar) { var x; - if(!Array.isArray(coord)) { + if(!Lib.isArrayOrTypedArray(coord)) { bounds[0][d] = Math.min(bounds[0][d], 0); bounds[1][d] = Math.max(bounds[1][d], len - 1); return; } for(var i = 0; i < (len || coord.length); ++i) { - if(Array.isArray(coord[i])) { + if(Lib.isArrayOrTypedArray(coord[i])) { for(var j = 0; j < coord[i].length; ++j) { x = axis.d2l(coord[i][j], 0, calendar); if(!isNaN(x) && isFinite(x)) { diff --git a/src/plots/mapbox/convert_text_opts.js b/src/plots/mapbox/convert_text_opts.js index 9d5678a3c00..a54e501f961 100644 --- a/src/plots/mapbox/convert_text_opts.js +++ b/src/plots/mapbox/convert_text_opts.js @@ -11,7 +11,6 @@ var Lib = require('../../lib'); - /** * Convert plotly.js 'textposition' to mapbox-gl 'anchor' and 'offset' * (with the help of the icon size). @@ -29,7 +28,7 @@ module.exports = function convertTextOpts(textposition, iconSize) { hPos = parts[1]; // ballpack values - var factor = Array.isArray(iconSize) ? Lib.mean(iconSize) : iconSize, + var factor = Lib.isArrayOrTypedArray(iconSize) ? Lib.mean(iconSize) : iconSize, xInc = 0.5 + (factor / 100), yInc = 1.5 + (factor / 100); diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 66dd30be3f8..423682eff04 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -10,6 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var Axes = require('../../plots/cartesian/axes'); var hasColorscale = require('../../components/colorscale/has_colorscale'); @@ -63,7 +64,7 @@ module.exports = function calc(gd, trace) { var base = trace.base, b; - if(Array.isArray(base)) { + if(isArrayOrTypedArray(base)) { for(i = 0; i < Math.min(base.length, cd.length); i++) { b = sa.d2c(base[i], 0, scalendar); if(isNumeric(b)) { diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index 83caa4de55b..b9a2fdf0746 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -10,6 +10,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var BADNUM = require('../../constants/numerical').BADNUM; var Registry = require('../../registry'); @@ -313,7 +314,7 @@ function applyAttributes(sieve) { initialPoffset = t.poffset, newPoffset; - if(Array.isArray(offset)) { + if(isArrayOrTypedArray(offset)) { // if offset is an array, then clone it into t.poffset. newPoffset = offset.slice(0, calcTrace.length); @@ -339,7 +340,7 @@ function applyAttributes(sieve) { var width = fullTrace.width, initialBarwidth = t.barwidth; - if(Array.isArray(width)) { + if(isArrayOrTypedArray(width)) { // if width is an array, then clone it into t.barwidth. var newBarwidth = width.slice(0, calcTrace.length); diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js index 688f2ba419f..9452bc3390a 100644 --- a/src/traces/box/calc.js +++ b/src/traces/box/calc.js @@ -220,7 +220,7 @@ function arraysToCalcdata(pt, trace, i) { } function calcSelection(cd, trace) { - if(Array.isArray(trace.selectedpoints)) { + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { for(var i = 0; i < cd.length; i++) { var pts = cd[i].pts || []; var ptNumber2cdIndex = {}; diff --git a/src/traces/carpet/array_minmax.js b/src/traces/carpet/array_minmax.js index 0a51b18ad5e..6500991299f 100644 --- a/src/traces/carpet/array_minmax.js +++ b/src/traces/carpet/array_minmax.js @@ -8,6 +8,8 @@ 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + module.exports = function(a) { return minMax(a, 0); }; @@ -16,7 +18,7 @@ function minMax(a, depth) { // Limit to ten dimensional datasets. This seems *exceedingly* unlikely to // ever cause problems or even be a concern. It's include strictly so that // circular arrays could never cause this to loop. - if(!Array.isArray(a) || depth >= 10) { + if(!isArrayOrTypedArray(a) || depth >= 10) { return null; } @@ -26,7 +28,7 @@ function minMax(a, depth) { for(var i = 0; i < n; i++) { var datum = a[i]; - if(Array.isArray(datum)) { + if(isArrayOrTypedArray(datum)) { var result = minMax(datum, depth + 1); if(result) { diff --git a/src/traces/carpet/axis_aligned_line.js b/src/traces/carpet/axis_aligned_line.js index 4255ba4d346..f25068fd14f 100644 --- a/src/traces/carpet/axis_aligned_line.js +++ b/src/traces/carpet/axis_aligned_line.js @@ -8,6 +8,8 @@ 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + /* This function retrns a set of control points that define a curve aligned along * either the a or b axis. Exactly one of a or b must be an array defining the range * spanned. @@ -19,7 +21,7 @@ module.exports = function(carpet, carpetcd, a, b) { var idx, tangent, tanIsoIdx, tanIsoPar, segment, refidx; var p0, p1, v0, v1, start, end, range; - var axis = Array.isArray(a) ? 'a' : 'b'; + var axis = isArrayOrTypedArray(a) ? 'a' : 'b'; var ax = axis === 'a' ? carpet.aaxis : carpet.baxis; var smoothing = ax.smoothing; var toIdx = axis === 'a' ? carpet.a2i : carpet.b2j; diff --git a/src/traces/carpet/cheater_basis.js b/src/traces/carpet/cheater_basis.js index 01f6f91b944..c136ab180fd 100644 --- a/src/traces/carpet/cheater_basis.js +++ b/src/traces/carpet/cheater_basis.js @@ -8,7 +8,7 @@ 'use strict'; -var isArray = require('../../lib').isArray; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; /* * Construct a 2D array of cheater values given a, b, and a slope. @@ -18,10 +18,10 @@ module.exports = function(a, b, cheaterslope) { var i, j, ascal, bscal, aval, bval; var data = []; - var na = isArray(a) ? a.length : a; - var nb = isArray(b) ? b.length : b; - var adata = isArray(a) ? a : null; - var bdata = isArray(b) ? b : null; + var na = isArrayOrTypedArray(a) ? a.length : a; + var nb = isArrayOrTypedArray(b) ? b.length : b; + var adata = isArrayOrTypedArray(a) ? a : null; + var bdata = isArrayOrTypedArray(b) ? b : null; // If we're using data, scale it so that for data that's just barely // not evenly spaced, the switch to value-based indexing is continuous. diff --git a/src/traces/carpet/has_columns.js b/src/traces/carpet/has_columns.js index de9b5510ebc..fad4812f044 100644 --- a/src/traces/carpet/has_columns.js +++ b/src/traces/carpet/has_columns.js @@ -6,9 +6,10 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + module.exports = function(data) { - return Array.isArray(data[0]); + return isArrayOrTypedArray(data[0]); }; diff --git a/src/traces/carpet/map_1d_array.js b/src/traces/carpet/map_1d_array.js index 1d5ef74252a..ca250334657 100644 --- a/src/traces/carpet/map_1d_array.js +++ b/src/traces/carpet/map_1d_array.js @@ -8,6 +8,8 @@ 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + /* * Map an array of x or y coordinates (c) to screen-space pixel coordinates (p). * The output array is optional, but if provided, it will be reused without @@ -16,7 +18,7 @@ module.exports = function mapArray(out, data, func) { var i; - if(!Array.isArray(out)) { + if(!isArrayOrTypedArray(out)) { // If not an array, make it an array: out = []; } else if(out.length > data.length) { diff --git a/src/traces/carpet/map_2d_array.js b/src/traces/carpet/map_2d_array.js index 76b397e77cb..172c1c6ab7f 100644 --- a/src/traces/carpet/map_2d_array.js +++ b/src/traces/carpet/map_2d_array.js @@ -8,6 +8,8 @@ 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + /* * Map an array of x or y coordinates (c) to screen-space pixel coordinates (p). * The output array is optional, but if provided, it will be reused without @@ -26,7 +28,7 @@ module.exports = function mapArray(out, data, func) { } for(i = 0; i < data.length; i++) { - if(!Array.isArray(out[i])) { + if(!isArrayOrTypedArray(out[i])) { // If not an array, make it an array: out[i] = []; } else if(out[i].length > data.length) { diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index 4e2250a4a03..650571cc4d6 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -29,7 +29,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var z = coerce('z'); - if(!Array.isArray(z)) { + if(!Lib.isArrayOrTypedArray(z)) { traceOut.visible = false; return; } diff --git a/src/traces/heatmap/has_columns.js b/src/traces/heatmap/has_columns.js index 21f38e1e747..cea3c5733ec 100644 --- a/src/traces/heatmap/has_columns.js +++ b/src/traces/heatmap/has_columns.js @@ -6,9 +6,10 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; + module.exports = function(trace) { - return !Array.isArray(trace.z[0]); + return !isArrayOrTypedArray(trace.z[0]); }; diff --git a/src/traces/heatmap/make_bound_array.js b/src/traces/heatmap/make_bound_array.js index 67f0c158bba..bf0e67b7a9d 100644 --- a/src/traces/heatmap/make_bound_array.js +++ b/src/traces/heatmap/make_bound_array.js @@ -9,6 +9,7 @@ 'use strict'; var Registry = require('../../registry'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) { var arrayOut = [], @@ -19,7 +20,7 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, dv, i; - var isArrayOfTwoItemsOrMore = Array.isArray(arrayIn) && arrayIn.length > 1; + var isArrayOfTwoItemsOrMore = isArrayOrTypedArray(arrayIn) && arrayIn.length > 1; if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) { var len = arrayIn.length; @@ -67,7 +68,7 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, var calendar = trace[ax._id.charAt(0) + 'calendar']; if(isHist || ax.type === 'category') v0 = ax.r2c(v0In, 0, calendar) || 0; - else if(Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0]; + else if(isArrayOrTypedArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0]; else if(v0In === undefined) v0 = 0; else v0 = ax.d2c(v0In, 0, calendar); diff --git a/src/traces/heatmap/xyz_defaults.js b/src/traces/heatmap/xyz_defaults.js index 7a98ee3a32d..1857768e4e5 100644 --- a/src/traces/heatmap/xyz_defaults.js +++ b/src/traces/heatmap/xyz_defaults.js @@ -10,11 +10,11 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var Registry = require('../../registry'); var hasColumns = require('./has_columns'); - module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, xName, yName) { var z = coerce('z'); xName = xName || 'x'; @@ -76,7 +76,7 @@ function isValidZ(z) { for(var i = 0; i < z.length; i++) { zi = z[i]; - if(!Array.isArray(zi)) { + if(!isArrayOrTypedArray(zi)) { allRowsAreArrays = false; break; } diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 42cfada1b73..c2d8caa33f6 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -72,7 +72,7 @@ module.exports = function calc(gd, trace) { var pr2c = function(v) { return pa.r2c(v, 0, calendar); }; var rawCounterData; - if(Array.isArray(trace[counterData]) && func !== 'count') { + if(Lib.isArrayOrTypedArray(trace[counterData]) && func !== 'count') { rawCounterData = trace[counterData]; isAvg = func === 'avg'; binFunc = binFunctions[func]; @@ -199,7 +199,7 @@ module.exports = function calc(gd, trace) { arraysToCalcdata(cd, trace); - if(Array.isArray(trace.selectedpoints)) { + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { Lib.tagSelected(cd, trace, ptNumber2cdIndex); } diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index 4049e5f8b5f..eff7a082dfa 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -24,7 +24,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var ret = array.map(function(attr) { var result = coerce(attr); - if(result && Array.isArray(result)) return result; + if(result && Lib.isArrayOrTypedArray(result)) return result; return null; }); diff --git a/src/traces/parcoords/calc.js b/src/traces/parcoords/calc.js index 702862132c7..e7bc16ac6bd 100644 --- a/src/traces/parcoords/calc.js +++ b/src/traces/parcoords/calc.js @@ -14,7 +14,7 @@ var Lib = require('../../lib'); var wrap = require('../../lib/gup').wrap; module.exports = function calc(gd, trace) { - var cs = !!trace.line.colorscale && Lib.isArray(trace.line.color); + var cs = !!trace.line.colorscale && Lib.isArrayOrTypedArray(trace.line.color); var color = cs ? trace.line.color : constHalf(trace._commonLength); var cscale = cs ? trace.line.colorscale : [[0, trace.line.color], [1, trace.line.color]]; diff --git a/src/traces/parcoords/defaults.js b/src/traces/parcoords/defaults.js index bbcdd63b475..fcd820efd89 100644 --- a/src/traces/parcoords/defaults.js +++ b/src/traces/parcoords/defaults.js @@ -16,10 +16,9 @@ var maxDimensionCount = require('./constants').maxDimensionCount; var handleDomainDefaults = require('../../plots/domain').defaults; function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - var lineColor = coerce('line.color', defaultColor); - if(hasColorscale(traceIn, 'line') && Lib.isArray(lineColor)) { + if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) { if(lineColor.length) { coerce('line.colorscale'); colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); diff --git a/src/traces/pie/calc.js b/src/traces/pie/calc.js index a8f4baaaa34..665c6758540 100644 --- a/src/traces/pie/calc.js +++ b/src/traces/pie/calc.js @@ -9,6 +9,7 @@ 'use strict'; var isNumeric = require('fast-isnumeric'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var tinycolor = require('tinycolor2'); var Color = require('../../components/color'); @@ -16,7 +17,7 @@ var helpers = require('./helpers'); module.exports = function calc(gd, trace) { var vals = trace.values; - var hasVals = Array.isArray(vals) && vals.length; + var hasVals = isArrayOrTypedArray(vals) && vals.length; var labels = trace.labels; var colors = trace.marker.colors || []; var cd = []; diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 9a1aa32551c..bfd94b9f169 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -22,7 +22,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var vals = coerce('values'); var labels = coerce('labels'); if(!Array.isArray(labels)) { - if(!Array.isArray(vals) || !vals.length) { + if(!Lib.isArrayOrTypedArray(vals) || !vals.length) { // must have at least one of vals or labels traceOut.visible = false; return; diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js index 9b3e07e0390..cdef0ee4408 100644 --- a/src/traces/sankey/render.js +++ b/src/traces/sankey/render.js @@ -87,7 +87,7 @@ function sankeyModel(layout, d, i) { return { pointNumber: i, label: l, - color: Lib.isArray(nodeSpec.color) ? nodeSpec.color[i] : nodeSpec.color + color: Lib.isArrayOrTypedArray(nodeSpec.color) ? nodeSpec.color[i] : nodeSpec.color }; }); @@ -95,7 +95,7 @@ function sankeyModel(layout, d, i) { return { pointNumber: i, label: linkSpec.label[i], - color: Lib.isArray(linkSpec.color) ? linkSpec.color[i] : linkSpec.color, + color: Lib.isArrayOrTypedArray(linkSpec.color) ? linkSpec.color[i] : linkSpec.color, source: linkSpec.source[i], target: linkSpec.target[i], value: d diff --git a/src/traces/scatter/calc.js b/src/traces/scatter/calc.js index c720b0fcf02..f551f3f0249 100644 --- a/src/traces/scatter/calc.js +++ b/src/traces/scatter/calc.js @@ -6,10 +6,10 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var isNumeric = require('fast-isnumeric'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var Axes = require('../../plots/cartesian/axes'); var BADNUM = require('../../constants/numerical').BADNUM; @@ -25,6 +25,31 @@ function calc(gd, trace) { var x = xa.makeCalcdata(trace, 'x'); var y = ya.makeCalcdata(trace, 'y'); var serieslen = trace._length; + var cd = new Array(serieslen); + + var ppad = calcMarkerSize(trace, serieslen); + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + + for(var i = 0; i < serieslen; i++) { + cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? + {x: x[i], y: y[i]} : + {x: BADNUM, y: BADNUM}; + + if(trace.ids) { + cd[i].id = String(trace.ids[i]); + } + } + + arraysToCalcdata(cd, trace); + calcColorscale(trace); + calcSelection(cd, trace); + + gd.firstscatter = false; + return cd; +} + +function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) { + var serieslen = trace._length; // cancel minimum tick spacings (only applies to bars and boxes) xa._minDtick = 0; @@ -35,8 +60,9 @@ function calc(gd, trace) { var xOptions = {padded: true}; var yOptions = {padded: true}; - var ppad = calcMarkerSize(trace, serieslen); - if(ppad) xOptions.ppad = yOptions.ppad = ppad; + if(ppad) { + xOptions.ppad = yOptions.ppad = ppad; + } // TODO: text size @@ -72,24 +98,6 @@ function calc(gd, trace) { Axes.expand(xa, x, xOptions); Axes.expand(ya, y, yOptions); - - // create the "calculated data" to plot - var cd = new Array(serieslen); - for(var i = 0; i < serieslen; i++) { - cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? - {x: x[i], y: y[i]} : {x: BADNUM, y: BADNUM}; - - if(trace.ids) { - cd[i].id = String(trace.ids[i]); - } - } - - arraysToCalcdata(cd, trace); - calcColorscale(trace); - calcSelection(cd, trace); - - gd.firstscatter = false; - return cd; } function calcMarkerSize(trace, serieslen) { @@ -111,7 +119,7 @@ function calcMarkerSize(trace, serieslen) { }; } - if(Array.isArray(marker.size)) { + if(isArrayOrTypedArray(marker.size)) { // I tried auto-type but category and dates dont make much sense. var ax = {type: 'linear'}; Axes.setConvert(ax); @@ -131,5 +139,6 @@ function calcMarkerSize(trace, serieslen) { module.exports = { calc: calc, - calcMarkerSize: calcMarkerSize + calcMarkerSize: calcMarkerSize, + calcAxisExpansion: calcAxisExpansion }; diff --git a/src/traces/scatter/calc_selection.js b/src/traces/scatter/calc_selection.js index cde4b95ffae..4d1cec0a6f7 100644 --- a/src/traces/scatter/calc_selection.js +++ b/src/traces/scatter/calc_selection.js @@ -11,7 +11,7 @@ var Lib = require('../../lib'); module.exports = function calcSelection(cd, trace) { - if(Array.isArray(trace.selectedpoints)) { + if(Lib.isArrayOrTypedArray(trace.selectedpoints)) { Lib.tagSelected(cd, trace); } }; diff --git a/src/traces/scatter/fillcolor_defaults.js b/src/traces/scatter/fillcolor_defaults.js index d6e199805e0..baf2b6e37ea 100644 --- a/src/traces/scatter/fillcolor_defaults.js +++ b/src/traces/scatter/fillcolor_defaults.js @@ -10,7 +10,7 @@ 'use strict'; var Color = require('../../components/color'); - +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) { var inheritColorFromMarker = false; @@ -20,10 +20,10 @@ module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coe var markerColor = traceOut.marker.color, markerLineColor = (traceOut.marker.line || {}).color; - if(markerColor && !Array.isArray(markerColor)) { + if(markerColor && !isArrayOrTypedArray(markerColor)) { inheritColorFromMarker = markerColor; } - else if(markerLineColor && !Array.isArray(markerLineColor)) { + else if(markerLineColor && !isArrayOrTypedArray(markerLineColor)) { inheritColorFromMarker = markerLineColor; } } diff --git a/src/traces/scatter/line_defaults.js b/src/traces/scatter/line_defaults.js index 10dabd85bd0..27125ea8c07 100644 --- a/src/traces/scatter/line_defaults.js +++ b/src/traces/scatter/line_defaults.js @@ -6,13 +6,12 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var hasColorscale = require('../../components/colorscale/has_colorscale'); var colorscaleDefaults = require('../../components/colorscale/defaults'); - module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) { var markerColor = (traceIn.marker || {}).color; @@ -22,7 +21,7 @@ module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); } else { - var lineColorDflt = (Array.isArray(markerColor) ? false : markerColor) || defaultColor; + var lineColorDflt = (isArrayOrTypedArray(markerColor) ? false : markerColor) || defaultColor; coerce('line.color', lineColorDflt); } diff --git a/src/traces/scatter/subtypes.js b/src/traces/scatter/subtypes.js index f1dfa84bd17..ddd32ad8547 100644 --- a/src/traces/scatter/subtypes.js +++ b/src/traces/scatter/subtypes.js @@ -29,6 +29,6 @@ module.exports = { isBubble: function(trace) { return Lib.isPlainObject(trace.marker) && - Array.isArray(trace.marker.size); + Lib.isArrayOrTypedArray(trace.marker.size); } }; diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 790768079df..e35150bbe07 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -152,7 +152,7 @@ function calculateSymbol(symbolIn) { function formatParam(paramIn, len, calculate, dflt, extraFn) { var paramOut = null; - if(Array.isArray(paramIn)) { + if(Lib.isArrayOrTypedArray(paramIn)) { paramOut = []; for(var i = 0; i < len; i++) { diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c5b512c1b03..80af61197b3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -8,29 +8,32 @@ 'use strict'; -var Lib = require('../../lib'); -var getTraceColor = require('../scatter/get_trace_color'); -var ErrorBars = require('../../components/errorbars'); -var extend = require('object-assign'); -var Axes = require('../../plots/cartesian/axes'); -var kdtree = require('kdgrass'); -var subTypes = require('../scatter/subtypes'); -var calcColorscales = require('../scatter/colorscale_calc'); -var Drawing = require('../../components/drawing'); -var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); -var DASHES = require('../../constants/gl2d_dashes'); -var formatColor = require('../../lib/gl_format_color'); -var linkTraces = require('../scatter/link_traces'); +var createRegl = require('regl'); var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var createError = require('regl-error2d'); +var kdtree = require('kdgrass'); var rgba = require('color-normalize'); var svgSdf = require('svg-path-sdf'); -var createRegl = require('regl'); var arrayRange = require('array-range'); + +var Lib = require('../../lib'); +var Axes = require('../../plots/cartesian/axes'); +var Drawing = require('../../components/drawing'); +var ErrorBars = require('../../components/errorbars'); +var formatColor = require('../../lib/gl_format_color'); + +var subTypes = require('../scatter/subtypes'); +var calcMarkerSize = require('../scatter/calc').calcMarkerSize; +var calcAxisExpansion = require('../scatter/calc').calcAxisExpansion; +var calcColorscales = require('../scatter/colorscale_calc'); +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); +var linkTraces = require('../scatter/link_traces'); +var getTraceColor = require('../scatter/get_trace_color'); var fillHoverText = require('../scatter/fill_hover_text'); -var isNumeric = require('fast-isnumeric'); +var DASHES = require('../../constants/gl2d_dashes'); +var BADNUM = require('../../constants/numerical').BADNUM; var SYMBOL_SDF_SIZE = 200; var SYMBOL_SIZE = 20; var SYMBOL_STROKE = SYMBOL_SIZE / 20; @@ -38,123 +41,77 @@ var SYMBOL_SDF = {}; var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var TOO_MANY_POINTS = 1e5; var DOT_RE = /-dot/; - -function calc(container, trace) { - var layout = container._fullLayout; - var positions; +var OPEN_RE = /-open/; + +function calc(gd, trace) { + var fullLayout = gd._fullLayout; + var xa = Axes.getFromId(gd, trace.xaxis); + var ya = Axes.getFromId(gd, trace.yaxis); + var subplot = fullLayout._plots[trace.xaxis + trace.yaxis]; + var count = trace._length; + var count2 = count * 2; var stash = {}; - var xaxis = Axes.getFromId(container, trace.xaxis); - var yaxis = Axes.getFromId(container, trace.yaxis); + var i, xx, yy; - var subplot = layout._plots[trace.xaxis + trace.yaxis]; + var x = xa.makeCalcdata(trace, 'x'); + var y = ya.makeCalcdata(trace, 'y'); - var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x'); - var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); - - var count = trace._length, i, xx, yy; + // we need hi-precision for scatter2d, + // regl-scatter2d uses NaNs for bad/missing values + var positions = new Array(count2); + for(i = 0; i < count; i++) { + xx = x[i]; + yy = y[i]; + positions[i * 2] = xx === BADNUM ? NaN : xx; + positions[i * 2 + 1] = yy === BADNUM ? NaN : yy; + } - if(!x) { - x = Array(count); - for(i = 0; i < count; i++) { - x[i] = i; + if(xa.type === 'log') { + for(i = 0; i < count2; i += 2) { + positions[i] = xa.c2l(positions[i]); } } - if(!y) { - y = Array(count); - for(i = 0; i < count; i++) { - y[i] = i; + if(ya.type === 'log') { + for(i = 1; i < count2; i += 2) { + positions[i] = ya.c2l(positions[i]); } } - // get log converted positions - var rawx = (xaxis.type === 'log' || x.length > count) ? x.slice(0, count) : x; - var rawy = (yaxis.type === 'log' || y.length > count) ? y.slice(0, count) : y; - - var convertX = (xaxis.type === 'log') ? xaxis.d2l : parseFloat; - var convertY = (yaxis.type === 'log') ? yaxis.d2l : parseFloat; - - // we need hi-precision for scatter2d - positions = new Array(count * 2); - - for(i = 0; i < count; i++) { - x[i] = convertX(x[i]); - y[i] = convertY(y[i]); - - // if no x defined, we are creating simple int sequence (API) - // we use parseFloat because it gives NaN (we need that for empty values to avoid drawing lines) and it is incredibly fast - xx = isNumeric(x[i]) ? +x[i] : NaN; - yy = isNumeric(y[i]) ? +y[i] : NaN; - - positions[i * 2] = xx; - positions[i * 2 + 1] = yy; - } - // we don't build a tree for log axes since it takes long to convert log2px // and it is also - if(xaxis.type !== 'log' && yaxis.type !== 'log') { + if(xa.type !== 'log' && ya.type !== 'log') { // FIXME: delegate this to webworker stash.tree = kdtree(positions, 512); - } - else { - var ids = stash.ids = Array(count); + } else { + var ids = stash.ids = new Array(count); for(i = 0; i < count; i++) { ids[i] = i; } } + // create scene options and scene calcColorscales(trace); - - var options = sceneOptions(container, subplot, trace, positions); - - // expanding axes is separate from options - if(!options.markers) { - Axes.expand(xaxis, rawx, { padded: true }); - Axes.expand(yaxis, rawy, { padded: true }); - } - else if(Array.isArray(options.markers.sizes)) { - var sizes = options.markers.sizes; - Axes.expand(xaxis, rawx, { padded: true, ppad: sizes }); - Axes.expand(yaxis, rawy, { padded: true, ppad: sizes }); - } - else { - var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; - var size = options.markers.size; - - // axes bounds - for(i = 0; i < count; i++) { - xx = x[i], yy = y[i]; - if(xbounds[0] > xx) xbounds[0] = xx; - if(xbounds[1] < xx) xbounds[1] = xx; - if(ybounds[0] > yy) ybounds[0] = yy; - if(ybounds[1] < yy) ybounds[1] = yy; - } - - // FIXME: is there a better way to separate expansion? - if(count < TOO_MANY_POINTS) { - Axes.expand(xaxis, rawx, { padded: true, ppad: size }); - Axes.expand(yaxis, rawy, { padded: true, ppad: size }); - } - // update axes fast for big number of points - else { - if(xaxis._min) { - xaxis._min.push({ val: xbounds[0], pad: size }); - } - if(xaxis._max) { - xaxis._max.push({ val: xbounds[1], pad: size }); - } - - if(yaxis._min) { - yaxis._min.push({ val: ybounds[0], pad: size }); - } - if(yaxis._max) { - yaxis._max.push({ val: ybounds[1], pad: size }); - } + var options = sceneOptions(gd, subplot, trace, positions); + var markerOptions = options.marker; + var scene = sceneUpdate(gd, subplot); + var ppad; + + // Re-use SVG scatter axis expansion routine except + // for graph with very large number of points where it + // performs poorly. + // In big data case, fake Axes.expand outputs with data bounds, + // and an average size for array marker.size inputs. + if(count < TOO_MANY_POINTS) { + ppad = calcMarkerSize(trace, count); + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + } else { + if(markerOptions) { + ppad = 2 * (markerOptions.sizeAvg || Math.max(markerOptions.size, 3)); } + fastAxisExpand(xa, x, ppad); + fastAxisExpand(ya, y, ppad); } - // create scene - var scene = sceneUpdate(container, subplot); - // set flags to create scene renderers if(options.fill && !scene.fill2d) scene.fill2d = true; if(options.marker && !scene.scatter2d) scene.scatter2d = true; @@ -176,22 +133,43 @@ function calc(container, trace) { stash.index = scene.count - 1; stash.x = x; stash.y = y; - stash.rawx = rawx; - stash.rawy = rawy; stash.positions = positions; stash.count = count; + gd.firstscatter = false; return [{x: false, y: false, t: stash, trace: trace}]; } +// Approximate Axes.expand results with speed +function fastAxisExpand(ax, vals, ppad) { + if(!Axes.doesAxisNeedAutoRange(ax) || !vals) return; + + var b0 = Infinity; + var b1 = -Infinity; + + for(var i = 0; i < vals.length; i += 2) { + var v = vals[i]; + if(v < b0) b0 = v; + if(v > b1) b1 = v; + } + + if(ax._min) ax._min = []; + ax._min.push({val: b0, pad: ppad}); + + if(ax._max) ax._max = []; + ax._max.push({val: b1, pad: ppad}); +} + // create scene options -function sceneOptions(container, subplot, trace, positions) { - var layout = container._fullLayout; +function sceneOptions(gd, subplot, trace, positions) { + var fullLayout = gd._fullLayout; var count = positions.length / 2; var markerOpts = trace.marker; - var i, ptrX = 0, ptrY = 0; - var xaxis = Axes.getFromId(container, trace.xaxis); - var yaxis = Axes.getFromId(container, trace.yaxis); + var xaxis = Axes.getFromId(gd, trace.xaxis); + var yaxis = Axes.getFromId(gd, trace.yaxis); + var ptrX = 0; + var ptrY = 0; + var i; var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; @@ -201,8 +179,7 @@ function sceneOptions(container, subplot, trace, positions) { hasErrorY = false; hasMarkers = false; hasFill = false; - } - else { + } else { hasLines = subTypes.hasLines(trace) && positions.length > 1; hasErrorX = trace.error_x && trace.error_x.visible === true; hasErrorY = trace.error_y && trace.error_y.visible === true; @@ -211,11 +188,13 @@ function sceneOptions(container, subplot, trace, positions) { hasFill = !!trace.fill && trace.fill !== 'none'; } - var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions, selectedOptions, unselectedOptions; + var lineOptions, markerOptions, fillOptions; + var errorXOptions, errorYOptions; + var selectedOptions, unselectedOptions; var linePositions; // get error values - var errorVals = hasError ? ErrorBars.calcFromTrace(trace, layout) : null; + var errorVals = hasError ? ErrorBars.calcFromTrace(trace, fullLayout) : null; if(hasErrorX) { errorXOptions = {}; @@ -337,7 +316,9 @@ function sceneOptions(container, subplot, trace, positions) { break; } } - lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : hasMarkers ? 'rect' : 'round'; + + lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : + hasMarkers ? 'rect' : 'round'; // fill gaps if(hasNaN && trace.connectgaps) { @@ -368,7 +349,6 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions = makeMarkerOptions(markerOpts); selectedOptions = makeSelectedOptions(trace.selected, markerOpts); unselectedOptions = makeSelectedOptions(trace.unselected, markerOpts); - markerOptions.positions = positions; } @@ -378,7 +358,7 @@ function sceneOptions(container, subplot, trace, positions) { if(!selected) return options; if(selected.marker && selected.marker.symbol) { - options = makeMarkerOptions(extend({}, markerOpts, selected.marker)); + options = makeMarkerOptions(Lib.extendFlat({}, markerOpts, selected.marker)); } // shortcut simple selection logic @@ -393,17 +373,21 @@ function sceneOptions(container, subplot, trace, positions) { } function makeMarkerOptions(markerOpts) { - var markerOptions = {}, i; + var markerOptions = {}; + var i; - // get basic symbol info - var multiMarker = Array.isArray(markerOpts.symbol); - var isOpen, symbol; - if(!multiMarker) { - isOpen = /-open/.test(markerOpts.symbol); - } + var multiSymbol = Array.isArray(markerOpts.symbol); + var multiColor = Lib.isArrayOrTypedArray(markerOpts.color); + var multiLineColor = Lib.isArrayOrTypedArray(markerOpts.line.color); + var multiOpacity = Lib.isArrayOrTypedArray(markerOpts.opacity); + var multiSize = Lib.isArrayOrTypedArray(markerOpts.size); + var multiLineWidth = Lib.isArrayOrTypedArray(markerOpts.line.width); + + var isOpen; + if(!multiSymbol) isOpen = OPEN_RE.test(markerOpts.symbol); // prepare colors - if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line) || Array.isArray(markerOpts.opacity)) { + if(multiSymbol || multiColor || multiLineColor || multiOpacity) { markerOptions.colors = new Array(count); markerOptions.borderColors = new Array(count); var colors = formatColor(markerOpts, markerOpts.opacity, count); @@ -428,9 +412,9 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.borderColors = borderColors; for(i = 0; i < count; i++) { - if(multiMarker) { - symbol = markerOpts.symbol[i]; - isOpen = /-open/.test(symbol); + if(multiSymbol) { + var symbol = markerOpts.symbol[i]; + isOpen = OPEN_RE.test(symbol); } if(isOpen) { borderColors[i] = colors[i].slice(); @@ -446,8 +430,7 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.color = rgba(markerOpts.color, 'uint8'); markerOptions.color[3] = 0; markerOptions.borderColor = rgba(markerOpts.color, 'uint8'); - } - else { + } else { markerOptions.color = rgba(markerOpts.color, 'uint8'); markerOptions.borderColor = rgba(markerOpts.line.color, 'uint8'); } @@ -455,53 +438,54 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.opacity = trace.opacity * markerOpts.opacity; } - // prepare markers - if(Array.isArray(markerOpts.symbol)) { + // prepare symbols + if(multiSymbol) { markerOptions.markers = new Array(count); - for(i = 0; i < count; ++i) { + for(i = 0; i < count; i++) { markerOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); } - } - else { + } else { markerOptions.marker = getSymbolSdf(markerOpts.symbol); } - // prepare sizes and expand axes - var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + // prepare sizes var markerSizeFunc = makeBubbleSizeFn(trace); - var size, sizes; + var s; - if(multiSize) { - sizes = markerOptions.sizes = new Array(count); + if(multiSize || multiLineWidth) { + var sizes = markerOptions.sizes = new Array(count); var borderSizes = markerOptions.borderSizes = new Array(count); + var sizeTotal = 0; + var sizeAvg; - if(Array.isArray(markerOpts.size)) { - for(i = 0; i < count; ++i) { + if(multiSize) { + for(i = 0; i < count; i++) { sizes[i] = markerSizeFunc(markerOpts.size[i]); + sizeTotal += sizes[i]; } - } - else { - size = markerSizeFunc(markerOpts.size); - for(i = 0; i < count; ++i) { - sizes[i] = size; + sizeAvg = sizeTotal / count; + } else { + s = markerSizeFunc(markerOpts.size); + for(i = 0; i < count; i++) { + sizes[i] = s; } } // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - if(Array.isArray(markerOpts.line.width)) { - for(i = 0; i < count; ++i) { + if(multiLineWidth) { + for(i = 0; i < count; i++) { borderSizes[i] = markerSizeFunc(markerOpts.line.width[i]); } - } - else { - size = markerSizeFunc(markerOpts.line.width); - for(i = 0; i < count; ++i) { - borderSizes[i] = size; + } else { + s = markerSizeFunc(markerOpts.line.width); + for(i = 0; i < count; i++) { + borderSizes[i] = s; } } - } - else { - size = markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + + markerOptions.sizeAvg = sizeAvg; + } else { + markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); markerOptions.borderSizes = markerSizeFunc(markerOpts.line.width); } @@ -520,9 +504,9 @@ function sceneOptions(container, subplot, trace, positions) { } // make sure scene exists on subplot, return it -function sceneUpdate(container, subplot) { +function sceneUpdate(gd, subplot) { var scene = subplot._scene; - var layout = container._fullLayout; + var fullLayout = gd._fullLayout; if(!subplot._scene) { scene = subplot._scene = { @@ -599,9 +583,12 @@ function sceneUpdate(container, subplot) { // make sure canvas is clear scene.clear = function clear() { - var vpSize = layout._size, width = layout.width, height = layout.height, vp, gl, regl; + var vpSize = fullLayout._size; + var width = fullLayout.width; + var height = fullLayout.height; var xaxis = subplot.xaxis; var yaxis = subplot.yaxis; + var vp, gl, regl; // multisubplot case if(xaxis && xaxis.domain && yaxis && yaxis.domain) { @@ -720,20 +707,22 @@ function getSymbolSdf(symbol) { return symbolSdf || null; } -function plot(container, subplot, cdata) { +function plot(gd, subplot, cdata) { if(!cdata.length) return; - var layout = container._fullLayout; + var fullLayout = gd._fullLayout; var scene = cdata[0][0].t.scene; - var dragmode = layout.dragmode; + var dragmode = fullLayout.dragmode; // we may have more subplots than initialized data due to Axes.getSubplots method if(!scene) return; - var vpSize = layout._size, width = layout.width, height = layout.height; + var vpSize = fullLayout._size; + var width = fullLayout.width; + var height = fullLayout.height; // make sure proper regl instances are created - layout._glcanvas.each(function(d) { + fullLayout._glcanvas.each(function(d) { if(d.regl || d.pick) return; d.regl = createRegl({ canvas: this, @@ -742,14 +731,14 @@ function plot(container, subplot, cdata) { preserveDrawingBuffer: true }, extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], - pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio }); }); - var regl = layout._glcanvas.data()[0].regl; + var regl = fullLayout._glcanvas.data()[0].regl; // that is needed for fills - linkTraces(container, subplot, cdata); + linkTraces(gd, subplot, cdata); if(scene.dirty) { // make sure scenes are created @@ -878,11 +867,11 @@ function plot(container, subplot, cdata) { var trace = cd.trace; var stash = cd.t; var id = stash.index; - var x = stash.rawx, - y = stash.rawy; + var x = stash.x; + var y = stash.y; - var xaxis = subplot.xaxis || Axes.getFromId(container, trace.xaxis || 'x'); - var yaxis = subplot.yaxis || Axes.getFromId(container, trace.yaxis || 'y'); + var xaxis = subplot.xaxis || Axes.getFromId(gd, trace.xaxis || 'x'); + var yaxis = subplot.yaxis || Axes.getFromId(gd, trace.yaxis || 'y'); var i; var range = [ @@ -922,7 +911,8 @@ function plot(container, subplot, cdata) { } // precalculate px coords since we are not going to pan during select - var xpx = Array(stash.count), ypx = Array(stash.count); + var xpx = new Array(stash.count); + var ypx = new Array(stash.count); for(i = 0; i < stash.count; i++) { xpx[i] = xaxis.c2p(x[i]); ypx[i] = yaxis.c2p(y[i]); @@ -944,7 +934,7 @@ function plot(container, subplot, cdata) { // create select2d if(!scene.select2d) { // create scatter instance by cloning scatter2d - scene.select2d = createScatter(layout._glcanvas.data()[1].regl, {clone: scene.scatter2d}); + scene.select2d = createScatter(fullLayout._glcanvas.data()[1].regl, {clone: scene.scatter2d}); } if(scene.scatter2d && scene.selectBatch && scene.selectBatch.length) { @@ -988,8 +978,8 @@ function hoverPoints(pointData, xval, yval, hovermode) { var trace = cd[0].trace; var xa = pointData.xa; var ya = pointData.ya; - var x = stash.rawx; - var y = stash.rawy; + var x = stash.x; + var y = stash.y; var xpx = xa.c2p(xval); var ypx = ya.c2p(yval); var maxDistance = pointData.distance; @@ -1078,16 +1068,16 @@ function hoverPoints(pointData, xval, yval, hovermode) { var marker = trace.marker; if(marker) { - di.ms = Array.isArray(marker.size) ? marker.size[id] : marker.size; - di.mo = Array.isArray(marker.opacity) ? marker.opacity[id] : marker.opacity; + di.ms = Lib.isArrayOrTypedArray(marker.size) ? marker.size[id] : marker.size; + di.mo = Lib.isArrayOrTypedArray(marker.opacity) ? marker.opacity[id] : marker.opacity; di.mx = Array.isArray(marker.symbol) ? marker.symbol[id] : marker.symbol; - di.mc = Array.isArray(marker.color) ? marker.color[id] : marker.color; + di.mc = Lib.isArrayOrTypedArray(marker.color) ? marker.color[id] : marker.color; } var line = marker && marker.line; if(line) { di.mlc = Array.isArray(line.color) ? line.color[id] : line.color; - di.mlw = Array.isArray(line.width) ? line.width[id] : line.width; + di.mlw = Lib.isArrayOrTypedArray(line.width) ? line.width[id] : line.width; } var grad = marker && marker.gradient; @@ -1096,9 +1086,9 @@ function hoverPoints(pointData, xval, yval, hovermode) { di.mgc = Array.isArray(grad.color) ? grad.color[id] : grad.color; } - var xc = xa.c2p(di.x, true), - yc = ya.c2p(di.y, true), - rad = di.mrc || 1; + var xp = xa.c2p(di.x, true); + var yp = ya.c2p(di.y, true); + var rad = di.mrc || 1; var hoverlabel = trace.hoverlabel; @@ -1121,12 +1111,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { Lib.extendFlat(pointData, { color: getTraceColor(trace, di), - x0: xc - rad, - x1: xc + rad, + x0: xp - rad, + x1: xp + rad, xLabelVal: di.x, - y0: yc - rad, - y1: yc + rad, + y0: yp - rad, + y1: yp + rad, yLabelVal: di.y, cd: fakeCd, @@ -1145,15 +1135,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { } function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd, - selection = [], - trace = cd[0].trace, - stash = cd[0].t, - x = stash.x, - y = stash.y, - rawx = stash.rawx, - rawy = stash.rawy; - + var cd = searchInfo.cd; + var selection = []; + var trace = cd[0].trace; + var stash = cd[0].t; + var x = stash.x; + var y = stash.y; var scene = stash.scene; if(!scene) return selection; @@ -1163,7 +1150,9 @@ function selectPoints(searchInfo, polygon) { // degenerate polygon does not enable selection // filter out points by visible scatter ones - var els = null, unels = null, i; + var els = null; + var unels = null; + var i; if(polygon !== false && !polygon.degenerate) { els = [], unels = []; for(i = 0; i < stash.count; i++) { @@ -1171,16 +1160,15 @@ function selectPoints(searchInfo, polygon) { els.push(i); selection.push({ pointNumber: i, - x: rawx ? rawx[i] : x[i], - y: rawy ? rawy[i] : y[i] + x: x[i], + y: y[i] }); } else { unels.push(i); } } - } - else { + } else { unels = arrayRange(stash.count); } diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 34439328ed2..3cbe1884488 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -145,9 +145,9 @@ function makeCircleOpts(calcTrace) { var trace = calcTrace[0].trace; var marker = trace.marker; var selectedpoints = trace.selectedpoints; - var arrayColor = Array.isArray(marker.color); - var arraySize = Array.isArray(marker.size); - var arrayOpacity = Array.isArray(marker.opacity); + var arrayColor = Lib.isArrayOrTypedArray(marker.color); + var arraySize = Lib.isArrayOrTypedArray(marker.size); + var arrayOpacity = Lib.isArrayOrTypedArray(marker.opacity); var i; function addTraceOpacity(o) { return trace.opacity * o; } @@ -279,7 +279,7 @@ function makeSymbolGeoJSON(calcTrace) { } function getFillFunc(attr) { - if(Array.isArray(attr)) { + if(Lib.isArrayOrTypedArray(attr)) { return function(v) { return v; }; } else if(attr) { diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index c748d804df4..d64e1c92476 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var Lib = require('../../lib'); @@ -48,8 +47,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout marker.line = {width: 0}; if(marker.symbol !== 'circle') { - if(Array.isArray(marker.size)) marker.size = marker.size[0]; - if(Array.isArray(marker.color)) marker.color = marker.color[0]; + if(Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0]; + if(Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0]; } } diff --git a/src/traces/surface/convert.js b/src/traces/surface/convert.js index 3c1650b3d08..b21874fa710 100644 --- a/src/traces/surface/convert.js +++ b/src/traces/surface/convert.js @@ -16,6 +16,7 @@ var fill = require('ndarray-fill'); var ops = require('ndarray-ops'); var tinycolor = require('tinycolor2'); +var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; var str2RgbaArray = require('../../lib/str2rgbarray'); var MIN_RESOLUTION = 128; @@ -45,17 +46,17 @@ proto.handlePick = function(selection) { ]; var traceCoordinate = [0, 0, 0]; - if(!Array.isArray(this.data.x)) { + if(!isArrayOrTypedArray(this.data.x)) { traceCoordinate[0] = selectIndex[0]; - } else if(Array.isArray(this.data.x[0])) { + } else if(isArrayOrTypedArray(this.data.x[0])) { traceCoordinate[0] = this.data.x[selectIndex[1]][selectIndex[0]]; } else { traceCoordinate[0] = this.data.x[selectIndex[0]]; } - if(!Array.isArray(this.data.y)) { + if(!isArrayOrTypedArray(this.data.y)) { traceCoordinate[1] = selectIndex[1]; - } else if(Array.isArray(this.data.y[0])) { + } else if(isArrayOrTypedArray(this.data.y[0])) { traceCoordinate[1] = this.data.y[selectIndex[1]][selectIndex[0]]; } else { traceCoordinate[1] = this.data.y[selectIndex[1]]; @@ -231,11 +232,11 @@ proto.update = function(data) { }); // coords x - if(!Array.isArray(x)) { + if(!isArrayOrTypedArray(x)) { fill(xc, function(row) { return xaxis.d2l(row, 0, xcalendar) * scaleFactor[0]; }); - } else if(Array.isArray(x[0])) { + } else if(isArrayOrTypedArray(x[0])) { fill(xc, function(row, col) { return xaxis.d2l(x[col][row], 0, xcalendar) * scaleFactor[0]; }); @@ -247,11 +248,11 @@ proto.update = function(data) { } // coords y - if(!Array.isArray(x)) { + if(!isArrayOrTypedArray(x)) { fill(yc, function(row, col) { return yaxis.d2l(col, 0, xcalendar) * scaleFactor[1]; }); - } else if(Array.isArray(y[0])) { + } else if(isArrayOrTypedArray(y[0])) { fill(yc, function(row, col) { return yaxis.d2l(y[col][row], 0, ycalendar) * scaleFactor[1]; }); diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index e0a41136038..df78f075cc8 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -32,7 +32,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var x = coerce('x'); coerce('y'); - traceOut._xlength = (Array.isArray(x) && Array.isArray(x[0])) ? z.length : z[0].length; + traceOut._xlength = (Array.isArray(x) && Lib.isArrayOrTypedArray(x[0])) ? z.length : z[0].length; traceOut._ylength = z.length; var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); diff --git a/test/image/baselines/gl2d_10.png b/test/image/baselines/gl2d_10.png index b3d8ce18148..85bcc6a1a3a 100644 Binary files a/test/image/baselines/gl2d_10.png and b/test/image/baselines/gl2d_10.png differ diff --git a/test/image/baselines/gl2d_12.png b/test/image/baselines/gl2d_12.png index 9f35f0bb4fe..e0e6c389512 100644 Binary files a/test/image/baselines/gl2d_12.png and b/test/image/baselines/gl2d_12.png differ diff --git a/test/image/baselines/gl2d_14.png b/test/image/baselines/gl2d_14.png index 9a4ae8542e8..d90ff1e2468 100644 Binary files a/test/image/baselines/gl2d_14.png and b/test/image/baselines/gl2d_14.png differ diff --git a/test/image/baselines/gl2d_17.png b/test/image/baselines/gl2d_17.png index 85a564fd72c..7eeb7dea3a8 100644 Binary files a/test/image/baselines/gl2d_17.png and b/test/image/baselines/gl2d_17.png differ diff --git a/test/image/baselines/gl2d_axes_booleans.png b/test/image/baselines/gl2d_axes_booleans.png index e0350f5c8de..7e523efca3b 100644 Binary files a/test/image/baselines/gl2d_axes_booleans.png and b/test/image/baselines/gl2d_axes_booleans.png differ diff --git a/test/image/baselines/gl2d_axes_labels.png b/test/image/baselines/gl2d_axes_labels.png index 5cf474017f0..decf839b993 100644 Binary files a/test/image/baselines/gl2d_axes_labels.png and b/test/image/baselines/gl2d_axes_labels.png differ diff --git a/test/image/baselines/gl2d_axes_labels2.png b/test/image/baselines/gl2d_axes_labels2.png index 07d41bf98a3..0df05344ea9 100644 Binary files a/test/image/baselines/gl2d_axes_labels2.png and b/test/image/baselines/gl2d_axes_labels2.png differ diff --git a/test/image/baselines/gl2d_axes_lines.png b/test/image/baselines/gl2d_axes_lines.png index 6d2711ff435..de668a0dc13 100644 Binary files a/test/image/baselines/gl2d_axes_lines.png and b/test/image/baselines/gl2d_axes_lines.png differ diff --git a/test/image/baselines/gl2d_axes_range_mode.png b/test/image/baselines/gl2d_axes_range_mode.png index 22f6212f49c..98eca06f700 100644 Binary files a/test/image/baselines/gl2d_axes_range_mode.png and b/test/image/baselines/gl2d_axes_range_mode.png differ diff --git a/test/image/baselines/gl2d_axes_range_type.png b/test/image/baselines/gl2d_axes_range_type.png index ba6739c52cc..5e02124eb9a 100644 Binary files a/test/image/baselines/gl2d_axes_range_type.png and b/test/image/baselines/gl2d_axes_range_type.png differ diff --git a/test/image/baselines/gl2d_clean-number.png b/test/image/baselines/gl2d_clean-number.png new file mode 100644 index 00000000000..4143c7c7dce Binary files /dev/null and b/test/image/baselines/gl2d_clean-number.png differ diff --git a/test/image/baselines/gl2d_connect_gaps.png b/test/image/baselines/gl2d_connect_gaps.png index 69d5a638652..dba70d4dc06 100644 Binary files a/test/image/baselines/gl2d_connect_gaps.png and b/test/image/baselines/gl2d_connect_gaps.png differ diff --git a/test/image/baselines/gl2d_date_axes.png b/test/image/baselines/gl2d_date_axes.png index 309ec31a081..35a016d6f19 100644 Binary files a/test/image/baselines/gl2d_date_axes.png and b/test/image/baselines/gl2d_date_axes.png differ diff --git a/test/image/baselines/gl2d_error_bars.png b/test/image/baselines/gl2d_error_bars.png index 7c2ef3fe710..0cd99081555 100644 Binary files a/test/image/baselines/gl2d_error_bars.png and b/test/image/baselines/gl2d_error_bars.png differ diff --git a/test/image/baselines/gl2d_error_bars_log.png b/test/image/baselines/gl2d_error_bars_log.png index d8b75b45d95..26cc4c4c472 100644 Binary files a/test/image/baselines/gl2d_error_bars_log.png and b/test/image/baselines/gl2d_error_bars_log.png differ diff --git a/test/image/baselines/gl2d_fonts.png b/test/image/baselines/gl2d_fonts.png index 038bcef6f67..6465d9087c6 100644 Binary files a/test/image/baselines/gl2d_fonts.png and b/test/image/baselines/gl2d_fonts.png differ diff --git a/test/image/baselines/gl2d_layout_image.png b/test/image/baselines/gl2d_layout_image.png index 89a268319f9..fa45cb7793a 100644 Binary files a/test/image/baselines/gl2d_layout_image.png and b/test/image/baselines/gl2d_layout_image.png differ diff --git a/test/image/baselines/gl2d_line_aligned.png b/test/image/baselines/gl2d_line_aligned.png index 2d3752f23c6..84913b81e0f 100644 Binary files a/test/image/baselines/gl2d_line_aligned.png and b/test/image/baselines/gl2d_line_aligned.png differ diff --git a/test/image/baselines/gl2d_line_dash.png b/test/image/baselines/gl2d_line_dash.png index af3a62f6466..1b73c3f0907 100644 Binary files a/test/image/baselines/gl2d_line_dash.png and b/test/image/baselines/gl2d_line_dash.png differ diff --git a/test/image/baselines/gl2d_line_select.png b/test/image/baselines/gl2d_line_select.png index acdf282ae05..150c00b55a2 100644 Binary files a/test/image/baselines/gl2d_line_select.png and b/test/image/baselines/gl2d_line_select.png differ diff --git a/test/image/baselines/gl2d_marker_size.png b/test/image/baselines/gl2d_marker_size.png index 2aaffd39d60..563f0272edd 100644 Binary files a/test/image/baselines/gl2d_marker_size.png and b/test/image/baselines/gl2d_marker_size.png differ diff --git a/test/image/baselines/gl2d_marker_symbols.png b/test/image/baselines/gl2d_marker_symbols.png index f8b4b8eba58..954265506ed 100644 Binary files a/test/image/baselines/gl2d_marker_symbols.png and b/test/image/baselines/gl2d_marker_symbols.png differ diff --git a/test/image/baselines/gl2d_multiple-traces-axes-labels.png b/test/image/baselines/gl2d_multiple-traces-axes-labels.png index c38de480a7f..deff530274c 100644 Binary files a/test/image/baselines/gl2d_multiple-traces-axes-labels.png and b/test/image/baselines/gl2d_multiple-traces-axes-labels.png differ diff --git a/test/image/baselines/gl2d_multiple-traces-axes.png b/test/image/baselines/gl2d_multiple-traces-axes.png index 024f7b7578b..5c44cfc442d 100644 Binary files a/test/image/baselines/gl2d_multiple-traces-axes.png and b/test/image/baselines/gl2d_multiple-traces-axes.png differ diff --git a/test/image/baselines/gl2d_multiple_subplots.png b/test/image/baselines/gl2d_multiple_subplots.png index 6335bb92491..f0a814a8a6b 100644 Binary files a/test/image/baselines/gl2d_multiple_subplots.png and b/test/image/baselines/gl2d_multiple_subplots.png differ diff --git a/test/image/baselines/gl2d_open_marker_line_width.png b/test/image/baselines/gl2d_open_marker_line_width.png index 389948b230f..b8519424f4d 100644 Binary files a/test/image/baselines/gl2d_open_marker_line_width.png and b/test/image/baselines/gl2d_open_marker_line_width.png differ diff --git a/test/image/baselines/gl2d_scatter-color-clustering.png b/test/image/baselines/gl2d_scatter-color-clustering.png index 228aa2179ff..98e1e811832 100644 Binary files a/test/image/baselines/gl2d_scatter-color-clustering.png and b/test/image/baselines/gl2d_scatter-color-clustering.png differ diff --git a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png index 87f05bdc541..e2ebc54e4a8 100644 Binary files a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png and b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png differ diff --git a/test/image/baselines/gl2d_scatter-colorscale-points.png b/test/image/baselines/gl2d_scatter-colorscale-points.png index 0242f8616aa..2aea7fb1d79 100644 Binary files a/test/image/baselines/gl2d_scatter-colorscale-points.png and b/test/image/baselines/gl2d_scatter-colorscale-points.png differ diff --git a/test/image/baselines/gl2d_scatter-marker-line-colorscales.png b/test/image/baselines/gl2d_scatter-marker-line-colorscales.png index 0de117bf3d0..ca93022e7c3 100644 Binary files a/test/image/baselines/gl2d_scatter-marker-line-colorscales.png and b/test/image/baselines/gl2d_scatter-marker-line-colorscales.png differ diff --git a/test/image/baselines/gl2d_scatter_fill_self_next.png b/test/image/baselines/gl2d_scatter_fill_self_next.png index 42cd181a489..41bfa5171f6 100644 Binary files a/test/image/baselines/gl2d_scatter_fill_self_next.png and b/test/image/baselines/gl2d_scatter_fill_self_next.png differ diff --git a/test/image/baselines/gl2d_shapes_below_traces.png b/test/image/baselines/gl2d_shapes_below_traces.png index 7eae642d04a..8c8a17c86c8 100644 Binary files a/test/image/baselines/gl2d_shapes_below_traces.png and b/test/image/baselines/gl2d_shapes_below_traces.png differ diff --git a/test/image/baselines/gl2d_simple_inset.png b/test/image/baselines/gl2d_simple_inset.png index b87b1d68337..c0005789be9 100644 Binary files a/test/image/baselines/gl2d_simple_inset.png and b/test/image/baselines/gl2d_simple_inset.png differ diff --git a/test/image/baselines/gl2d_size_margins.png b/test/image/baselines/gl2d_size_margins.png index 1cea44693ad..20d53998065 100644 Binary files a/test/image/baselines/gl2d_size_margins.png and b/test/image/baselines/gl2d_size_margins.png differ diff --git a/test/image/baselines/gl2d_stacked_coupled_subplots.png b/test/image/baselines/gl2d_stacked_coupled_subplots.png index 534e823812d..c67933ea7e3 100644 Binary files a/test/image/baselines/gl2d_stacked_coupled_subplots.png and b/test/image/baselines/gl2d_stacked_coupled_subplots.png differ diff --git a/test/image/baselines/gl2d_stacked_subplots.png b/test/image/baselines/gl2d_stacked_subplots.png index 79cef6fec93..c7d26103984 100644 Binary files a/test/image/baselines/gl2d_stacked_subplots.png and b/test/image/baselines/gl2d_stacked_subplots.png differ diff --git a/test/image/baselines/gl2d_subplots_anchor.png b/test/image/baselines/gl2d_subplots_anchor.png index 078e27ed403..341f440ab3b 100644 Binary files a/test/image/baselines/gl2d_subplots_anchor.png and b/test/image/baselines/gl2d_subplots_anchor.png differ diff --git a/test/image/baselines/gl2d_tick-labels.png b/test/image/baselines/gl2d_tick-labels.png index 437905db4ca..759baf31cfa 100644 Binary files a/test/image/baselines/gl2d_tick-labels.png and b/test/image/baselines/gl2d_tick-labels.png differ diff --git a/test/image/mocks/gl2d_clean-number.json b/test/image/mocks/gl2d_clean-number.json new file mode 100644 index 00000000000..6969e2d58fd --- /dev/null +++ b/test/image/mocks/gl2d_clean-number.json @@ -0,0 +1,7 @@ +{ + "data": [{ + "type": "scattergl", + "x": ["1", " 2 ", "3$", "4%"], + "y": ["1", " 2 ", "$3", "%4"] + }] +} diff --git a/test/jasmine/assets/ie9_mock.js b/test/jasmine/assets/ie9_mock.js index 0d032173c95..ac9dd7a30d8 100644 --- a/test/jasmine/assets/ie9_mock.js +++ b/test/jasmine/assets/ie9_mock.js @@ -6,3 +6,4 @@ delete window.Float32Array; delete window.Float64Array; delete window.Int16Array; delete window.Int32Array; +delete window.DataView; diff --git a/test/jasmine/bundle_tests/ie9_test.js b/test/jasmine/bundle_tests/ie9_test.js index d874657245b..1d215fd40af 100644 --- a/test/jasmine/bundle_tests/ie9_test.js +++ b/test/jasmine/bundle_tests/ie9_test.js @@ -18,12 +18,13 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); describe('Bundle with IE9 supported trace types:', function() { - afterEach(destroyGraphDiv); - it(' check that ie9_mock.js did its job', function() { + it('check that ie9_mock.js did its job', function() { expect(function() { return ArrayBuffer; }) .toThrow(new ReferenceError('ArrayBuffer is not defined')); + expect(function() { return DataView; }) + .toThrow(new ReferenceError('DataView is not defined')); expect(function() { return Uint8Array; }) .toThrow(new ReferenceError('Uint8Array is not defined')); }); diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index b8c141101b5..b3e74a8c9bf 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -12,6 +12,7 @@ var Cartesian = require('@src/plots/cartesian'); var Axes = require('@src/plots/cartesian/axes'); var Fx = require('@src/components/fx'); var supplyLayoutDefaults = require('@src/plots/cartesian/layout_defaults'); +var BADNUM = require('@src/constants/numerical').BADNUM; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -19,7 +20,6 @@ var failTest = require('../assets/fail_test'); var selectButton = require('../assets/modebar_button'); var supplyDefaults = require('../assets/supply_defaults'); - describe('Test axes', function() { 'use strict'; @@ -2447,6 +2447,123 @@ describe('Test axes', function() { }); }); }); + + describe('makeCalcdata', function() { + var ax; + + function _makeCalcdata(trace, axLetter, axType) { + ax = {type: axType}; + Axes.setConvert(ax); + ax._categories = []; + return ax.makeCalcdata(trace, axLetter); + } + + describe('should convert items', function() { + it('- linear case', function() { + var out = _makeCalcdata({ + x: ['1', NaN, null, 2], + }, 'x', 'linear'); + expect(out).toEqual([1, BADNUM, BADNUM, 2]); + }); + + it('- date case', function() { + var out = _makeCalcdata({ + x: ['2000-01-01', NaN, null, new Date(2000, 0, 1).getTime()], + }, 'x', 'date'); + expect(out).toEqual([946684800000, BADNUM, BADNUM, 946684800000]); + }); + + it('- category case', function() { + var out = _makeCalcdata({ + x: ['a', 'b', null, 4], + }, 'x', 'category'); + + expect(out).toEqual([0, 1, BADNUM, 2]); + }); + }); + + describe('should fill item to other coordinate length if not present', function() { + it('- base case', function() { + var out = _makeCalcdata({ + y: [1, 2, 1], + }, 'x', 'linear'); + expect(out).toEqual([0, 1, 2]); + }); + + it('- x0/dx case', function() { + var out = _makeCalcdata({ + y: [1, 2, 1], + x0: 2, + dx: 10, + _length: 3 + }, 'x', 'linear'); + expect(out).toEqual([2, 12, 22]); + }); + + it('- other length case', function() { + var out = _makeCalcdata({ + _length: 5, + y: [1, 2, 1], + }, 'x', 'linear'); + expect(out).toEqual([0, 1, 2, 3, 4]); + }); + }); + + describe('should subarray typed arrays', function() { + it('- same length linear case', function() { + var x = new Float32Array([1, 2, 3]); + var out = _makeCalcdata({ + _length: 3, + x: x + }, 'x', 'linear'); + expect(out).toBe(x); + }); + + it('- same length log case', function() { + var x = new Float32Array([1, 2, 3]); + var out = _makeCalcdata({ + _length: 3, + x: x + }, 'x', 'log'); + expect(out).toBe(x); + }); + + it('- subarray case', function() { + var x = new Float32Array([1, 2, 3]); + var out = _makeCalcdata({ + _length: 2, + x: x + }, 'x', 'linear'); + expect(out).toEqual(new Float32Array([1, 2])); + // check that in and out are linked to same buffer + expect(out.buffer).toBeDefined(); + expect(out.buffer).toEqual(x.buffer); + }); + }); + + describe('should convert typed arrays to plain array', function() { + it('- on a category axis', function() { + var out = _makeCalcdata({ + x: new Float32Array([3, 1, 2]), + }, 'x', 'category'); + expect(out).toEqual([0, 1, 2]); + expect(ax._categories).toEqual([3, 1, 2]); + }); + + it('- on a date axis', function() { + var dates = [[2000, 0, 1], [2001, 0, 1], [2002, 0, 1]] + .map(function(d) { return new Date(d[0], d[1], d[2]).getTime(); }); + + // We could make this work down the road (in v2), + // when address our timezone problems. + var out = _makeCalcdata({ + x: new Float64Array(dates) + }, 'x', 'date'); + + expect(out).toEqual([946684800000, 978307200000, 1009843200000]); + }); + }); + }); }); function getZoomInButton(gd) { diff --git a/test/jasmine/tests/colorscale_test.js b/test/jasmine/tests/colorscale_test.js index 3eaa7c902d5..22771341d9e 100644 --- a/test/jasmine/tests/colorscale_test.js +++ b/test/jasmine/tests/colorscale_test.js @@ -190,6 +190,41 @@ describe('Test colorscale:', function() { expect(hasColorscale(trace, 'marker')).toBe(true); expect(hasColorscale(trace, 'marker.line')).toBe(true); }); + + it('should return true when marker color is a typed array with at least one non-NaN', function() { + trace = { + marker: { + color: new Float32Array([1, 2, 3]), + line: { + color: new Float32Array([2, 3, 4]) + } + } + }; + expect(hasColorscale(trace, 'marker')).toBe(true); + expect(hasColorscale(trace, 'marker.line')).toBe(true); + + trace = { + marker: { + color: new Float32Array([1, NaN, 3]), + line: { + color: new Float32Array([2, 3, NaN]) + } + } + }; + expect(hasColorscale(trace, 'marker')).toBe(true); + expect(hasColorscale(trace, 'marker.line')).toBe(true); + + trace = { + marker: { + color: new Float32Array([NaN, undefined, 'not-a-number']), + line: { + color: new Float32Array(['not-a-number', NaN, undefined]) + } + } + }; + expect(hasColorscale(trace, 'marker')).toBe(false); + expect(hasColorscale(trace, 'marker.line')).toBe(false); + }); }); describe('handleDefaults (heatmap-like version)', function() { diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 852c543f44f..23512108888 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -534,11 +534,22 @@ describe('@gl Test hover and click interactions', function() { describe('@noCI @gl Test gl2d lasso/select:', function() { var mockFancy = require('@mocks/gl2d_14.json'); + delete mockFancy.layout.xaxis.autorange; + delete mockFancy.layout.yaxis.autorange; + mockFancy.layout.xaxis.range = [-2.951309064136961, 2.0954721318818916]; + mockFancy.layout.yaxis.range = [-0.9248866483012275, 1.3232607344525835]; + var mockFast = Lib.extendDeep({}, mockFancy, { data: [{mode: 'markers'}], layout: { - xaxis: {type: 'linear'}, - yaxis: {type: 'linear'} + xaxis: { + type: 'linear', + range: [-3.869222222222223, 73.55522222222223] + }, + yaxis: { + type: 'linear', + range: [-0.7402222222222222, 17.144222222222222] + } } }); diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index 0a219692b1c..7ebdd5c2756 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -209,7 +209,6 @@ describe('@gl Test gl plot side effects', function() { describe('@gl Test gl2d plots', function() { var gd; - var mock = require('@mocks/gl2d_10.json'); beforeEach(function() { @@ -255,9 +254,9 @@ describe('@gl Test gl2d plots', function() { var relayoutCallback = jasmine.createSpy('relayoutCallback'); var originalX = [-0.3037383177570093, 5.303738317757009]; - var originalY = [-0.5, 6.1]; - var newX = [-0.5, 5]; - var newY = [-1.7, 4.95]; + var originalY = [-0.5806379476536665, 6.218528262566369]; + var newX = [-0.5516431924882629, 5.082159624413145]; + var newY = [-1.7947747709072441, 5.004391439312791]; var precision = 1; Plotly.newPlot(gd, _mock) @@ -584,8 +583,8 @@ describe('@gl Test gl2d plots', function() { }); }) .then(function() { - expect(gd.layout.xaxis.range).toBeCloseToArray([-7.6, 23.6], 1); - expect(gd.layout.yaxis.range).toBeCloseToArray([0.2, 15.8], 1); + expect(gd.layout.xaxis.range).toBeCloseToArray([-8.2, 24.2], 1); + expect(gd.layout.yaxis.range).toBeCloseToArray([-0.12, 16.1], 1); }) .catch(fail) .then(done); @@ -834,3 +833,103 @@ describe('@gl Test gl2d plots', function() { .then(done); }); }); + +describe('Test scattergl autorange:', function() { + afterEach(destroyGraphDiv); + + describe('should return the same value as SVG scatter for ~small~ data', function() { + var specs = [ + {name: 'lines+markers', fig: require('@mocks/gl2d_10.json')}, + {name: 'bubbles', fig: require('@mocks/gl2d_12.json')}, + {name: 'line on log axes', fig: require('@mocks/gl2d_14.json')}, + {name: 'fill to zero', fig: require('@mocks/gl2d_axes_labels2.json')}, + {name: 'annotations', fig: require('@mocks/gl2d_annotations.json')} + ]; + + specs.forEach(function(s) { + it('- case ' + s.name, function(done) { + var gd = createGraphDiv(); + var glRangeX; + var glRangeY; + + // ensure the mocks have auto-range turned on + var glFig = Lib.extendDeep({}, s.fig); + Lib.extendDeep(glFig.layout, {xaxis: {autorange: true}}); + Lib.extendDeep(glFig.layout, {yaxis: {autorange: true}}); + + var svgFig = Lib.extendDeep({}, glFig); + svgFig.data.forEach(function(t) { t.type = 'scatter'; }); + + Plotly.newPlot(gd, glFig).then(function() { + glRangeX = gd._fullLayout.xaxis.range; + glRangeY = gd._fullLayout.yaxis.range; + }) + .then(function() { + return Plotly.newPlot(gd, svgFig); + }) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray(glRangeX, 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray(glRangeY, 'y range'); + }) + .catch(fail) + .then(done); + }); + }); + }); + + describe('should return the approximative values for ~big~ data', function() { + beforeEach(function() { + spyOn(ScatterGl, 'plot'); + }); + + // threshold for 'fast' axis expansion routine + var N = 1e5; + var x = new Array(N); + var y = new Array(N); + var ms = new Array(N); + + Lib.seedPseudoRandom(); + + for(var i = 0; i < N; i++) { + x[i] = Lib.pseudoRandom(); + y[i] = Lib.pseudoRandom(); + ms[i] = 10 * Lib.pseudoRandom() + 20; + } + + it('- case scalar marker.size', function(done) { + var gd = createGraphDiv(); + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + x: x, + y: y, + marker: {size: 10} + }]) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.02, 1.02], 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.04, 1.04], 'y range'); + }) + .catch(fail) + .then(done); + }); + + it('- case array marker.size', function(done) { + var gd = createGraphDiv(); + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + x: x, + y: y, + marker: {size: ms} + }]) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.05, 1.05], 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.11, 1.11], 'y range'); + }) + .catch(fail) + .then(done); + }); + }); +}); diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index 30da55e7423..c9bef41f48f 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -449,6 +449,34 @@ describe('heatmap calc', function() { [100, 255, 1010] ]); }); + + it('should fill in bricks if x/y not given (typed array case)', function() { + var out = _calc({ + z: [ + new Float32Array([1, 2, 3]), + new Float32Array([3, 1, 2]) + ] + }); + + expect(out.x).toBeCloseToArray([-0.5, 0.5, 1.5, 2.5]); + expect(out.y).toBeCloseToArray([-0.5, 0.5, 1.5]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); + + it('should convert x/y coordinates into bricks (typed array case)', function() { + var out = _calc({ + x: new Float32Array([1, 2, 3]), + y: new Float32Array([2, 6]), + z: [ + new Float32Array([1, 2, 3]), + new Float32Array([3, 1, 2]) + ] + }); + + expect(out.x).toBeCloseToArray([0.5, 1.5, 2.5, 3.5]); + expect(out.y).toBeCloseToArray([0, 4, 8]); + expect(out.z).toBeCloseTo2DArray([[1, 2, 3], [3, 1, 2]]); + }); }); describe('heatmap plot', function() { diff --git a/test/jasmine/tests/is_array_test.js b/test/jasmine/tests/is_array_test.js index bea361516db..df95a3b3971 100644 --- a/test/jasmine/tests/is_array_test.js +++ b/test/jasmine/tests/is_array_test.js @@ -1,12 +1,11 @@ var Lib = require('@src/lib'); -describe('isArray', function() { - 'use strict'; - - var isArray = Lib.isArray; - +describe('isArrayOrTypedArray', function() { function A() {} + var buffer = new ArrayBuffer(2); + var dv = new DataView(buffer); + var shouldPass = [ [], new Array(10), @@ -30,18 +29,65 @@ describe('isArray', function() { '\n', new Date(), new RegExp('foo'), - new String('string') + new String('string'), + dv + ]; + + shouldPass.forEach(function(obj) { + it('treats ' + JSON.stringify(obj) + ' as an array', function() { + expect(Lib.isArrayOrTypedArray(obj)).toBe(true); + }); + }); + + shouldFail.forEach(function(obj) { + it('treats ' + JSON.stringify(obj !== window ? obj : 'window') + ' as NOT an array', function() { + expect(Lib.isArrayOrTypedArray(obj)).toBe(false); + }); + }); +}); + +describe('isTypedArray', function() { + function A() {} + + var buffer = new ArrayBuffer(2); + var dv = new DataView(buffer); + + var shouldPass = [ + new Float32Array(1), + new Int32Array([1, 2, 3]) + ]; + + var shouldFail = [ + new Array(10), + [], + A, + new A(), + document, + window, + null, + undefined, + 'string', + true, + false, + NaN, + Infinity, + /foo/, + '\n', + new Date(), + new RegExp('foo'), + new String('string'), + dv ]; shouldPass.forEach(function(obj) { it('treats ' + JSON.stringify(obj) + ' as an array', function() { - expect(isArray(obj)).toBe(true); + expect(Lib.isTypedArray(obj)).toBe(true); }); }); shouldFail.forEach(function(obj) { it('treats ' + JSON.stringify(obj !== window ? obj : 'window') + ' as NOT an array', function() { - expect(isArray(obj)).toBe(false); + expect(Lib.isTypedArray(obj)).toBe(false); }); }); }); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 29ce0b65301..d167d8feadb 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -652,6 +652,24 @@ describe('Test lib.js:', function() { expect(cOut).toBe(outObj.b.c); }); + describe('data_array valType', function() { + var attrs = { + d: {valType: 'data_array'} + }; + + it('should pass ref to out object (plain array case)', function() { + var arr = [1, 2, 3]; + out = coerce({d: arr}, {}, attrs, 'd'); + expect(out).toBe(arr); + }); + + it('should pass ref to out object (typed array case)', function() { + var arr = new Float32Array([1, 2, 3]); + out = coerce({d: arr}, {}, attrs, 'd'); + expect(out).toBe(arr); + }); + }); + describe('string valType', function() { var dflt = 'Jabberwock', stringAttrs = { diff --git a/test/jasmine/tests/parcoords_test.js b/test/jasmine/tests/parcoords_test.js index cbcf989c032..c638424097e 100644 --- a/test/jasmine/tests/parcoords_test.js +++ b/test/jasmine/tests/parcoords_test.js @@ -628,7 +628,7 @@ describe('@gl parcoords', function() { function restyleDimension(key, setterValue) { // array values need to be wrapped in an array; unwrapping here for value comparison - var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue; + var value = Array.isArray(setterValue) ? setterValue[0] : setterValue; return function() { return Plotly.restyle(gd, 'dimensions[2].' + key, setterValue).then(function() { @@ -892,7 +892,7 @@ describe('@gl parcoords', function() { Plotly.plot(gd, mockCopy.data, mockCopy.layout); function restyleDimension(key, dimIndex, setterValue) { - var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue; + var value = Array.isArray(setterValue) ? setterValue[0] : setterValue; return function() { return Plotly.restyle(gd, 'dimensions[' + dimIndex + '].' + key, setterValue).then(function() { expect(gd.data[0].dimensions[dimIndex][key]).toEqual(value, 'for dimension attribute \'' + key + '\''); diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 0d52ac76d28..0dfebad8e73 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -523,7 +523,7 @@ describe('Test plot api', function() { return gd; } - it('should trigger recalc when switching into select or lasso dragmode', function() { + it('should trigger replot (but not recalc) when switching into select or lasso dragmode for scattergl traces', function() { var gd = mock({ data: [{ type: 'scattergl', @@ -541,8 +541,8 @@ describe('Test plot api', function() { expect(subroutines.layoutReplot).not.toHaveBeenCalled(); } - function expectRecalc() { - expect(gd.calcdata).toBeUndefined(); + function expectReplot() { + expect(gd.calcdata).toBeDefined(); expect(subroutines.doModeBar).not.toHaveBeenCalled(); expect(subroutines.layoutReplot).toHaveBeenCalled(); } @@ -551,7 +551,7 @@ describe('Test plot api', function() { expectModeBarOnly(); Plotly.relayout(mock(gd), 'dragmode', 'lasso'); - expectRecalc(); + expectReplot(); Plotly.relayout(mock(gd), 'dragmode', 'select'); expectModeBarOnly(); @@ -563,7 +563,7 @@ describe('Test plot api', function() { expectModeBarOnly(); Plotly.relayout(mock(gd), 'dragmode', 'select'); - expectRecalc(); + expectReplot(); }); }); @@ -1545,8 +1545,7 @@ describe('Test plot api', function() { }); }); - - describe('Plotly.ExtendTraces', function() { + describe('Plotly.extendTraces / Plotly.prependTraces', function() { var gd; beforeEach(function() { @@ -1593,7 +1592,6 @@ describe('Test plot api', function() { }); - it('should throw an error when indices are omitted', function() { expect(function() { @@ -1741,7 +1739,6 @@ describe('Test plot api', function() { expect(gd.data).toEqual(cachedData); }); - it('extend is the inverse of prepend - no maxPoints', function() { var cachedData = Lib.extendDeep([], gd.data); @@ -1759,7 +1756,6 @@ describe('Test plot api', function() { expect(gd.data).toEqual(cachedData); }); - it('prepend is the inverse of extend - with maxPoints', function() { var maxPoints = 3; var cachedData = Lib.extendDeep([], gd.data); @@ -1777,6 +1773,173 @@ describe('Test plot api', function() { expect(gd.data).toEqual(cachedData); }); + + it('should throw when trying to extend a plain array with a typed array', function() { + gd.data = [{ + x: new Float32Array([1, 2, 3]), + marker: {size: new Float32Array([20, 30, 10])} + }]; + + expect(function() { + Plotly.extendTraces(gd, {x: [[1]]}, [0]); + }).toThrow(new Error('cannot extend array with an array of a different type: x')); + }); + + it('should throw when trying to extend a typed array with a plain array', function() { + gd.data = [{ + x: [1, 2, 3], + marker: {size: [20, 30, 10]} + }]; + + expect(function() { + Plotly.extendTraces(gd, {x: [new Float32Array([1])]}, [0]); + }).toThrow(new Error('cannot extend array with an array of a different type: x')); + }); + + it('should extend traces with update keys (typed array case)', function() { + gd.data = [{ + x: new Float32Array([1, 2, 3]), + marker: {size: new Float32Array([20, 30, 10])} + }]; + + Plotly.extendTraces(gd, { + x: [new Float32Array([4, 5])], + 'marker.size': [new Float32Array([40, 30])] + }, [0]); + + expect(gd.data[0].x).toEqual(new Float32Array([1, 2, 3, 4, 5])); + expect(gd.data[0].marker.size).toEqual(new Float32Array([20, 30, 10, 40, 30])); + }); + + describe('should extend/prepend and window traces with update keys linked', function() { + function _base(method, args, expectations) { + gd.data = [{ + x: [1, 2, 3] + }, { + x: new Float32Array([1, 2, 3]) + }]; + + Plotly[method](gd, { + x: [args.newPts, new Float32Array(args.newPts)] + }, [0, 1], args.maxp); + + expect(PlotlyInternal.redraw).toHaveBeenCalled(); + expect(Plotly.Queue.add).toHaveBeenCalled(); + + expect(gd.data[0].x).toEqual(expectations.newArray); + expect(gd.data[1].x).toEqual(new Float32Array(expectations.newArray)); + + var cont = Plotly.Queue.add.calls.first().args[2][1].x; + expect(cont[0]).toEqual(expectations.remainder); + expect(cont[1]).toEqual(new Float32Array(expectations.remainder)); + } + + function _extendTraces(args, expectations) { + return _base('extendTraces', args, expectations); + } + + function _prependTraces(args, expectations) { + return _base('prependTraces', args, expectations); + } + + it('- extend no maxp', function() { + _extendTraces({ + newPts: [4, 5] + }, { + newArray: [1, 2, 3, 4, 5], + remainder: [] + }); + }); + + it('- extend maxp === insert.length', function() { + _extendTraces({ + newPts: [4, 5], + maxp: 2 + }, { + newArray: [4, 5], + remainder: [1, 2, 3] + }); + }); + + it('- extend maxp < insert.length', function() { + _extendTraces({ + newPts: [4, 5], + maxp: 1 + }, { + newArray: [5], + remainder: [1, 2, 3, 4] + }); + }); + + it('- extend maxp > insert.length', function() { + _extendTraces({ + newPts: [4, 5], + maxp: 4 + }, { + newArray: [2, 3, 4, 5], + remainder: [1] + }); + }); + + it('- extend maxp === 0', function() { + _extendTraces({ + newPts: [4, 5], + maxp: 0 + }, { + newArray: [], + remainder: [1, 2, 3, 4, 5] + }); + }); + + it('- prepend no maxp', function() { + _prependTraces({ + newPts: [-1, 0] + }, { + newArray: [-1, 0, 1, 2, 3], + remainder: [] + }); + }); + + it('- prepend maxp === insert.length', function() { + _prependTraces({ + newPts: [-1, 0], + maxp: 2 + }, { + newArray: [-1, 0], + remainder: [1, 2, 3] + }); + }); + + it('- prepend maxp < insert.length', function() { + _prependTraces({ + newPts: [-1, 0], + maxp: 1 + }, { + newArray: [-1], + remainder: [0, 1, 2, 3] + }); + }); + + it('- prepend maxp > insert.length', function() { + _prependTraces({ + newPts: [-1, 0], + maxp: 4 + }, { + newArray: [-1, 0, 1, 2], + remainder: [3] + }); + }); + + it('- prepend maxp === 0', function() { + _prependTraces({ + newPts: [-1, 0], + maxp: 0 + }, { + newArray: [], + remainder: [-1, 0, 1, 2, 3] + }); + }); + }); }); describe('Plotly.purge', function() { @@ -2600,7 +2763,13 @@ describe('Test plot api', function() { ['text_chart_arrays', require('@mocks/text_chart_arrays.json')], ['updatemenus', require('@mocks/updatemenus.json')], ['violins', require('@mocks/violins.json')], - ['world-cals', require('@mocks/world-cals.json')] + ['world-cals', require('@mocks/world-cals.json')], + ['typed arrays', { + data: [{ + x: new Float32Array([1, 2, 3]), + y: new Float32Array([1, 2, 1]) + }] + }] ]; mockList.forEach(function(mockSpec) { diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index 9a49e8c4547..5a666a7ac73 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -179,7 +179,7 @@ describe('sankey tests', function() { } }); - expect(Lib.isArray(fullTrace.node.color)).toBe(true, 'set up color array'); + expect(Array.isArray(fullTrace.node.color)).toBe(true, 'set up color array'); expect(fullTrace.node.color).toEqual(['rgba(31, 119, 180, 0.8)', 'rgba(255, 127, 14, 0.8)']); }); @@ -197,7 +197,7 @@ describe('sankey tests', function() { } }, {colorway: ['rgb(255, 0, 0)', 'rgb(0, 0, 255)']}); - expect(Lib.isArray(fullTrace.node.color)).toBe(true, 'set up color array'); + expect(Array.isArray(fullTrace.node.color)).toBe(true, 'set up color array'); expect(fullTrace.node.color).toEqual(['rgba(255, 0, 0, 0.8)', 'rgba(0, 0, 255, 0.8)']); }); @@ -215,7 +215,7 @@ describe('sankey tests', function() { } }); - expect(Lib.isArray(fullTrace.link.label)).toBe(true, 'must be an array'); + expect(Array.isArray(fullTrace.link.label)).toBe(true, 'must be an array'); expect(fullTrace.link.label).toEqual([], 'an array of empty strings'); }); @@ -233,7 +233,7 @@ describe('sankey tests', function() { } }); - expect(Lib.isArray(fullTrace.link.label)).toBe(true, 'must be an array'); + expect(Array.isArray(fullTrace.link.label)).toBe(true, 'must be an array'); expect(fullTrace.link.label).toEqual(['a', 'b'], 'an array of the supplied values'); }); }); diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 1f6fdb97b3f..ec295c7d9d9 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -13,6 +13,15 @@ var fail = require('../assets/fail_test'); var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; +var getOpacity = function(node) { return Number(node.style.opacity); }; +var getFillOpacity = function(node) { return Number(node.style['fill-opacity']); }; +var getColor = function(node) { return node.style.fill; }; +var getMarkerSize = function(node) { + // find path arc multiply by 2 to get the corresponding marker.size value + // (works for circles only) + return d3.select(node).attr('d').split('A')[1].split(',')[0] * 2; +}; + describe('Test scatter', function() { 'use strict'; @@ -757,6 +766,53 @@ describe('end-to-end scatter tests', function() { .catch(fail) .then(done); }); + + it('should work with typed arrays', function(done) { + function _assert(colors, sizes) { + var pts = d3.selectAll('.point'); + expect(pts.size()).toBe(3, '# of pts'); + + pts.each(function(_, i) { + expect(getColor(this)).toBe(colors[i], 'color ' + i); + expect(getMarkerSize(this)).toBe(sizes[i], 'size ' + i); + }); + } + + Plotly.newPlot(gd, [{ + x: new Float32Array([1, 2, 3]), + y: new Float32Array([1, 2, 1]), + marker: { + size: new Float32Array([20, 30, 10]), + color: new Float32Array([10, 30, 20]), + cmin: 10, + cmax: 30, + colorscale: [ + [0, 'rgb(255, 0, 0)'], + [0.5, 'rgb(0, 255, 0)'], + [1, 'rgb(0, 0, 255)'] + ] + } + }]) + .then(function() { + _assert( + ['rgb(255, 0, 0)', 'rgb(0, 0, 255)', 'rgb(0, 255, 0)'], + [20, 30, 10] + ); + + return Plotly.restyle(gd, { + 'marker.size': [new Float32Array([40, 30, 20])], + 'marker.color': [new Float32Array([20, 30, 10])] + }); + }) + .then(function() { + _assert( + ['rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'rgb(255, 0, 0)'], + [40, 30, 20] + ); + }) + .catch(fail) + .then(done); + }); }); describe('scatter hoverPoints', function() { @@ -860,15 +916,6 @@ describe('Test Scatter.style', function() { }; } - var getOpacity = function(node) { return Number(node.style.opacity); }; - var getFillOpacity = function(node) { return Number(node.style['fill-opacity']); }; - var getColor = function(node) { return node.style.fill; }; - var getMarkerSize = function(node) { - // find path arc multiply by 2 to get the corresponding marker.size value - // (works for circles only) - return d3.select(node).attr('d').split('A')[1].split(',')[0] * 2; - }; - var r = 'rgb(255, 0, 0)'; var g = 'rgb(0, 255, 0)'; var b = 'rgb(0, 0, 255)'; diff --git a/test/jasmine/tests/table_test.js b/test/jasmine/tests/table_test.js index 2d532275963..ab5d988f020 100644 --- a/test/jasmine/tests/table_test.js +++ b/test/jasmine/tests/table_test.js @@ -318,7 +318,7 @@ describe('table', function() { function restyleValues(what, key, setterValue) { // array values need to be wrapped in an array; unwrapping here for value comparison - var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue; + var value = Array.isArray(setterValue) ? setterValue[0] : setterValue; return function() { return Plotly.restyle(gd, what + '.values[' + key + ']', setterValue).then(function() { @@ -367,7 +367,7 @@ describe('table', function() { function restyleValues(what, fun, setterValue) { - var value = Lib.isArray(setterValue) ? setterValue[0] : setterValue; + var value = Array.isArray(setterValue) ? setterValue[0] : setterValue; return function() { return Plotly.restyle(gd, what, setterValue).then(function() {