crossfilters: reverse ES6 module change; add group support for enumerated dimensions

This commit is contained in:
bkmartinjr
2018-08-04 13:05:50 -07:00
parent f839653c00
commit 8f56f0ce6e
4 changed files with 77 additions and 63 deletions
+1 -1
View File
@@ -251,4 +251,4 @@ class BitArray {
}
}
export default BitArray;
module.exports = BitArray;
+62 -56
View File
@@ -29,9 +29,20 @@ more complex API. In a few cases, elements of that API were incorporated.
*/
import PositiveIntervals from "./positiveIntervals";
import BitArray from "./bitArray";
import {fillRange, lowerBound, lowerBoundIndirect, upperBound, upperBoundIndirect} from "./util";
var PositiveIntervals = require("./positiveIntervals");
var BitArray = require("./bitArray");
var Util = require("./util");
class NotImplementedError extends Error {
constructor(...params) {
super(...params);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, NotImplementedError);
}
}
}
class TypedCrossfilter {
constructor(data) {
@@ -118,7 +129,7 @@ class ScalarDimension {
this.value = array;
// create sort index
this.index = fillRange(new Uint32Array(this.crossfilter.data.length));
this.index = Util.fillRange(new Uint32Array(this.crossfilter.data.length));
this.index.sort((a, b) => array[a] - array[b]);
// groups, if any
@@ -149,20 +160,6 @@ class ScalarDimension {
_updateFilters(newFilter) {
newFilter = PositiveIntervals.canonicalize(newFilter);
// XXX removed optimization for now
// special case optimization - select all/none can bypass
// more complex work and just clobber everything.
//
// if (newFilter.length === 0) {
// this.crossfilter.selection.deselectAll(this._id);
// } else if (
// newFilter.length === 1 &&
// newFilter[0][0] === 0 &&
// newFilter[0][1] == this.index.length
// ) {
// this.crossfilter.selection.selectAll(this._id);
// } else {
const adds = PositiveIntervals.difference(newFilter, this.currentFilter);
const dels = PositiveIntervals.difference(this.currentFilter, newFilter);
@@ -190,23 +187,20 @@ class ScalarDimension {
f.dim.groups.forEach(grp => grp._updateReduceAdd(this, adds))
);
// XXX removed optimization
// }
this.currentFilter = newFilter;
}
// filter by value - exact match
filterExact(value) {
const newFilter = [
lowerBoundIndirect(
Util.lowerBoundIndirect(
this.value,
this.index,
value,
0,
this.value.length
),
upperBoundIndirect(
Util.upperBoundIndirect(
this.value,
this.index,
value,
@@ -227,14 +221,14 @@ class ScalarDimension {
const newFilter = [];
for (let v = 0, len = values.length; v < len; v++) {
const intv = [
lowerBoundIndirect(
Util.lowerBoundIndirect(
this.value,
this.index,
values[v],
0,
this.value.length
),
upperBoundIndirect(
Util.upperBoundIndirect(
this.value,
this.index,
values[v],
@@ -253,14 +247,14 @@ class ScalarDimension {
filterRange(range) {
const newFilter = [];
const intv = [
lowerBoundIndirect(
Util.lowerBoundIndirect(
this.value,
this.index,
range[0],
0,
this.value.length
),
upperBoundIndirect(
Util.upperBoundIndirect(
this.value,
this.index,
range[1],
@@ -380,7 +374,7 @@ class EnumDimension extends ScalarDimension {
const enumLen = this.enumIndex.length;
for (let i = 0; i < len; i++) {
const v = value(data[i]);
const e = lowerBound(this.enumIndex, v, 0, enumLen);
const e = Util.lowerBound(this.enumIndex, v, 0, enumLen);
array[i] = e;
}
return array;
@@ -388,14 +382,14 @@ class EnumDimension extends ScalarDimension {
filterExact(value) {
return super.filterExact(
lowerBound(this.enumIndex, value, 0, this.enumIndex.length)
Util.lowerBound(this.enumIndex, value, 0, this.enumIndex.length)
);
}
filterEnum(values) {
return super.filterEnum(
values.map(v =>
lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
Util.lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
)
);
}
@@ -403,7 +397,7 @@ class EnumDimension extends ScalarDimension {
filterRange(range) {
return super.filterEnum(
range.map(v =>
lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
Util.lowerBound(this.enumIndex, v, 0, this.enumIndex.length)
)
);
}
@@ -417,31 +411,13 @@ class EnumDimension extends ScalarDimension {
// Groups! Map/reduce
//
// XXX: potential optimizations not implemented
// - groupValue may be identity - could skip creating separate group map
// - only works for scalars so far (no enum)
//
class ScalarGroup {
constructor(groupValue, groupValueType, dimension) {
// parent dimension
this.dimension = dimension;
// groupValue is optional. Defaults to identity. Used to perform
// initial map operation.
//
if (groupValue === undefined) {
this.mapValue = dimension.value;
} else {
const data = dimension.value;
const len = data.length;
const array = new groupValueType(dimension.value.length);
for (let i = 0; i < len; i++) {
array[i] = groupValue(data[i]);
}
this.mapValue = array;
}
this.groups = [];
// generate group names from dimension values
this.mapValue = this._map(groupValue, groupValueType, dimension);
// group index is mapping from data record index to group index
this.groupIndex = new Uint32Array(dimension.crossfilter.data.length);
@@ -453,6 +429,24 @@ class ScalarGroup {
this._reduce();
}
// internal support function - map all dimension values to group values.
//
_map(groupValue, groupValueType, dimension) {
// groupValue is optional. Defaults to identity. Used to perform
// initial map operation.
//
// identity: save some memory...
if (groupValue === undefined) return dimension.value;
const data = dimension.value;
const len = data.length;
const mapValue = new groupValueType(dimension.value.length);
for (let i = 0; i < len; i++) {
mapValue[i] = groupValue(data[i]);
}
return mapValue;
}
// Update the group reduction incrementally. Called when *any* dimension filter
// changes. Guaranteed to be called AFTER the crossfilter is updated.
//
@@ -461,8 +455,6 @@ class ScalarGroup {
// * intv: interval list of newly selected values on `dim` (adds)
//
_updateReduceAdd(dim, intv) {
// console.log("_updateReduceAdd", dim, intv);
// ignore updates to self, as we don't reduce inclusive of our filter
if (dim === this.dimension || intv.length === 0) return;
@@ -489,8 +481,6 @@ class ScalarGroup {
// * intv: interval list of previously selected values on `dim` (dels)
//
_updateReduceDel(dim, intv) {
// console.log("_updateReduceDel", dim, intv);
// ignore updates to self, as we don't reduce inclusive of our filter
if (dim === this.dimension || intv.length === 0) return;
@@ -584,10 +574,26 @@ class EnumGroup extends ScalarGroup {
super(groupValue, groupValueType, dimension);
}
_map(groupValue, groupValueType, dimension) {
// groupValue is optional. Defaults to identity. Used to perform
// initial map operation.
//
// identity: save some memory
if (groupValue === undefined) return dimension.value;
// non-identity mapping unsupported for EnumDimension/EnumGroup.
// XXX: this could be implemented, but would require another index
// array to map from the group names/keys back to the dimension values.
// With this, we just rely on the dimensions `enumIndex` to map from
// enumeration value to the record.
throw new NotImplementedError("enumerated group mapping not implemented");
}
all() {
const res = [];
this.groups.forEach(e =>
res.push({
// XXX: assumes identity group map - see comment in _map()
key: this.dimension.enumIndex[e.key],
value: e.value
})
@@ -609,4 +615,4 @@ crossfilter.TypedCrossfilter = TypedCrossfilter;
crossfilter.ScalarDimension = ScalarDimension;
crossfilter.EnumDimension = EnumDimension;
export default crossfilter;
module.exports = crossfilter;
@@ -129,4 +129,4 @@ class PositiveIntervals {
}
}
export default PositiveIntervals;
module.exports = PositiveIntervals;
+13 -5
View File
@@ -8,7 +8,7 @@
// fill an array or typedarray with a sequential range of numbers,
// starting with `start`
//
export function fillRange(arr, start = 0) {
function fillRange(arr, start = 0) {
for (let i = 0, len = arr.length; i < len; i++) {
arr[i] = i + start;
}
@@ -30,7 +30,7 @@ export function fillRange(arr, start = 0) {
// a factory version of lowerBound that takes an accessor (rather than having
// a special-cased version for lining the indirection).
//
export function lowerBound(valueArray, value, first, last) {
function lowerBound(valueArray, value, first, last) {
// this is just a binary search
while (first < last) {
const middle = (first + last) >>> 1;
@@ -45,7 +45,7 @@ export function lowerBound(valueArray, value, first, last) {
// Inlined performance optimization - used to indirect through a sort map.
//
export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
// this is just a binary search
while (first < last) {
const middle = (first + last) >>> 1;
@@ -69,7 +69,7 @@ export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
// C++: upper_bound()
// Python: bisect.bisect_right()
//
export function upperBound(valueArray, value, first, last) {
function upperBound(valueArray, value, first, last) {
// this is just a binary search
while (first < last) {
const middle = (first + last) >>> 1;
@@ -84,7 +84,7 @@ export function upperBound(valueArray, value, first, last) {
// Inline performance optimization
//
export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
function upperBoundIndirect(valueArray, indexArray, value, first, last) {
// this is just a binary search
while (first < last) {
const middle = (first + last) >>> 1;
@@ -96,3 +96,11 @@ export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
}
return first;
}
module.exports = {
fillRange,
lowerBound,
lowerBoundIndirect,
upperBound,
upperBoundIndirect
};