Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for encoding TypedArrays as primitive objects for serialization #2911

Closed
wants to merge 11 commits into from
69 changes: 3 additions & 66 deletions src/lib/coerce.js
Expand Up @@ -19,10 +19,7 @@ var nestedProperty = require('./nested_property');
var counterRegex = require('./regex').counter;
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
var wrap180 = require('./angles').wrap180;
var isArray = require('./is_array');
var isArrayOrTypedArray = isArray.isArrayOrTypedArray;
var isPrimitiveTypedArrayRepr = isArray.isPrimitiveTypedArrayRepr;
var b64 = require('base64-arraybuffer');
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;

exports.valObjectMeta = {
data_array: {
Expand All @@ -36,18 +33,8 @@ exports.valObjectMeta = {
otherOpts: ['dflt'],
coerceFunction: function(v, propOut, dflt) {
// TODO maybe `v: {type: 'float32', vals: [/* ... */]}` also
if(isArrayOrTypedArray(v)) {
propOut.set(v);
} else if(isPrimitiveTypedArrayRepr(v)) {
var coercedV = primitiveTypedArrayReprToTypedArray(v);
if(coercedV === undefined && dflt !== undefined) {
propOut.set(dflt);
} else {
propOut.set(coercedV);
}
} else if(dflt !== undefined) {
propOut.set(dflt);
}
if(isArrayOrTypedArray(v)) propOut.set(v);
else if(dflt !== undefined) propOut.set(dflt);
}
},
enumerated: {
Expand Down Expand Up @@ -405,10 +392,6 @@ exports.coerce = function(containerIn, containerOut, attributes, attribute, dflt
if(opts.arrayOk && isArrayOrTypedArray(v)) {
propOut.set(v);
return v;
} else if(opts.arrayOk && isPrimitiveTypedArrayRepr(v)) {
var coercedV = primitiveTypedArrayReprToTypedArray(v);
propOut.set(coercedV);
return coercedV;
}

var coerceFunction = exports.valObjectMeta[opts.valType].coerceFunction;
Expand Down Expand Up @@ -538,49 +521,3 @@ function validate(value, opts) {
return out !== failed;
}
exports.validate = validate;

var dtypeStringToTypedarrayType = {
int8: Int8Array,
int16: Int16Array,
int32: Int32Array,
uint8: Uint8Array,
uint8_clamped: Uint8ClampedArray,
uint16: Uint16Array,
uint32: Uint32Array,
float32: Float32Array,
float64: Float64Array
};

/**
* Convert a primitive TypedArray representation object into a TypedArray
* @param {object} v: Object with `dtype` and `data` properties that
* represens a TypedArray.
*
* @returns {TypedArray}
*/
function primitiveTypedArrayReprToTypedArray(v) {
// v has dtype and data properties

// Get TypedArray constructor type
var TypeArrayType = dtypeStringToTypedarrayType[v.dtype];

// Process data
var coercedV;
var value = v.value;
if(value instanceof ArrayBuffer) {
// value is an ArrayBuffer
coercedV = new TypeArrayType(value);
} else if(value.constructor === DataView) {
// value has a buffer property, where the buffer is an ArrayBuffer
coercedV = new TypeArrayType(value.buffer);
} else if(Array.isArray(value)) {
// value is a primitive array
coercedV = new TypeArrayType(value);
} else if(typeof value === 'string' ||
value instanceof String) {
// value is a base64 encoded string
var buffer = b64.decode(value);
coercedV = new TypeArrayType(buffer);
}
return coercedV;
}
1 change: 1 addition & 0 deletions src/lib/index.js
Expand Up @@ -31,6 +31,7 @@ var isArrayModule = require('./is_array');
lib.isTypedArray = isArrayModule.isTypedArray;
lib.isArrayOrTypedArray = isArrayModule.isArrayOrTypedArray;
lib.isArray1D = isArrayModule.isArray1D;
lib.isPrimitiveTypedArrayRepr = isArrayModule.isPrimitiveTypedArrayRepr;

var coerceModule = require('./coerce');
lib.valObjectMeta = coerceModule.valObjectMeta;
Expand Down
1 change: 1 addition & 0 deletions src/plot_api/index.js
Expand Up @@ -16,6 +16,7 @@ exports.restyle = main.restyle;
exports.relayout = main.relayout;
exports.redraw = main.redraw;
exports.update = main.update;
exports.import = main.import;
exports.react = main.react;
exports.extendTraces = main.extendTraces;
exports.prependTraces = main.prependTraces;
Expand Down
77 changes: 77 additions & 0 deletions src/plot_api/plot_api.js
Expand Up @@ -13,6 +13,7 @@
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var hasHover = require('has-hover');
var b64 = require('base64-arraybuffer');

var Lib = require('../lib');
var Events = require('../lib/events');
Expand Down Expand Up @@ -2156,6 +2157,82 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
});
};

/**
* Convert a primitive TypedArray representation object into a TypedArray
* @param {object} v: Object with `dtype` and `data` properties that
* represens a TypedArray.
*
* @returns {TypedArray}
*/
var dtypeStringToTypedarrayType = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to append this list:

plotly.js/.eslintrc

Lines 12 to 21 in a8c6217

"globals": {
"Promise": true,
"Float32Array": true,
"Float64Array": true,
"Uint8Array": true,
"Int16Array": true,
"Int32Array": true,
"ArrayBuffer": true,
"DataView": true,
"SVGElement": false

to make npm run lint pass.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we'll need to add fallbacks so that browsers w/o typed array support don't break.

We have a "test" for that:

npm run test-jasmine -- --bundleTest=ie9_test.js

int8: Int8Array,
int16: Int16Array,
int32: Int32Array,
uint8: Uint8Array,
uint8_clamped: Uint8ClampedArray,
uint16: Uint16Array,
uint32: Uint32Array,
float32: Float32Array,
float64: Float64Array
};

function primitiveTypedArrayReprToTypedArray(v) {
// v has dtype and data properties

// Get TypedArray constructor type
var TypeArrayType = dtypeStringToTypedarrayType[v.dtype];

// Process data
var coercedV;
var value = v.value;
if(value instanceof ArrayBuffer) {
// value is an ArrayBuffer
coercedV = new TypeArrayType(value);
} else if(value.constructor === DataView) {
// value has a buffer property, where the buffer is an ArrayBuffer
coercedV = new TypeArrayType(value.buffer);
} else if(Array.isArray(value)) {
// value is a primitive array
coercedV = new TypeArrayType(value);
} else if(typeof value === 'string' ||
value instanceof String) {
// value is a base64 encoded string
var buffer = b64.decode(value);
coercedV = new TypeArrayType(buffer);
}
return coercedV;
}

function performImport(v) {
if(Lib.isPrimitiveTypedArrayRepr(v)) {
return primitiveTypedArrayReprToTypedArray(v);
} else if(Lib.isTypedArray(v)) {
return v;
} else if(Array.isArray(v)) {
return v.map(performImport);
} else if(Lib.isPlainObject(v)) {
var result = {};
for(var k in v) {
if(v.hasOwnProperty(k)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we may list the keys using Object.getOwnPropertyNames and loop through them.

result[k] = performImport(v[k]);
}
}
return result;
} else {
return v;
}
}

/**
* Plotly.import:
* Import an object or array into... TODO
* @param v
* @returns {object}
*/
exports.import = function(v) {
return performImport(v);
};

/**
* Plotly.react:
* A plot/update method that takes the full plot state (same API as plot/newPlot)
Expand Down
47 changes: 21 additions & 26 deletions test/jasmine/tests/primitive_typed_array_repr_test.js
@@ -1,3 +1,4 @@
var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var supplyDefaults = require('../assets/supply_defaults');
var b64 = require('base64-arraybuffer');
Expand Down Expand Up @@ -29,16 +30,15 @@ describe('Test TypedArray representations', function() {
dtype: arraySpec[0],
value: value
};
var gd = {
var raw = {
data: [{
y: repr
}],
};

supplyDefaults(gd);
var gd = Plotly.import(raw);

expect(gd.data[0].y).toEqual(repr);
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
expect(gd.data[0].y).toEqual(arraySpec[1]);
});
});
});
Expand All @@ -54,16 +54,15 @@ describe('Test TypedArray representations', function() {
dtype: arraySpec[0],
value: value
};
var gd = {
var raw = {
data: [{
y: repr
}],
};

supplyDefaults(gd);
var gd = Plotly.import(raw);

expect(gd.data[0].y).toEqual(repr);
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
expect(gd.data[0].y).toEqual(arraySpec[1]);
});
});
});
Expand All @@ -79,16 +78,15 @@ describe('Test TypedArray representations', function() {
dtype: arraySpec[0],
value: value
};
var gd = {
var raw = {
data: [{
y: repr
}],
};

supplyDefaults(gd);
var gd = Plotly.import(raw);

expect(gd.data[0].y).toEqual(repr);
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
expect(gd.data[0].y).toEqual(arraySpec[1]);
});
});
});
Expand All @@ -104,45 +102,42 @@ describe('Test TypedArray representations', function() {
dtype: arraySpec[0],
value: value
};
var gd = {
var raw = {
data: [{
y: repr
}],
};

supplyDefaults(gd);
expect(gd.data[0].y).toEqual(repr);
expect(gd._fullData[0].y).toEqual(arraySpec[1]);
var gd = Plotly.import(raw);
expect(gd.data[0].y).toEqual(arraySpec[1]);
});
});
});

describe('mock', function() {
it('should accept representation as base 64 and Array in Mock', function() {

var gd = Lib.extendDeep({}, mock1);
supplyDefaults(gd);
it('should import representation as base 64 and Array in Mock', function() {

var gd = Plotly.import(mock1);
// Check x
// data_array property
expect(gd.data[0].x).toEqual({
expect(mock1.data[0].x).toEqual({
'dtype': 'float64',
'value': [3, 2, 1]});
expect(gd._fullData[0].x).toEqual(new Float64Array([3, 2, 1]));
expect(gd.data[0].x).toEqual(new Float64Array([3, 2, 1]));

// Check y
// data_array property
expect(gd.data[0].y).toEqual({
expect(mock1.data[0].y).toEqual({
'dtype': 'float32',
'value': 'AABAQAAAAEAAAIA/'});
expect(gd._fullData[0].y).toEqual(new Float32Array([3, 2, 1]));
expect(gd.data[0].y).toEqual(new Float32Array([3, 2, 1]));

// Check marker.color
// This is an arrayOk property not a data_array property
expect(gd.data[0].marker.color).toEqual({
expect(mock1.data[0].marker.color).toEqual({
'dtype': 'uint16',
'value': 'AwACAAEA'});
expect(gd._fullData[0].marker.color).toEqual(new Uint16Array([3, 2, 1]));
expect(gd.data[0].marker.color).toEqual(new Uint16Array([3, 2, 1]));
});
});
});