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

Histogram TypedArray support #3211

Merged
merged 4 commits into from
Nov 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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