Skip to content

Commit 636e644

Browse files
committed
Added support for encoding TypedArrays as primitive objects (representation objects)
A TypedArray representation object has two properties: `dtype` and `data`. `dtype` is a string indicating the type of the typed array (`'int8'`, `'float32'`, `'uint16'`, etc.) `data` is a primitive JavaScript object that stores the typed array data. It can be one of: - Standard JavaScript Array - ArrayBuffer - DataView - A base64 encoded string The representation objects may stand in for TypedArrays in `data_type` properties and in properties with `arrayOk: true`. The representation object is stored in `data`/`layout`, while the converted TypedArray is stored in `_fullData`/`_fullLayout`
1 parent 7bba266 commit 636e644

File tree

5 files changed

+236
-4
lines changed

5 files changed

+236
-4
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"@plotly/d3-sankey": "^0.5.0",
6060
"alpha-shape": "^1.0.0",
6161
"array-range": "^1.0.1",
62+
"base64-arraybuffer": "^0.1.5",
6263
"canvas-fit": "^1.5.0",
6364
"color-normalize": "^1.0.3",
6465
"color-rgba": "^2.0.0",

src/lib/coerce.js

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ var nestedProperty = require('./nested_property');
1919
var counterRegex = require('./regex').counter;
2020
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
2121
var wrap180 = require('./angles').wrap180;
22-
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
22+
var isArray = require('./is_array');
23+
var isArrayOrTypedArray = isArray.isArrayOrTypedArray;
24+
var isTypedArray = isArray.isTypedArray;
25+
var isPrimitiveTypedArrayRepr = isArray.isPrimitiveTypedArrayRepr;
26+
var b64 = require('base64-arraybuffer');
2327

2428
exports.valObjectMeta = {
2529
data_array: {
@@ -33,8 +37,18 @@ exports.valObjectMeta = {
3337
otherOpts: ['dflt'],
3438
coerceFunction: function(v, propOut, dflt) {
3539
// TODO maybe `v: {type: 'float32', vals: [/* ... */]}` also
36-
if(isArrayOrTypedArray(v)) propOut.set(v);
37-
else if(dflt !== undefined) propOut.set(dflt);
40+
if(isArrayOrTypedArray(v)) {
41+
propOut.set(v);
42+
} else if(isPrimitiveTypedArrayRepr(v)) {
43+
var coercedV = primitiveTypedArrayReprToTypedArray(v);
44+
if(coercedV === undefined && dflt !== undefined) {
45+
propOut.set(dflt);
46+
} else {
47+
propOut.set(coercedV);
48+
}
49+
} else if(dflt !== undefined) {
50+
propOut.set(dflt);
51+
}
3852
}
3953
},
4054
enumerated: {
@@ -392,6 +406,10 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt
392406
if(opts.arrayOk && isArrayOrTypedArray(v)) {
393407
propOut.set(v);
394408
return v;
409+
} else if(opts.arrayOk && isPrimitiveTypedArrayRepr(v)) {
410+
var coercedV = primitiveTypedArrayReprToTypedArray(v);
411+
propOut.set(coercedV);
412+
return coercedV;
395413
}
396414

397415
var coerceFunction = exports.valObjectMeta[opts.valType].coerceFunction;
@@ -521,3 +539,48 @@ function validate(value, opts) {
521539
return out !== failed;
522540
}
523541
exports.validate = validate;
542+
543+
var dtypeStringToTypedarrayType = {
544+
int8: Int8Array,
545+
int16: Int16Array,
546+
int32: Int32Array,
547+
uint8: Uint8Array,
548+
uint16: Uint16Array,
549+
uint32: Uint32Array,
550+
float32: Float32Array,
551+
float64: Float64Array
552+
};
553+
554+
/**
555+
* Convert a primitive TypedArray representation object into a TypedArray
556+
* @param {object} v: Object with `dtype` and `data` properties that
557+
* represens a TypedArray.
558+
*
559+
* @returns {TypedArray}
560+
*/
561+
function primitiveTypedArrayReprToTypedArray(v) {
562+
// v has dtype and data properties
563+
564+
// Get TypedArray constructor type
565+
var TypeArrayType = dtypeStringToTypedarrayType[v.dtype];
566+
567+
// Process data
568+
var coercedV;
569+
var data = v.data;
570+
if(data instanceof ArrayBuffer) {
571+
// data is an ArrayBuffer
572+
coercedV = new TypeArrayType(data);
573+
} else if(data.constructor === DataView) {
574+
// data has a buffer property, where the buffer is an ArrayBuffer
575+
coercedV = new TypeArrayType(data.buffer);
576+
} else if(Array.isArray(data)) {
577+
// data is a primitive array
578+
coercedV = new TypeArrayType(data);
579+
} else if(typeof data === 'string' ||
580+
data instanceof String) {
581+
// data is a base64 encoded string
582+
var buffer = b64.decode(data);
583+
coercedV = new TypeArrayType(buffer);
584+
}
585+
return coercedV;
586+
}

src/lib/is_array.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ function isArray1D(a) {
3838
return !isArrayOrTypedArray(a[0]);
3939
}
4040

41+
function isPrimitiveTypedArrayRepr(a) {
42+
return (a !== undefined && a !== null &&
43+
typeof a === 'object' &&
44+
a.hasOwnProperty('dtype') && a.hasOwnProperty('data'));
45+
}
46+
4147
module.exports = {
4248
isTypedArray: isTypedArray,
4349
isArrayOrTypedArray: isArrayOrTypedArray,
44-
isArray1D: isArray1D
50+
isArray1D: isArray1D,
51+
isPrimitiveTypedArrayRepr: isPrimitiveTypedArrayRepr
4552
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"data": [{
3+
"type": "scatter",
4+
"x": {"dtype": "float64", "data": [3, 2, 1]},
5+
"y": {"dtype": "float32", "data": "AABAQAAAAEAAAIA/"},
6+
"marker": {
7+
"color": {
8+
"dtype": "uint16",
9+
"data": "AwACAAEA",
10+
},
11+
}
12+
}]
13+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
var Lib = require('@src/lib');
2+
var supplyDefaults = require('../assets/supply_defaults');
3+
var isTypedArray = require('../../../src/lib/is_array').isTypedArray;
4+
var b64 = require('base64-arraybuffer');
5+
var mock1 = require('@mocks/typed_array_repr_scatter.json');
6+
7+
var typedArraySpecs = [
8+
['int8', new Int8Array([-128, -34, 1, 127])],
9+
['uint8', new Uint8Array([0, 1, 127, 255])],
10+
['int16', new Int16Array([-32768, -123, 345, 32767])],
11+
['uint16', new Uint16Array([0, 345, 32767, 65535])],
12+
['int32', new Int32Array([-2147483648, -123, 345, 32767, 2147483647])],
13+
['uint32', new Uint32Array([0, 345, 32767, 4294967295])],
14+
['float32', new Float32Array([1.2E-38, -2345.25, 2.7182818, 3.1415926, 2, 3.4E38])],
15+
['float64', new Float64Array([5.0E-324, 2.718281828459045, 3.141592653589793, 1.8E308])]
16+
];
17+
18+
describe('Test TypedArray representations', function() {
19+
'use strict';
20+
21+
describe('ArrayBuffer', function() {
22+
it('should accept representation as ArrayBuffer', function() {
23+
typedArraySpecs.forEach(function(arraySpec) {
24+
// Build data and confirm its type
25+
var data = arraySpec[1].buffer;
26+
expect(data.constructor).toEqual(ArrayBuffer);
27+
28+
var repr = {
29+
dtype: arraySpec[0],
30+
data: data
31+
};
32+
var gd = {
33+
data: [{
34+
y: repr
35+
}],
36+
};
37+
38+
supplyDefaults(gd);
39+
40+
expect(gd.data[0].y).toEqual(repr);
41+
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
42+
});
43+
});
44+
});
45+
46+
describe('Array', function() {
47+
it('should accept representation as Array', function() {
48+
typedArraySpecs.forEach(function(arraySpec) {
49+
// Build data and confirm its type
50+
var data = Array.prototype.slice.call(arraySpec[1]);
51+
expect(Array.isArray(data)).toEqual(true);
52+
53+
var repr = {
54+
dtype: arraySpec[0],
55+
data: data
56+
};
57+
var gd = {
58+
data: [{
59+
y: repr
60+
}],
61+
};
62+
63+
supplyDefaults(gd);
64+
65+
expect(gd.data[0].y).toEqual(repr);
66+
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
67+
});
68+
});
69+
});
70+
71+
describe('DataView', function() {
72+
it('should accept representation as DataView', function() {
73+
typedArraySpecs.forEach(function(arraySpec) {
74+
// Build data and confirm its type
75+
var data = new DataView(arraySpec[1].buffer);
76+
expect(data.constructor).toEqual(DataView);
77+
78+
var repr = {
79+
dtype: arraySpec[0],
80+
data: data
81+
};
82+
var gd = {
83+
data: [{
84+
y: repr
85+
}],
86+
};
87+
88+
supplyDefaults(gd);
89+
90+
expect(gd.data[0].y).toEqual(repr);
91+
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
92+
});
93+
});
94+
});
95+
96+
describe('base64', function() {
97+
it('should accept representation as base 64 string', function() {
98+
typedArraySpecs.forEach(function(arraySpec) {
99+
// Build data and confirm its type
100+
var data = b64.encode(arraySpec[1].buffer);
101+
expect(typeof data).toEqual('string');
102+
103+
var repr = {
104+
dtype: arraySpec[0],
105+
data: data
106+
};
107+
var gd = {
108+
data: [{
109+
y: repr
110+
}],
111+
};
112+
113+
supplyDefaults(gd);
114+
expect(gd.data[0].y).toEqual(repr);
115+
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
116+
});
117+
});
118+
});
119+
120+
describe('mock', function() {
121+
it('should accept representation as base 64 and Array in Mock', function() {
122+
123+
var gd = Lib.extendDeep({}, mock1);
124+
supplyDefaults(gd);
125+
126+
// Check x
127+
// data_array property
128+
expect(gd.data[0].x).toEqual({
129+
'dtype': 'float64',
130+
'data': [3, 2, 1]});
131+
expect(gd._fullData[0].x).toEqual(new Float64Array([3, 2, 1]));
132+
133+
// Check y
134+
// data_array property
135+
expect(gd.data[0].y).toEqual({
136+
'dtype': 'float32',
137+
'data': 'AABAQAAAAEAAAIA/'});
138+
expect(gd._fullData[0].y).toEqual(new Float32Array([3, 2, 1]));
139+
140+
// Check marker.color
141+
// This is an arrayOk property not a data_array property
142+
expect(gd.data[0].marker.color).toEqual({
143+
'dtype': 'uint16',
144+
'data': 'AwACAAEA'});
145+
expect(gd._fullData[0].marker.color).toEqual(new Uint16Array([3, 2, 1]));
146+
});
147+
});
148+
});

0 commit comments

Comments
 (0)