diff --git a/src/lib/array.js b/src/lib/array.js index bda12db0795..8c00c51c3fe 100644 --- a/src/lib/array.js +++ b/src/lib/array.js @@ -7,6 +7,8 @@ */ 'use strict'; +var b64 = require('base64-arraybuffer'); +var isPlainObject = require('./is_plain_object'); var isArray = Array.isArray; @@ -63,6 +65,112 @@ exports.ensureArray = function(out, n) { return out; }; +var typedArrays = { + int8: typeof Int8Array !== 'undefined' ? Int8Array : null, + uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null, + uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null, + int16: typeof Int16Array !== 'undefined' ? Int16Array : null, + uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null, + int32: typeof Int32Array !== 'undefined' ? Int32Array : null, + uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null, + float32: typeof Float32Array !== 'undefined' ? Float32Array : null, + float64: typeof Float64Array !== 'undefined' ? Float64Array : null, +}; +exports.typedArrays = typedArrays; + + +exports.decodeTypedArraySpec = function(v) { + // Assume processed by coerceTypedArraySpec + v = coerceTypedArraySpec(v); + var T = typedArrays[v.dtype]; + var buffer; + if(v.bvals.constructor === ArrayBuffer) { + // Already an ArrayBuffer + buffer = v.bvals; + } else { + // Decode, assuming a string + buffer = b64.decode(v.bvals); + } + + // Check if 1d shape. If so, we're done + if(v.ndims === 1) { + // Construct single Typed array over entire buffer + return new T(buffer); + } else { + // Reshape into nested plain arrays with innermost + // level containing typed arrays + // We could eventually adopt an ndarray library + + // Build cumulative product of dimensions + var cumulativeShape = v.shape.map(function(a, i) { + return a * (v.shape[i - 1] || 1); + }); + + // Loop of dimensions in reverse order + var nestedArray = []; + for(var dimInd = v.ndims - 1; dimInd > 0; dimInd--) { + var subArrayLength = v.shape[dimInd]; + var numSubArrays = cumulativeShape[dimInd - 1]; + var nextArray = []; + + if(dimInd === v.ndims - 1) { + // First time through, we build the + // inner most typed arrays + for(var typedInd = 0; typedInd < numSubArrays; typedInd++) { + var typedOffset = typedInd * subArrayLength; + nextArray.push( + new T(buffer, typedOffset * T.BYTES_PER_ELEMENT, subArrayLength) + ); + } + } else { + // Following times through, build + // next layer of nested arrays + for(var i = 0; i < numSubArrays; i++) { + var offset = i * subArrayLength; + nextArray.push(nextArray.slice(offset, offset + subArrayLength - 1)); + } + } + + // Update nested array with next nesting level + nestedArray = nextArray; + } + + return nestedArray; + } +}; + +function isTypedArraySpec(v) { + // Assume v has not passed through + return isPlainObject(v) && typedArrays[v.dtype] && v.bvals && ( + Number.isInteger(v.shape) || + (isArrayOrTypedArray(v.shape) && + v.shape.length > 0 && + v.shape.every(function(d) { return Number.isInteger(d); })) + ); +} +exports.isTypedArraySpec = isTypedArraySpec; + +function coerceTypedArraySpec(v) { + // Assume isTypedArraySpec passed + var coerced = {dtype: v.dtype, bvals: v.bvals}; + + // Normalize shape to a list + if(Number.isInteger(v.shape)) { + coerced.shape = [v.shape]; + } else { + coerced.shape = v.shape; + } + + // Add length property + coerced.length = v.shape.reduce(function(a, b) { return a * b; }); + + // Add ndims + coerced.ndims = v.shape.length; + + return coerced; +} +exports.coerceTypedArraySpec = coerceTypedArraySpec; + /* * TypedArray-compatible concatenation of n arrays * if all arrays are the same type it will preserve that type, diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 4ff5465d3cd..c2d931f7b3a 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -18,22 +18,11 @@ var DESELECTDIM = require('../constants/interactions').DESELECTDIM; var nestedProperty = require('./nested_property'); var counterRegex = require('./regex').counter; var modHalf = require('./mod').modHalf; -var isPlainObject = require('./is_plain_object'); var isArrayOrTypedArray = require('./array').isArrayOrTypedArray; +var isTypedArraySpec = require('./array').isTypedArraySpec; +var decodeTypedArraySpec = require('./array').decodeTypedArraySpec; +var coerceTypedArraySpec = require('./array').coerceTypedArraySpec; -var typedArrays = { - int8: typeof Int8Array !== 'undefined' ? 1 : 0, - uint8: typeof Uint8Array !== 'undefined' ? 1 : 0, - uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? 1 : 0, - int16: typeof Int16Array !== 'undefined' ? 1 : 0, - uint16: typeof Uint16Array !== 'undefined' ? 1 : 0, - int32: typeof Int32Array !== 'undefined' ? 1 : 0, - uint32: typeof Uint32Array !== 'undefined' ? 1 : 0, - float32: typeof Float32Array !== 'undefined' ? 1 : 0, - float64: typeof Float64Array !== 'undefined' ? 1 : 0, - bigint64: typeof BigInt64Array !== 'undefined' ? 1 : 0, - biguint64: typeof BigUint64Array !== 'undefined' ? 1 : 0 -}; exports.valObjectMeta = { data_array: { @@ -53,12 +42,45 @@ exports.valObjectMeta = { if(isArrayOrTypedArray(v)) { propOut.set(v); wasSet = true; - } else if(isPlainObject(v)) { - var T = typedArrays[v.dtype]; - if(T) { - propOut.set(v); - wasSet = true; + } else if(isTypedArraySpec(v)) { + // Copy and coerce spec + v = coerceTypedArraySpec(v); + + // See if caching location is available + var uid = propOut.obj.uid; + var module = propOut.obj._module; + + if(v.bvals.constructor === ArrayBuffer || !uid || !module) { + // Already an ArrayBuffer + // decoding is cheap + propOut.set(decodeTypedArraySpec(v)); + } else { + var prop = propOut.astr; + var cache = module._b64BufferCache || {}; + + // Check cache + var cachedBuffer = ((cache[uid] || {})[prop] || {})[v.bvals]; + if(cachedBuffer !== undefined) { + // Use cached array buffer instead of base64 encoded + // string + v.bvals = cachedBuffer; + propOut.set(decodeTypedArraySpec(v)); + } else { + // Not in so cache decode + var decoded = decodeTypedArraySpec(v); + propOut.set(decoded); + + // Update cache for next time + cache[uid] = (cache[uid] || {}); + + // Clear any prior cache value (only store one per + // trace property + cache[uid][prop] = {}; + cache[uid][prop][v.bvals] = decoded.buffer; + module._b64BufferCache = cache; + } } + wasSet = true; } if(!wasSet && dflt !== undefined) propOut.set(dflt); } diff --git a/src/plots/plots.js b/src/plots/plots.js index 9c10b2cf1e9..c28b8f3df72 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -11,7 +11,6 @@ var d3 = require('d3'); var timeFormatLocale = require('d3-time-format').timeFormatLocale; var isNumeric = require('fast-isnumeric'); -var b64 = require('base64-arraybuffer'); var Registry = require('../registry'); var PlotSchema = require('../plot_api/plot_schema'); @@ -2849,50 +2848,7 @@ function _transition(gd, transitionOpts, opts) { return transitionStarting.then(function() { return gd; }); } -var typedArrays = { - int8: typeof Int8Array !== 'undefined' ? Int8Array : null, - uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null, - uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null, - int16: typeof Int16Array !== 'undefined' ? Int16Array : null, - uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null, - int32: typeof Int32Array !== 'undefined' ? Int32Array : null, - uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null, - float32: typeof Float32Array !== 'undefined' ? Float32Array : null, - float64: typeof Float64Array !== 'undefined' ? Float64Array : null, - bigint64: typeof BigInt64Array !== 'undefined' ? BigInt64Array : null, - biguint64: typeof BigUint64Array !== 'undefined' ? BigUint64Array : null -}; - -function _decode(cont) { - if(cont.dtype && cont.bvals) { - var T = typedArrays[cont.dtype]; - if(T) { - return new T(b64.decode(cont.bvals)); - } - } - - for(var prop in cont) { - if(prop[0] !== '_' && cont.hasOwnProperty(prop)) { - var item = cont[prop]; - if(Lib.isPlainObject(item)) { - var r = _decode(item); - if(r !== undefined) cont[prop] = r; - } - } - } -} - -function decodeB64Arrays(gd) { - for(var i = 0; i < gd._fullData.length; i++) { - _decode(gd._fullData[i]); - } - - _decode(gd._fullLayout); -} - plots.doCalcdata = function(gd, traces) { - decodeB64Arrays(gd); - var axList = axisIDs.list(gd); var fullData = gd._fullData; var fullLayout = gd._fullLayout; diff --git a/src/traces/heatmap/xyz_defaults.js b/src/traces/heatmap/xyz_defaults.js index 5618b4a35b3..cb0c912e965 100644 --- a/src/traces/heatmap/xyz_defaults.js +++ b/src/traces/heatmap/xyz_defaults.js @@ -19,25 +19,19 @@ module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, x yName = yName || 'y'; var x, y; - var shapeX = x ? (x.shape ? x.shape[0] : x.length) || 0 : 0; - var shapeY = y ? (y.shape ? y.shape[0] : y.length) || 0 : 0; - var shapeZ = z ? (z.shape ? z.shape[0] : z.length) || 0 : 0; + if(z === undefined || !z.length) return 0; - var zlen = shapeZ || (z && z.length) || 0; - - if(z === undefined || !zlen) return 0; - - if(Lib.isArray1D(traceIn.z) || (z && z.shape && z.shape.length === 1)) { + if(Lib.isArray1D(z)) { x = coerce(xName); y = coerce(yName); - var xlen = shapeX || Lib.minRowLength(x); - var ylen = shapeY || Lib.minRowLength(y); + var xlen = Lib.minRowLength(x); + var ylen = Lib.minRowLength(y); // column z must be accompanied by xName and yName arrays if(xlen === 0 || ylen === 0) return 0; - traceOut._length = Math.min(xlen, ylen, zlen); + traceOut._length = Math.min(xlen, ylen, z.length); } else { x = coordDefaults(xName, coerce); y = coordDefaults(yName, coerce); diff --git a/src/traces/isosurface/defaults.js b/src/traces/isosurface/defaults.js index 0ce5fc342b8..c8b9916c86e 100644 --- a/src/traces/isosurface/defaults.js +++ b/src/traces/isosurface/defaults.js @@ -38,18 +38,12 @@ function supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce) { var z = coerce('z'); var value = coerce('value'); - var len = 0; - - if(x && y && z && value) { - len = Math.min( - (x.shape ? x.shape[0] : x.length) || 0, - (y.shape ? y.shape[0] : y.length) || 0, - (z.shape ? z.shape[0] : z.length) || 0, - (value.shape ? value.shape[0] : value.length) || 0 - ); - } - - if(!len) { + if( + !x || !x.length || + !y || !y.length || + !z || !z.length || + !value || !value.length + ) { traceOut.visible = false; return; } diff --git a/src/traces/scatter/xy_defaults.js b/src/traces/scatter/xy_defaults.js index 697472f2d21..5c8f7ace4ef 100644 --- a/src/traces/scatter/xy_defaults.js +++ b/src/traces/scatter/xy_defaults.js @@ -19,22 +19,19 @@ module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) { var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); - var shapeX = x && x.shape ? x.shape[0] : 0; - var shapeY = y && y.shape ? y.shape[0] : 0; - if(x) { - var xlen = shapeX || Lib.minRowLength(x); + var xlen = Lib.minRowLength(x); if(y) { - len = shapeY || Math.min(xlen, Lib.minRowLength(y)); + len = Math.min(xlen, Lib.minRowLength(y)); } else { - len = shapeX || xlen; + len = xlen; coerce('y0'); coerce('dy'); } } else { if(!y) return 0; - len = shapeY || Lib.minRowLength(y); + len = Lib.minRowLength(y); coerce('x0'); coerce('dx'); } diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index 5be93835de4..0c1b1cf8274 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -78,12 +78,8 @@ function handleXYZDefaults(traceIn, traceOut, coerce, layout) { handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); if(x && y && z) { - var shapeX = (x.shape ? x.shape[0] : x.length) || 0; - var shapeY = (y.shape ? y.shape[0] : y.length) || 0; - var shapeZ = (z.shape ? z.shape[0] : z.length) || 0; - // TODO: what happens if one is missing? - len = Math.min(shapeX, shapeY, shapeZ); + len = Math.min(x.length, y.length, z.length); traceOut._length = traceOut._xlength = traceOut._ylength = traceOut._zlength = len; }