Skip to content

Commit a0d980e

Browse files
authored
Merge pull request #5308 from plotly/jonmmease_typed_array_encoding
Typed array encoding in supplyDefaults with Caching
2 parents dc9f4e5 + 2381deb commit a0d980e

File tree

7 files changed

+165
-98
lines changed

7 files changed

+165
-98
lines changed

Diff for: src/lib/array.js

+108
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88

99
'use strict';
10+
var b64 = require('base64-arraybuffer');
11+
var isPlainObject = require('./is_plain_object');
1012

1113
var isArray = Array.isArray;
1214

@@ -63,6 +65,112 @@ exports.ensureArray = function(out, n) {
6365
return out;
6466
};
6567

68+
var typedArrays = {
69+
int8: typeof Int8Array !== 'undefined' ? Int8Array : null,
70+
uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null,
71+
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null,
72+
int16: typeof Int16Array !== 'undefined' ? Int16Array : null,
73+
uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null,
74+
int32: typeof Int32Array !== 'undefined' ? Int32Array : null,
75+
uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null,
76+
float32: typeof Float32Array !== 'undefined' ? Float32Array : null,
77+
float64: typeof Float64Array !== 'undefined' ? Float64Array : null,
78+
};
79+
exports.typedArrays = typedArrays;
80+
81+
82+
exports.decodeTypedArraySpec = function(v) {
83+
// Assume processed by coerceTypedArraySpec
84+
v = coerceTypedArraySpec(v);
85+
var T = typedArrays[v.dtype];
86+
var buffer;
87+
if(v.bvals.constructor === ArrayBuffer) {
88+
// Already an ArrayBuffer
89+
buffer = v.bvals;
90+
} else {
91+
// Decode, assuming a string
92+
buffer = b64.decode(v.bvals);
93+
}
94+
95+
// Check if 1d shape. If so, we're done
96+
if(v.ndims === 1) {
97+
// Construct single Typed array over entire buffer
98+
return new T(buffer);
99+
} else {
100+
// Reshape into nested plain arrays with innermost
101+
// level containing typed arrays
102+
// We could eventually adopt an ndarray library
103+
104+
// Build cumulative product of dimensions
105+
var cumulativeShape = v.shape.map(function(a, i) {
106+
return a * (v.shape[i - 1] || 1);
107+
});
108+
109+
// Loop of dimensions in reverse order
110+
var nestedArray = [];
111+
for(var dimInd = v.ndims - 1; dimInd > 0; dimInd--) {
112+
var subArrayLength = v.shape[dimInd];
113+
var numSubArrays = cumulativeShape[dimInd - 1];
114+
var nextArray = [];
115+
116+
if(dimInd === v.ndims - 1) {
117+
// First time through, we build the
118+
// inner most typed arrays
119+
for(var typedInd = 0; typedInd < numSubArrays; typedInd++) {
120+
var typedOffset = typedInd * subArrayLength;
121+
nextArray.push(
122+
new T(buffer, typedOffset * T.BYTES_PER_ELEMENT, subArrayLength)
123+
);
124+
}
125+
} else {
126+
// Following times through, build
127+
// next layer of nested arrays
128+
for(var i = 0; i < numSubArrays; i++) {
129+
var offset = i * subArrayLength;
130+
nextArray.push(nextArray.slice(offset, offset + subArrayLength - 1));
131+
}
132+
}
133+
134+
// Update nested array with next nesting level
135+
nestedArray = nextArray;
136+
}
137+
138+
return nestedArray;
139+
}
140+
};
141+
142+
function isTypedArraySpec(v) {
143+
// Assume v has not passed through
144+
return isPlainObject(v) && typedArrays[v.dtype] && v.bvals && (
145+
Number.isInteger(v.shape) ||
146+
(isArrayOrTypedArray(v.shape) &&
147+
v.shape.length > 0 &&
148+
v.shape.every(function(d) { return Number.isInteger(d); }))
149+
);
150+
}
151+
exports.isTypedArraySpec = isTypedArraySpec;
152+
153+
function coerceTypedArraySpec(v) {
154+
// Assume isTypedArraySpec passed
155+
var coerced = {dtype: v.dtype, bvals: v.bvals};
156+
157+
// Normalize shape to a list
158+
if(Number.isInteger(v.shape)) {
159+
coerced.shape = [v.shape];
160+
} else {
161+
coerced.shape = v.shape;
162+
}
163+
164+
// Add length property
165+
coerced.length = v.shape.reduce(function(a, b) { return a * b; });
166+
167+
// Add ndims
168+
coerced.ndims = v.shape.length;
169+
170+
return coerced;
171+
}
172+
exports.coerceTypedArraySpec = coerceTypedArraySpec;
173+
66174
/*
67175
* TypedArray-compatible concatenation of n arrays
68176
* if all arrays are the same type it will preserve that type,

Diff for: src/lib/coerce.js

+41-19
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,11 @@ var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
1818
var nestedProperty = require('./nested_property');
1919
var counterRegex = require('./regex').counter;
2020
var modHalf = require('./mod').modHalf;
21-
var isPlainObject = require('./is_plain_object');
2221
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
22+
var isTypedArraySpec = require('./array').isTypedArraySpec;
23+
var decodeTypedArraySpec = require('./array').decodeTypedArraySpec;
24+
var coerceTypedArraySpec = require('./array').coerceTypedArraySpec;
2325

24-
var typedArrays = {
25-
int8: typeof Int8Array !== 'undefined' ? 1 : 0,
26-
uint8: typeof Uint8Array !== 'undefined' ? 1 : 0,
27-
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? 1 : 0,
28-
int16: typeof Int16Array !== 'undefined' ? 1 : 0,
29-
uint16: typeof Uint16Array !== 'undefined' ? 1 : 0,
30-
int32: typeof Int32Array !== 'undefined' ? 1 : 0,
31-
uint32: typeof Uint32Array !== 'undefined' ? 1 : 0,
32-
float32: typeof Float32Array !== 'undefined' ? 1 : 0,
33-
float64: typeof Float64Array !== 'undefined' ? 1 : 0,
34-
bigint64: typeof BigInt64Array !== 'undefined' ? 1 : 0,
35-
biguint64: typeof BigUint64Array !== 'undefined' ? 1 : 0
36-
};
3726

3827
exports.valObjectMeta = {
3928
data_array: {
@@ -53,12 +42,45 @@ exports.valObjectMeta = {
5342
if(isArrayOrTypedArray(v)) {
5443
propOut.set(v);
5544
wasSet = true;
56-
} else if(isPlainObject(v)) {
57-
var T = typedArrays[v.dtype];
58-
if(T) {
59-
propOut.set(v);
60-
wasSet = true;
45+
} else if(isTypedArraySpec(v)) {
46+
// Copy and coerce spec
47+
v = coerceTypedArraySpec(v);
48+
49+
// See if caching location is available
50+
var uid = propOut.obj.uid;
51+
var module = propOut.obj._module;
52+
53+
if(v.bvals.constructor === ArrayBuffer || !uid || !module) {
54+
// Already an ArrayBuffer
55+
// decoding is cheap
56+
propOut.set(decodeTypedArraySpec(v));
57+
} else {
58+
var prop = propOut.astr;
59+
var cache = module._b64BufferCache || {};
60+
61+
// Check cache
62+
var cachedBuffer = ((cache[uid] || {})[prop] || {})[v.bvals];
63+
if(cachedBuffer !== undefined) {
64+
// Use cached array buffer instead of base64 encoded
65+
// string
66+
v.bvals = cachedBuffer;
67+
propOut.set(decodeTypedArraySpec(v));
68+
} else {
69+
// Not in so cache decode
70+
var decoded = decodeTypedArraySpec(v);
71+
propOut.set(decoded);
72+
73+
// Update cache for next time
74+
cache[uid] = (cache[uid] || {});
75+
76+
// Clear any prior cache value (only store one per
77+
// trace property
78+
cache[uid][prop] = {};
79+
cache[uid][prop][v.bvals] = decoded.buffer;
80+
module._b64BufferCache = cache;
81+
}
6182
}
83+
wasSet = true;
6284
}
6385
if(!wasSet && dflt !== undefined) propOut.set(dflt);
6486
}

Diff for: src/plots/plots.js

-44
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
var d3 = require('d3');
1212
var timeFormatLocale = require('d3-time-format').timeFormatLocale;
1313
var isNumeric = require('fast-isnumeric');
14-
var b64 = require('base64-arraybuffer');
1514

1615
var Registry = require('../registry');
1716
var PlotSchema = require('../plot_api/plot_schema');
@@ -2849,50 +2848,7 @@ function _transition(gd, transitionOpts, opts) {
28492848
return transitionStarting.then(function() { return gd; });
28502849
}
28512850

2852-
var typedArrays = {
2853-
int8: typeof Int8Array !== 'undefined' ? Int8Array : null,
2854-
uint8: typeof Uint8Array !== 'undefined' ? Uint8Array : null,
2855-
uint8clamped: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : null,
2856-
int16: typeof Int16Array !== 'undefined' ? Int16Array : null,
2857-
uint16: typeof Uint16Array !== 'undefined' ? Uint16Array : null,
2858-
int32: typeof Int32Array !== 'undefined' ? Int32Array : null,
2859-
uint32: typeof Uint32Array !== 'undefined' ? Uint32Array : null,
2860-
float32: typeof Float32Array !== 'undefined' ? Float32Array : null,
2861-
float64: typeof Float64Array !== 'undefined' ? Float64Array : null,
2862-
bigint64: typeof BigInt64Array !== 'undefined' ? BigInt64Array : null,
2863-
biguint64: typeof BigUint64Array !== 'undefined' ? BigUint64Array : null
2864-
};
2865-
2866-
function _decode(cont) {
2867-
if(cont.dtype && cont.bvals) {
2868-
var T = typedArrays[cont.dtype];
2869-
if(T) {
2870-
return new T(b64.decode(cont.bvals));
2871-
}
2872-
}
2873-
2874-
for(var prop in cont) {
2875-
if(prop[0] !== '_' && cont.hasOwnProperty(prop)) {
2876-
var item = cont[prop];
2877-
if(Lib.isPlainObject(item)) {
2878-
var r = _decode(item);
2879-
if(r !== undefined) cont[prop] = r;
2880-
}
2881-
}
2882-
}
2883-
}
2884-
2885-
function decodeB64Arrays(gd) {
2886-
for(var i = 0; i < gd._fullData.length; i++) {
2887-
_decode(gd._fullData[i]);
2888-
}
2889-
2890-
_decode(gd._fullLayout);
2891-
}
2892-
28932851
plots.doCalcdata = function(gd, traces) {
2894-
decodeB64Arrays(gd);
2895-
28962852
var axList = axisIDs.list(gd);
28972853
var fullData = gd._fullData;
28982854
var fullLayout = gd._fullLayout;

Diff for: src/traces/heatmap/xyz_defaults.js

+5-11
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,19 @@ module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, x
1919
yName = yName || 'y';
2020
var x, y;
2121

22-
var shapeX = x ? (x.shape ? x.shape[0] : x.length) || 0 : 0;
23-
var shapeY = y ? (y.shape ? y.shape[0] : y.length) || 0 : 0;
24-
var shapeZ = z ? (z.shape ? z.shape[0] : z.length) || 0 : 0;
22+
if(z === undefined || !z.length) return 0;
2523

26-
var zlen = shapeZ || (z && z.length) || 0;
27-
28-
if(z === undefined || !zlen) return 0;
29-
30-
if(Lib.isArray1D(traceIn.z) || (z && z.shape && z.shape.length === 1)) {
24+
if(Lib.isArray1D(z)) {
3125
x = coerce(xName);
3226
y = coerce(yName);
3327

34-
var xlen = shapeX || Lib.minRowLength(x);
35-
var ylen = shapeY || Lib.minRowLength(y);
28+
var xlen = Lib.minRowLength(x);
29+
var ylen = Lib.minRowLength(y);
3630

3731
// column z must be accompanied by xName and yName arrays
3832
if(xlen === 0 || ylen === 0) return 0;
3933

40-
traceOut._length = Math.min(xlen, ylen, zlen);
34+
traceOut._length = Math.min(xlen, ylen, z.length);
4135
} else {
4236
x = coordDefaults(xName, coerce);
4337
y = coordDefaults(yName, coerce);

Diff for: src/traces/isosurface/defaults.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,12 @@ function supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
3838
var z = coerce('z');
3939
var value = coerce('value');
4040

41-
var len = 0;
42-
43-
if(x && y && z && value) {
44-
len = Math.min(
45-
(x.shape ? x.shape[0] : x.length) || 0,
46-
(y.shape ? y.shape[0] : y.length) || 0,
47-
(z.shape ? z.shape[0] : z.length) || 0,
48-
(value.shape ? value.shape[0] : value.length) || 0
49-
);
50-
}
51-
52-
if(!len) {
41+
if(
42+
!x || !x.length ||
43+
!y || !y.length ||
44+
!z || !z.length ||
45+
!value || !value.length
46+
) {
5347
traceOut.visible = false;
5448
return;
5549
}

Diff for: src/traces/scatter/xy_defaults.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,19 @@ module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
1919
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
2020
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
2121

22-
var shapeX = x && x.shape ? x.shape[0] : 0;
23-
var shapeY = y && y.shape ? y.shape[0] : 0;
24-
2522
if(x) {
26-
var xlen = shapeX || Lib.minRowLength(x);
23+
var xlen = Lib.minRowLength(x);
2724
if(y) {
28-
len = shapeY || Math.min(xlen, Lib.minRowLength(y));
25+
len = Math.min(xlen, Lib.minRowLength(y));
2926
} else {
30-
len = shapeX || xlen;
27+
len = xlen;
3128
coerce('y0');
3229
coerce('dy');
3330
}
3431
} else {
3532
if(!y) return 0;
3633

37-
len = shapeY || Lib.minRowLength(y);
34+
len = Lib.minRowLength(y);
3835
coerce('x0');
3936
coerce('dx');
4037
}

Diff for: src/traces/scatter3d/defaults.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,8 @@ function handleXYZDefaults(traceIn, traceOut, coerce, layout) {
7878
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
7979

8080
if(x && y && z) {
81-
var shapeX = (x.shape ? x.shape[0] : x.length) || 0;
82-
var shapeY = (y.shape ? y.shape[0] : y.length) || 0;
83-
var shapeZ = (z.shape ? z.shape[0] : z.length) || 0;
84-
8581
// TODO: what happens if one is missing?
86-
len = Math.min(shapeX, shapeY, shapeZ);
82+
len = Math.min(x.length, y.length, z.length);
8783
traceOut._length = traceOut._xlength = traceOut._ylength = traceOut._zlength = len;
8884
}
8985

0 commit comments

Comments
 (0)