Skip to content

Commit

Permalink
Merge pull request #3211 from plotly/hist-typedarray
Browse files Browse the repository at this point in the history
Histogram TypedArray support
  • Loading branch information
alexcjohnson authored Nov 5, 2018
2 parents e40c807 + 1ca72c3 commit 03a12e0
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 84 deletions.
134 changes: 134 additions & 0 deletions src/lib/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var isArray = Array.isArray;

// IE9 fallbacks

var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ?
{isView: function() { return false; }} :
ArrayBuffer;

var dv = (typeof DataView === 'undefined') ?
function() {} :
DataView;

function isTypedArray(a) {
return ab.isView(a) && !(a instanceof dv);
}
exports.isTypedArray = isTypedArray;

function isArrayOrTypedArray(a) {
return isArray(a) || isTypedArray(a);
}
exports.isArrayOrTypedArray = isArrayOrTypedArray;

/*
* Test whether an input object is 1D.
*
* Assumes we already know the object is an array.
*
* Looks only at the first element, if the dimensionality is
* not consistent we won't figure that out here.
*/
function isArray1D(a) {
return !isArrayOrTypedArray(a[0]);
}
exports.isArray1D = isArray1D;

/*
* Ensures an array has the right amount of storage space. If it doesn't
* exist, it creates an array. If it does exist, it returns it if too
* short or truncates it in-place.
*
* The goal is to just reuse memory to avoid a bit of excessive garbage
* collection.
*/
exports.ensureArray = function(out, n) {
// TODO: typed array support here? This is only used in
// traces/carpet/compute_control_points
if(!isArray(out)) out = [];

// If too long, truncate. (If too short, it will grow
// automatically so we don't care about that case)
out.length = n;

return out;
};

/*
* TypedArray-compatible concatenation of n arrays
* if all arrays are the same type it will preserve that type,
* otherwise it falls back on Array.
* Also tries to avoid copying, in case one array has zero length
* But never mutates an existing array
*/
exports.concat = function() {
var args = [];
var allArray = true;
var totalLen = 0;

var _constructor, arg0, i, argi, posi, leni, out, j;

for(i = 0; i < arguments.length; i++) {
argi = arguments[i];
leni = argi.length;
if(leni) {
if(arg0) args.push(argi);
else {
arg0 = argi;
posi = leni;
}

if(isArray(argi)) {
_constructor = false;
}
else {
allArray = false;
if(!totalLen) {
_constructor = argi.constructor;
}
else if(_constructor !== argi.constructor) {
// TODO: in principle we could upgrade here,
// ie keep typed array but convert all to Float64Array?
_constructor = false;
}
}

totalLen += leni;
}
}

if(!totalLen) return [];
if(!args.length) return arg0;

if(allArray) return arg0.concat.apply(arg0, args);
if(_constructor) {
// matching typed arrays
out = new _constructor(totalLen);
out.set(arg0);
for(i = 0; i < args.length; i++) {
argi = args[i];
out.set(argi, posi);
posi += argi.length;
}
return out;
}

// mismatched types or Array + typed
out = new Array(totalLen);
for(j = 0; j < arg0.length; j++) out[j] = arg0[j];
for(i = 0; i < args.length; i++) {
argi = args[i];
for(j = 0; j < argi.length; j++) out[posi + j] = argi[j];
posi += j;
}
return out;
};
2 changes: 1 addition & 1 deletion src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var nestedProperty = require('./nested_property');
var counterRegex = require('./regex').counter;
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
var modHalf = require('./mod').modHalf;
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;

exports.valObjectMeta = {
data_array: {
Expand Down
27 changes: 0 additions & 27 deletions src/lib/ensure_array.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/gl_format_color.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var rgba = require('color-normalize');

var Colorscale = require('../components/colorscale');
var colorDflt = require('../components/color/attributes').defaultLine;
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;

var colorDfltRgba = rgba(colorDflt);
var opacityDflt = 1;
Expand Down
13 changes: 7 additions & 6 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ lib.relativeAttr = require('./relative_attr');
lib.isPlainObject = require('./is_plain_object');
lib.toLogRange = require('./to_log_range');
lib.relinkPrivateKeys = require('./relink_private');
lib.ensureArray = require('./ensure_array');

var arrayModule = require('./array');
lib.isTypedArray = arrayModule.isTypedArray;
lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray;
lib.isArray1D = arrayModule.isArray1D;
lib.ensureArray = arrayModule.ensureArray;
lib.concat = arrayModule.concat;

var modModule = require('./mod');
lib.mod = modModule.mod;
lib.modHalf = modModule.modHalf;

var isArrayModule = require('./is_array');
lib.isTypedArray = isArrayModule.isTypedArray;
lib.isArrayOrTypedArray = isArrayModule.isArrayOrTypedArray;
lib.isArray1D = isArrayModule.isArray1D;

var coerceModule = require('./coerce');
lib.valObjectMeta = coerceModule.valObjectMeta;
lib.coerce = coerceModule.coerce;
Expand Down
45 changes: 0 additions & 45 deletions src/lib/is_array.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/nested_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'use strict';

var isNumeric = require('fast-isnumeric');
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;

/**
* convert a string s (such as 'xaxis.range[0]')
Expand Down
2 changes: 1 addition & 1 deletion src/lib/relink_private.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
var isPlainObject = require('./is_plain_object');

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
'use strict';

var isNumeric = require('fast-isnumeric');
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;

/**
* aggNums() returns the result of an aggregate function applied to an array of
Expand Down
2 changes: 1 addition & 1 deletion src/traces/histogram/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
for(i = 0; i < traces.length; i++) {
tracei = traces[i];
pos0 = tracei._pos0 = pa.makeCalcdata(tracei, mainData);
allPos = allPos.concat(pos0);
allPos = Lib.concat(allPos, pos0);
delete tracei._autoBinFinished;
if(trace.visible === true) {
if(isFirstVisible) {
Expand Down
10 changes: 10 additions & 0 deletions test/jasmine/tests/histogram_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,16 @@ describe('Test histogram', function() {
expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5);
});

it('can handle TypedArrays', function() {
var trace1 = {x: new Float32Array([1, 2, 3, 4])};
var trace2 = {x: new Float32Array([5, 5.5, 6, 6.5])};
var trace3 = {x: new Float64Array([1, 1.1, 1.2, 1.3]), xaxis: 'x2'};
var trace4 = {x: new Float64Array([1, 1.2, 1.4, 1.6]), yaxis: 'y2'};

expect(calcPositions(trace1, [trace2, trace3, trace4])).toEqual([1, 3, 5]);
expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5);
});

describe('cumulative distribution functions', function() {
var base = {
x: [0, 5, 10, 15, 5, 10, 15, 10, 15, 15],
Expand Down
82 changes: 82 additions & 0 deletions test/jasmine/tests/lib_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,88 @@ describe('Test lib.js:', function() {
expect(toContainer).toEqual(expected);
});
});

describe('concat', function() {
var concat = Lib.concat;

beforeEach(function() {
spyOn(Array.prototype, 'concat').and.callThrough();
});

it('works with multiple Arrays', function() {
var res = concat([1], [[2], 3], [{a: 4}, 5, 6]);
expect(Array.prototype.concat.calls.count()).toBe(1);

// note: can't `concat` in the `expect` if we want to count native
// `Array.concat calls`, because `toEqual` calls `Array.concat`
// profusely itself.
expect(res).toEqual([1, [2], 3, {a: 4}, 5, 6]);
});

it('works with some empty arrays', function() {
var a1 = [1];
var res = concat(a1, [], [2, 3]);
expect(Array.prototype.concat.calls.count()).toBe(1);
expect(res).toEqual([1, 2, 3]);
expect(a1).toEqual([1]); // did not mutate a1

Array.prototype.concat.calls.reset();
var a1b = concat(a1, []);
var a1c = concat([], a1b);
var a1d = concat([], a1c, []);
expect(Array.prototype.concat.calls.count()).toBe(0);

expect(a1d).toEqual([1]);
// does not mutate a1, but *will* return it unchanged if it's the
// only one with data
expect(a1d).toBe(a1);

expect(concat([], [0], [1, 0], [2, 0, 0])).toEqual([0, 1, 0, 2, 0, 0]);

// a single typedArray will keep its identity (and type)
// even if other empty arrays don't match type.
var f1 = new Float32Array([1, 2]);
Array.prototype.concat.calls.reset();
res = concat([], f1, new Float64Array([]));
expect(Array.prototype.concat.calls.count()).toBe(0);
expect(res).toBe(f1);
expect(f1).toEqual(new Float32Array([1, 2]));
});

it('works with all empty arrays', function() {
[[], [[]], [[], []], [[], [], [], []]].forEach(function(empties) {
Array.prototype.concat.calls.reset();
var res = concat.apply(null, empties);
expect(Array.prototype.concat.calls.count()).toBe(0);
expect(res).toEqual([]);
});
});

it('converts mismatched types to Array', function() {
[
[[1, 2], new Float64Array([3, 4])],
[new Float64Array([1, 2]), [3, 4]],
[new Float64Array([1, 2]), new Float32Array([3, 4])]
].forEach(function(mismatch) {
Array.prototype.concat.calls.reset();
var res = concat.apply(null, mismatch);
// no concat - all entries moved over individually
expect(Array.prototype.concat.calls.count()).toBe(0);
expect(res).toEqual([1, 2, 3, 4]);
});
});

it('concatenates matching TypedArrays preserving type', function() {
[Float32Array, Float64Array, Int16Array, Int32Array].forEach(function(Type, i) {
var v = i * 10;
Array.prototype.concat.calls.reset();
var res = concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v]));
// no concat - uses `TypedArray.set`
expect(Array.prototype.concat.calls.count()).toBe(0);
expect(res).toEqual(new Type([v, v + 1, v, v + 2, v, v]));
});
});
});
});

describe('Queue', function() {
Expand Down

0 comments on commit 03a12e0

Please sign in to comment.