mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 06:18:12 +08:00
start at groups implementation for typedCrossfilter
This commit is contained in:
@@ -116,7 +116,7 @@ class BitArray {
|
|||||||
// all selection tests assume unallocated dimensions are zero valued.
|
// all selection tests assume unallocated dimensions are zero valued.
|
||||||
this.deselectAll(dim);
|
this.deselectAll(dim);
|
||||||
const col = dim >>> 5;
|
const col = dim >>> 5;
|
||||||
this.bitmask[col] &= ~(1 << (dim % 32));
|
this.bitmask[col] &= ~(1 << dim % 32);
|
||||||
this.dimensionCount--;
|
this.dimensionCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,35 @@ class BitArray {
|
|||||||
|
|
||||||
for (let w = 0; w < width; w++) {
|
for (let w = 0; w < width; w++) {
|
||||||
const bitmask = this.bitmask[w];
|
const bitmask = this.bitmask[w];
|
||||||
if (!bitmask || bitarray[w * length + index] !== bitmask) return false;
|
|
||||||
|
// Wha?? XXXX
|
||||||
|
// if (!bitmask || bitarray[w * length + index] !== bitmask) return false;
|
||||||
|
if (bitmask && bitarray[w * length + index] !== bitmask) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if this index is selected in ALL dimensions IGNORING dim
|
||||||
|
//
|
||||||
|
isSelectedIgnoringDim(index, dim) {
|
||||||
|
const ignoreOffset = dim >>> 5;
|
||||||
|
const ignoreMask = ~(1 << dim % 32);
|
||||||
|
|
||||||
|
const width = this.width;
|
||||||
|
const length = this.length;
|
||||||
|
const bitarray = this.bitarray;
|
||||||
|
|
||||||
|
for (let w = 0; w < width; w++) {
|
||||||
|
const bitmask = this.bitmask[w];
|
||||||
|
if (w === ignoreOffset) {
|
||||||
|
if (
|
||||||
|
bitmask &&
|
||||||
|
(bitarray[w * length + index] & ignoreMask) !== (bitmask & ignoreMask)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (bitmask && bitarray[w * length + index] !== bitmask) return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -139,7 +167,7 @@ class BitArray {
|
|||||||
selectOne(dim, index) {
|
selectOne(dim, index) {
|
||||||
const col = dim >>> 5;
|
const col = dim >>> 5;
|
||||||
const before = this.bitarray[col * this.length + index];
|
const before = this.bitarray[col * this.length + index];
|
||||||
const after = before | (1 << (dim % 32));
|
const after = before | (1 << dim % 32);
|
||||||
this.bitarray[col * this.length + index] = after;
|
this.bitarray[col * this.length + index] = after;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +176,7 @@ class BitArray {
|
|||||||
deselectOne(dim, index) {
|
deselectOne(dim, index) {
|
||||||
const col = dim >>> 5;
|
const col = dim >>> 5;
|
||||||
const before = this.bitarray[col * this.length + index];
|
const before = this.bitarray[col * this.length + index];
|
||||||
const after = before & ~(1 << (dim % 32));
|
const after = before & ~(1 << dim % 32);
|
||||||
this.bitarray[col * this.length + index] = after;
|
this.bitarray[col * this.length + index] = after;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +186,7 @@ class BitArray {
|
|||||||
let col = dim >> 5;
|
let col = dim >> 5;
|
||||||
const bitmask = this.bitmask[col];
|
const bitmask = this.bitmask[col];
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.bitarray;
|
||||||
const one = 1 << (dim % 32);
|
const one = 1 << dim % 32;
|
||||||
for (let i = col * this.length, len = i + this.length; i < len; i++) {
|
for (let i = col * this.length, len = i + this.length; i < len; i++) {
|
||||||
bitarray[i] |= one;
|
bitarray[i] |= one;
|
||||||
}
|
}
|
||||||
@@ -170,7 +198,7 @@ class BitArray {
|
|||||||
let col = dim >> 5;
|
let col = dim >> 5;
|
||||||
const bitmask = this.bitmask[col];
|
const bitmask = this.bitmask[col];
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.bitarray;
|
||||||
const zero = ~(1 << (dim % 32));
|
const zero = ~(1 << dim % 32);
|
||||||
for (let i = col * this.length, len = i + this.length; i < len; i++) {
|
for (let i = col * this.length, len = i + this.length; i < len; i++) {
|
||||||
bitarray[i] &= zero;
|
bitarray[i] &= zero;
|
||||||
}
|
}
|
||||||
@@ -184,7 +212,7 @@ class BitArray {
|
|||||||
const first = range[0];
|
const first = range[0];
|
||||||
const last = range[1];
|
const last = range[1];
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.bitarray;
|
||||||
const one = 1 << (dim % 32);
|
const one = 1 << dim % 32;
|
||||||
const offset = col * this.length;
|
const offset = col * this.length;
|
||||||
for (let i = first; i < last; i++) {
|
for (let i = first; i < last; i++) {
|
||||||
bitarray[offset + indirect[i]] |= one;
|
bitarray[offset + indirect[i]] |= one;
|
||||||
@@ -198,7 +226,7 @@ class BitArray {
|
|||||||
const first = range[0];
|
const first = range[0];
|
||||||
const last = range[1];
|
const last = range[1];
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.bitarray;
|
||||||
const zero = ~(1 << (dim % 32));
|
const zero = ~(1 << dim % 32);
|
||||||
const offset = col * this.length;
|
const offset = col * this.length;
|
||||||
for (let i = first; i < last; i++) {
|
for (let i = first; i < last; i++) {
|
||||||
bitarray[offset + indirect[i]] &= zero;
|
bitarray[offset + indirect[i]] &= zero;
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ class ScalarDimension {
|
|||||||
// create sort index
|
// create sort index
|
||||||
this.index = Util.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]);
|
this.index.sort((a, b) => array[a] - array[b]);
|
||||||
|
|
||||||
|
// groups, if any
|
||||||
|
this.groups = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_createValueArray(value, array) {
|
_createValueArray(value, array) {
|
||||||
@@ -134,12 +137,15 @@ class ScalarDimension {
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.crossfilter._freeDimension(this._id);
|
this.crossfilter._freeDimension(this._id);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
id() {
|
id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Argument is an array of intervals indicating records newly selected/filtered
|
||||||
|
//
|
||||||
_updateFilters(newFilter) {
|
_updateFilters(newFilter) {
|
||||||
newFilter = PositiveIntervals.canonicalize(newFilter);
|
newFilter = PositiveIntervals.canonicalize(newFilter);
|
||||||
|
|
||||||
@@ -323,6 +329,16 @@ class ScalarDimension {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
group(groupValue) {
|
||||||
|
const grp = new ScalarGroup(groupValue, this.value.constructor, this);
|
||||||
|
this.groups.push(grp);
|
||||||
|
return grp;
|
||||||
|
}
|
||||||
|
|
||||||
|
_freeGroup(group) {
|
||||||
|
this.groups = this.groups.filter(e => e !== group);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ordered enumeration - supports any sortable enumerable type, eg,
|
// Ordered enumeration - supports any sortable enumerable type, eg,
|
||||||
@@ -379,6 +395,121 @@ 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
|
||||||
|
//
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
// group index is mapping from data record index to group index
|
||||||
|
this.groupIndex = new Uint32Array(dimension.crossfilter.data.length);
|
||||||
|
|
||||||
|
// default to counting
|
||||||
|
this.reduceCount();
|
||||||
|
|
||||||
|
// Creates this.groups
|
||||||
|
this._reduce();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the group reduction incrementally
|
||||||
|
_updateReduce(adds, dels) {
|
||||||
|
// XXX
|
||||||
|
throw new Error("unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce the entire data set, creating both the group index and the
|
||||||
|
// groups data.
|
||||||
|
//
|
||||||
|
_reduce() {
|
||||||
|
const dimension = this.dimension;
|
||||||
|
const data = dimension.crossfilter.data;
|
||||||
|
|
||||||
|
// Create groups
|
||||||
|
const groupNames = new Set(this.mapValue);
|
||||||
|
this.groups = [];
|
||||||
|
const groupIndexByName = {};
|
||||||
|
groupNames.forEach(name => {
|
||||||
|
this.groups.push({ key: name, value: this.reduceInitial() });
|
||||||
|
groupIndexByName[name] = this.groups.length - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduce all filtered records, IGNORING the current dimension's filter
|
||||||
|
const selection = dimension.crossfilter.selection;
|
||||||
|
for (let i = 0, len = data.length; i < len; i++) {
|
||||||
|
if (selection.isSelectedIgnoringDim(i, dimension.id())) {
|
||||||
|
const group = this.groups[this.groupIndex[i]];
|
||||||
|
group.value = this.reduceAdd(group.value, data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.dimension._freeGroup(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return number of distinct values in the group, independent of any filters.
|
||||||
|
//
|
||||||
|
size() {
|
||||||
|
return this.groups.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the reduce functions and return the grouping.
|
||||||
|
//
|
||||||
|
reduce(add, remove, initial) {
|
||||||
|
this.reduceAdd = add;
|
||||||
|
this.reduceRemove = remove;
|
||||||
|
this.reduceInitial = initial;
|
||||||
|
this._reduce();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the reduce functions to count records.
|
||||||
|
reduceCount() {
|
||||||
|
return this.reduce((p, v) => p + 1, (p, v) => p - 1, () => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the reduce functions to sum records using specified value accessor.
|
||||||
|
//
|
||||||
|
reduceSum(value) {
|
||||||
|
return this.reduce((p, v) => p + value(v), (p, v) => p - value(v), () => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
all() {
|
||||||
|
const res = [...this.groups];
|
||||||
|
res.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrapper for backwards compat with crossfilter.
|
// Wrapper for backwards compat with crossfilter.
|
||||||
//
|
//
|
||||||
function crossfilter(data) {
|
function crossfilter(data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user