Skip to content

Commit 03a12e0

Browse files
authored
Merge pull request #3211 from plotly/hist-typedarray
Histogram TypedArray support
2 parents e40c807 + 1ca72c3 commit 03a12e0

File tree

12 files changed

+239
-84
lines changed

12 files changed

+239
-84
lines changed

Diff for: src/lib/array.js

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var isArray = Array.isArray;
12+
13+
// IE9 fallbacks
14+
15+
var ab = (typeof ArrayBuffer === 'undefined' || !ArrayBuffer.isView) ?
16+
{isView: function() { return false; }} :
17+
ArrayBuffer;
18+
19+
var dv = (typeof DataView === 'undefined') ?
20+
function() {} :
21+
DataView;
22+
23+
function isTypedArray(a) {
24+
return ab.isView(a) && !(a instanceof dv);
25+
}
26+
exports.isTypedArray = isTypedArray;
27+
28+
function isArrayOrTypedArray(a) {
29+
return isArray(a) || isTypedArray(a);
30+
}
31+
exports.isArrayOrTypedArray = isArrayOrTypedArray;
32+
33+
/*
34+
* Test whether an input object is 1D.
35+
*
36+
* Assumes we already know the object is an array.
37+
*
38+
* Looks only at the first element, if the dimensionality is
39+
* not consistent we won't figure that out here.
40+
*/
41+
function isArray1D(a) {
42+
return !isArrayOrTypedArray(a[0]);
43+
}
44+
exports.isArray1D = isArray1D;
45+
46+
/*
47+
* Ensures an array has the right amount of storage space. If it doesn't
48+
* exist, it creates an array. If it does exist, it returns it if too
49+
* short or truncates it in-place.
50+
*
51+
* The goal is to just reuse memory to avoid a bit of excessive garbage
52+
* collection.
53+
*/
54+
exports.ensureArray = function(out, n) {
55+
// TODO: typed array support here? This is only used in
56+
// traces/carpet/compute_control_points
57+
if(!isArray(out)) out = [];
58+
59+
// If too long, truncate. (If too short, it will grow
60+
// automatically so we don't care about that case)
61+
out.length = n;
62+
63+
return out;
64+
};
65+
66+
/*
67+
* TypedArray-compatible concatenation of n arrays
68+
* if all arrays are the same type it will preserve that type,
69+
* otherwise it falls back on Array.
70+
* Also tries to avoid copying, in case one array has zero length
71+
* But never mutates an existing array
72+
*/
73+
exports.concat = function() {
74+
var args = [];
75+
var allArray = true;
76+
var totalLen = 0;
77+
78+
var _constructor, arg0, i, argi, posi, leni, out, j;
79+
80+
for(i = 0; i < arguments.length; i++) {
81+
argi = arguments[i];
82+
leni = argi.length;
83+
if(leni) {
84+
if(arg0) args.push(argi);
85+
else {
86+
arg0 = argi;
87+
posi = leni;
88+
}
89+
90+
if(isArray(argi)) {
91+
_constructor = false;
92+
}
93+
else {
94+
allArray = false;
95+
if(!totalLen) {
96+
_constructor = argi.constructor;
97+
}
98+
else if(_constructor !== argi.constructor) {
99+
// TODO: in principle we could upgrade here,
100+
// ie keep typed array but convert all to Float64Array?
101+
_constructor = false;
102+
}
103+
}
104+
105+
totalLen += leni;
106+
}
107+
}
108+
109+
if(!totalLen) return [];
110+
if(!args.length) return arg0;
111+
112+
if(allArray) return arg0.concat.apply(arg0, args);
113+
if(_constructor) {
114+
// matching typed arrays
115+
out = new _constructor(totalLen);
116+
out.set(arg0);
117+
for(i = 0; i < args.length; i++) {
118+
argi = args[i];
119+
out.set(argi, posi);
120+
posi += argi.length;
121+
}
122+
return out;
123+
}
124+
125+
// mismatched types or Array + typed
126+
out = new Array(totalLen);
127+
for(j = 0; j < arg0.length; j++) out[j] = arg0[j];
128+
for(i = 0; i < args.length; i++) {
129+
argi = args[i];
130+
for(j = 0; j < argi.length; j++) out[posi + j] = argi[j];
131+
posi += j;
132+
}
133+
return out;
134+
};

Diff for: src/lib/coerce.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var nestedProperty = require('./nested_property');
1919
var counterRegex = require('./regex').counter;
2020
var DESELECTDIM = require('../constants/interactions').DESELECTDIM;
2121
var modHalf = require('./mod').modHalf;
22-
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
22+
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
2323

2424
exports.valObjectMeta = {
2525
data_array: {

Diff for: src/lib/ensure_array.js

-27
This file was deleted.

Diff for: src/lib/gl_format_color.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var rgba = require('color-normalize');
1515

1616
var Colorscale = require('../components/colorscale');
1717
var colorDflt = require('../components/color/attributes').defaultLine;
18-
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
18+
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
1919

2020
var colorDfltRgba = rgba(colorDflt);
2121
var opacityDflt = 1;

Diff for: src/lib/index.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,18 @@ lib.relativeAttr = require('./relative_attr');
2424
lib.isPlainObject = require('./is_plain_object');
2525
lib.toLogRange = require('./to_log_range');
2626
lib.relinkPrivateKeys = require('./relink_private');
27-
lib.ensureArray = require('./ensure_array');
27+
28+
var arrayModule = require('./array');
29+
lib.isTypedArray = arrayModule.isTypedArray;
30+
lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray;
31+
lib.isArray1D = arrayModule.isArray1D;
32+
lib.ensureArray = arrayModule.ensureArray;
33+
lib.concat = arrayModule.concat;
2834

2935
var modModule = require('./mod');
3036
lib.mod = modModule.mod;
3137
lib.modHalf = modModule.modHalf;
3238

33-
var isArrayModule = require('./is_array');
34-
lib.isTypedArray = isArrayModule.isTypedArray;
35-
lib.isArrayOrTypedArray = isArrayModule.isArrayOrTypedArray;
36-
lib.isArray1D = isArrayModule.isArray1D;
37-
3839
var coerceModule = require('./coerce');
3940
lib.valObjectMeta = coerceModule.valObjectMeta;
4041
lib.coerce = coerceModule.coerce;

Diff for: src/lib/is_array.js

-45
This file was deleted.

Diff for: src/lib/nested_property.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'use strict';
1111

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

1515
/**
1616
* convert a string s (such as 'xaxis.range[0]')

Diff for: src/lib/relink_private.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
'use strict';
1111

12-
var isArrayOrTypedArray = require('./is_array').isArrayOrTypedArray;
12+
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;
1313
var isPlainObject = require('./is_plain_object');
1414

1515
/**

Diff for: src/lib/stats.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'use strict';
1111

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

1515
/**
1616
* aggNums() returns the result of an aggregate function applied to an array of

Diff for: src/traces/histogram/calc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
252252
for(i = 0; i < traces.length; i++) {
253253
tracei = traces[i];
254254
pos0 = tracei._pos0 = pa.makeCalcdata(tracei, mainData);
255-
allPos = allPos.concat(pos0);
255+
allPos = Lib.concat(allPos, pos0);
256256
delete tracei._autoBinFinished;
257257
if(trace.visible === true) {
258258
if(isFirstVisible) {

Diff for: test/jasmine/tests/histogram_test.js

+10
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,16 @@ describe('Test histogram', function() {
536536
expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5);
537537
});
538538

539+
it('can handle TypedArrays', function() {
540+
var trace1 = {x: new Float32Array([1, 2, 3, 4])};
541+
var trace2 = {x: new Float32Array([5, 5.5, 6, 6.5])};
542+
var trace3 = {x: new Float64Array([1, 1.1, 1.2, 1.3]), xaxis: 'x2'};
543+
var trace4 = {x: new Float64Array([1, 1.2, 1.4, 1.6]), yaxis: 'y2'};
544+
545+
expect(calcPositions(trace1, [trace2, trace3, trace4])).toEqual([1, 3, 5]);
546+
expect(calcPositions(trace3)).toBeCloseToArray([1.1, 1.3], 5);
547+
});
548+
539549
describe('cumulative distribution functions', function() {
540550
var base = {
541551
x: [0, 5, 10, 15, 5, 10, 15, 10, 15, 15],

Diff for: test/jasmine/tests/lib_test.js

+82
Original file line numberDiff line numberDiff line change
@@ -2459,6 +2459,88 @@ describe('Test lib.js:', function() {
24592459
expect(toContainer).toEqual(expected);
24602460
});
24612461
});
2462+
2463+
describe('concat', function() {
2464+
var concat = Lib.concat;
2465+
2466+
beforeEach(function() {
2467+
spyOn(Array.prototype, 'concat').and.callThrough();
2468+
});
2469+
2470+
it('works with multiple Arrays', function() {
2471+
var res = concat([1], [[2], 3], [{a: 4}, 5, 6]);
2472+
expect(Array.prototype.concat.calls.count()).toBe(1);
2473+
2474+
// note: can't `concat` in the `expect` if we want to count native
2475+
// `Array.concat calls`, because `toEqual` calls `Array.concat`
2476+
// profusely itself.
2477+
expect(res).toEqual([1, [2], 3, {a: 4}, 5, 6]);
2478+
});
2479+
2480+
it('works with some empty arrays', function() {
2481+
var a1 = [1];
2482+
var res = concat(a1, [], [2, 3]);
2483+
expect(Array.prototype.concat.calls.count()).toBe(1);
2484+
expect(res).toEqual([1, 2, 3]);
2485+
expect(a1).toEqual([1]); // did not mutate a1
2486+
2487+
Array.prototype.concat.calls.reset();
2488+
var a1b = concat(a1, []);
2489+
var a1c = concat([], a1b);
2490+
var a1d = concat([], a1c, []);
2491+
expect(Array.prototype.concat.calls.count()).toBe(0);
2492+
2493+
expect(a1d).toEqual([1]);
2494+
// does not mutate a1, but *will* return it unchanged if it's the
2495+
// only one with data
2496+
expect(a1d).toBe(a1);
2497+
2498+
expect(concat([], [0], [1, 0], [2, 0, 0])).toEqual([0, 1, 0, 2, 0, 0]);
2499+
2500+
// a single typedArray will keep its identity (and type)
2501+
// even if other empty arrays don't match type.
2502+
var f1 = new Float32Array([1, 2]);
2503+
Array.prototype.concat.calls.reset();
2504+
res = concat([], f1, new Float64Array([]));
2505+
expect(Array.prototype.concat.calls.count()).toBe(0);
2506+
expect(res).toBe(f1);
2507+
expect(f1).toEqual(new Float32Array([1, 2]));
2508+
});
2509+
2510+
it('works with all empty arrays', function() {
2511+
[[], [[]], [[], []], [[], [], [], []]].forEach(function(empties) {
2512+
Array.prototype.concat.calls.reset();
2513+
var res = concat.apply(null, empties);
2514+
expect(Array.prototype.concat.calls.count()).toBe(0);
2515+
expect(res).toEqual([]);
2516+
});
2517+
});
2518+
2519+
it('converts mismatched types to Array', function() {
2520+
[
2521+
[[1, 2], new Float64Array([3, 4])],
2522+
[new Float64Array([1, 2]), [3, 4]],
2523+
[new Float64Array([1, 2]), new Float32Array([3, 4])]
2524+
].forEach(function(mismatch) {
2525+
Array.prototype.concat.calls.reset();
2526+
var res = concat.apply(null, mismatch);
2527+
// no concat - all entries moved over individually
2528+
expect(Array.prototype.concat.calls.count()).toBe(0);
2529+
expect(res).toEqual([1, 2, 3, 4]);
2530+
});
2531+
});
2532+
2533+
it('concatenates matching TypedArrays preserving type', function() {
2534+
[Float32Array, Float64Array, Int16Array, Int32Array].forEach(function(Type, i) {
2535+
var v = i * 10;
2536+
Array.prototype.concat.calls.reset();
2537+
var res = concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v]));
2538+
// no concat - uses `TypedArray.set`
2539+
expect(Array.prototype.concat.calls.count()).toBe(0);
2540+
expect(res).toEqual(new Type([v, v + 1, v, v + 2, v, v]));
2541+
});
2542+
});
2543+
});
24622544
});
24632545

24642546
describe('Queue', function() {

0 commit comments

Comments
 (0)