From 5400df25caed7dad595c234c516093250a8be790 Mon Sep 17 00:00:00 2001 From: bkmartinjr Date: Wed, 1 Aug 2018 16:32:31 -0700 Subject: [PATCH] finish group() implementation --- src/util/typedCrossfilter/bitArray.js | 5 +- src/util/typedCrossfilter/index.js | 151 ++++++++++++++++++++------ 2 files changed, 120 insertions(+), 36 deletions(-) diff --git a/src/util/typedCrossfilter/bitArray.js b/src/util/typedCrossfilter/bitArray.js index c022f4e2..667f359c 100644 --- a/src/util/typedCrossfilter/bitArray.js +++ b/src/util/typedCrossfilter/bitArray.js @@ -129,10 +129,7 @@ class BitArray { for (let w = 0; w < width; w++) { const bitmask = this.bitmask[w]; - - // Wha?? XXXX - // if (!bitmask || bitarray[w * length + index] !== bitmask) return false; - if (bitmask && bitarray[w * length + index] !== bitmask) return false; + if (!bitmask || bitarray[w * length + index] !== bitmask) return false; } return true; } diff --git a/src/util/typedCrossfilter/index.js b/src/util/typedCrossfilter/index.js index da8cfc5c..aae260b9 100644 --- a/src/util/typedCrossfilter/index.js +++ b/src/util/typedCrossfilter/index.js @@ -149,35 +149,49 @@ 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); - dels.forEach(interval => - this.crossfilter.selection.deselectIndirectFromRange( - this._id, - this.index, - interval - ) - ); - adds.forEach(interval => - this.crossfilter.selection.selectIndirectFromRange( - this._id, - this.index, - interval - ) - ); - } + // 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); + + this.crossfilter.filters.forEach(f => + f.dim.groups.forEach(grp => grp._updateReduceDel(this, dels)) + ); + + dels.forEach(interval => + this.crossfilter.selection.deselectIndirectFromRange( + this._id, + this.index, + interval + ) + ); + + adds.forEach(interval => + this.crossfilter.selection.selectIndirectFromRange( + this._id, + this.index, + interval + ) + ); + + this.crossfilter.filters.forEach(f => + f.dim.groups.forEach(grp => grp._updateReduceAdd(this, adds)) + ); + + // XXX removed optimization + // } this.currentFilter = newFilter; } @@ -393,13 +407,19 @@ class EnumDimension extends ScalarDimension { ) ); } + + group(groupValue) { + const grp = new EnumGroup(groupValue, this.value.constructor, this); + this.groups.push(grp); + return grp; + } } // Groups! Map/reduce // // XXX: potential optimizations not implemented // - groupValue may be identity - could skip creating separate group map -// - only works for scalars so far +// - only works for scalars so far (no enum) // class ScalarGroup { constructor(groupValue, groupValueType, dimension) { @@ -433,10 +453,60 @@ class ScalarGroup { this._reduce(); } - // Update the group reduction incrementally - _updateReduce(adds, dels) { - // XXX - throw new Error("unimplemented"); + // Update the group reduction incrementally. Called when *any* dimension filter + // changes. Guaranteed to be called AFTER the crossfilter is updated. + // + // Arguments: + // * dim: the dimension that is changing + // * 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; + + // Each item in the range was just added to `dim`. It was NOT previously + // selected - reduceAdd if it is now selected. + const selection = this.dimension.crossfilter.selection; + const data = this.dimension.crossfilter.data; + intv.forEach(rng => { + for (let r = rng[0]; r < rng[1]; r++) { + const i = dim.index[r]; + if (selection.isSelectedIgnoringDim(i, this.dimension.id())) { + const group = this.groups[this.groupIndex[i]]; + group.value = this.reduceAdd(group.value, data[i]); + } + } + }); + } + + // Update the group reduction incrementally. Called when *any* dimension filter + // changes. Guaranteed to be called BEFORE the crossfilter is updated. + // + // Arguments: + // * dim: the dimension that is changing + // * 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; + + // Each item in the range will be remved from `dim`. reduceRemove if it + // is currently selected. + const selection = this.dimension.crossfilter.selection; + const data = this.dimension.crossfilter.data; + intv.forEach(rng => { + for (let r = rng[0]; r < rng[1]; r++) { + const i = dim.index[r]; + if (selection.isSelectedIgnoringDim(i, this.dimension.id())) { + const group = this.groups[this.groupIndex[i]]; + group.value = this.reduceRemove(group.value, data[i]); + } + } + }); } // Reduce the entire data set, creating both the group index and the @@ -457,7 +527,6 @@ class ScalarGroup { // Create groupIndex - index map between data record index and group index for (let i = 0, len = this.mapValue.length; i < len; i++) { - // this.groupIndex[index[i]] = groupIndexByName[this.mapValue[i]]; this.groupIndex[i] = groupIndexByName[this.mapValue[i]]; } @@ -510,6 +579,24 @@ class ScalarGroup { } } +class EnumGroup extends ScalarGroup { + constructor(groupValue, groupValueType, dimension) { + super(groupValue, groupValueType, dimension); + } + + all() { + const res = []; + this.groups.forEach(e => + res.push({ + key: this.dimension.enumIndex[e.key], + value: e.value + }) + ); + res.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)); + return res; + } +} + // Wrapper for backwards compat with crossfilter. // function crossfilter(data) {