mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-27 04:58:12 +08:00
crossfilters: reverse ES6 module change; add group support for enumerated dimensions
This commit is contained in:
@@ -251,4 +251,4 @@ class BitArray {
|
||||
}
|
||||
}
|
||||
|
||||
export default BitArray;
|
||||
module.exports = BitArray;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user