mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-05 20:28:12 +08:00
Merge branch 'master' into csweaver/api-v2-init
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 4.4 KiB |
+31
-31
@@ -1,31 +1,31 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
import { scaleRGB } from "./scaleRGB";
|
import { scaleRGB } from "./scaleRGB";
|
||||||
|
|
||||||
// maintain a cache of already parsed RGB names, as it is reasonably expensive
|
// maintain a cache of already parsed RGB names, as it is reasonably expensive
|
||||||
// to do this operation. This lets us have speed, but keep the pleasant ability
|
// to do this operation. This lets us have speed, but keep the pleasant ability
|
||||||
// to talk about colors by their text description eg, 'rgb(0,0,1)'
|
// to talk about colors by their text description eg, 'rgb(0,0,1)'
|
||||||
//
|
//
|
||||||
const colorCache = new Object(null); // no prototype
|
const colorCache = new Object(null); // no prototype
|
||||||
|
|
||||||
function parseColorName(c) {
|
function parseColorName(c) {
|
||||||
if (c[0] !== "#") {
|
if (c[0] !== "#") {
|
||||||
const _c = c.replace(/[^\d,.]/g, "").split(",");
|
const _c = c.replace(/[^\d,.]/g, "").split(",");
|
||||||
return [scaleRGB(+_c[0]), scaleRGB(+_c[1]), scaleRGB(+_c[2])];
|
return [scaleRGB(+_c[0]), scaleRGB(+_c[1]), scaleRGB(+_c[2])];
|
||||||
} else {
|
} else {
|
||||||
var parsedHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
var parsedHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
||||||
return [
|
return [
|
||||||
scaleRGB(parseInt(parsedHex[1], 16)),
|
scaleRGB(parseInt(parsedHex[1], 16)),
|
||||||
scaleRGB(parseInt(parsedHex[2], 16)),
|
scaleRGB(parseInt(parsedHex[2], 16)),
|
||||||
scaleRGB(parseInt(parsedHex[3], 16))
|
scaleRGB(parseInt(parsedHex[3], 16))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseRGB = c => {
|
export const parseRGB = c => {
|
||||||
var cv = colorCache[c];
|
var cv = colorCache[c];
|
||||||
if (!cv) {
|
if (!cv) {
|
||||||
cv = parseColorName(c);
|
cv = parseColorName(c);
|
||||||
colorCache[c] = cv;
|
colorCache[c] = cv;
|
||||||
}
|
}
|
||||||
return cv;
|
return cv;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
|
|
||||||
// Substitute for a d3 linear scale - less flexible, more performant.
|
// Substitute for a d3 linear scale - less flexible, more performant.
|
||||||
// Returns a function which will scale a value.
|
// Returns a function which will scale a value.
|
||||||
//
|
//
|
||||||
// Example will scale [0,1] to [-1,1]
|
// Example will scale [0,1] to [-1,1]
|
||||||
// var myScale = scaleLinear([0, 1], [-1, 1]);
|
// var myScale = scaleLinear([0, 1], [-1, 1]);
|
||||||
// myScale(0) === -1
|
// myScale(0) === -1
|
||||||
// this is is equivalent to d3.scaleLinear().domain([0,1]).range([-1,1])
|
// this is is equivalent to d3.scaleLinear().domain([0,1]).range([-1,1])
|
||||||
|
|
||||||
export const scaleLinear = (domain, range) => {
|
export const scaleLinear = (domain, range) => {
|
||||||
const domainStart = domain[0];
|
const domainStart = domain[0];
|
||||||
const scale = (range[1] - range[0]) / (domain[1] - domain[0]);
|
const scale = (range[1] - range[0]) / (domain[1] - domain[0]);
|
||||||
const rangeStart = range[0];
|
const rangeStart = range[0];
|
||||||
return function(value) {
|
return function(value) {
|
||||||
return (value - domainStart) * scale + rangeStart;
|
return (value - domainStart) * scale + rangeStart;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+41
-41
@@ -1,41 +1,41 @@
|
|||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
|
|
||||||
// In the case where the REST server does not implement data schema
|
// In the case where the REST server does not implement data schema
|
||||||
// declaration, we attempt to deduce it by sniffing the data.
|
// declaration, we attempt to deduce it by sniffing the data.
|
||||||
//
|
//
|
||||||
export function createSchemaByDataSniffing(ranges) {
|
export function createSchemaByDataSniffing(ranges) {
|
||||||
let schema = {};
|
let schema = {};
|
||||||
_.forEach(ranges, (value, key) => {
|
_.forEach(ranges, (value, key) => {
|
||||||
schema[key] = {
|
schema[key] = {
|
||||||
displayname: key,
|
displayname: key,
|
||||||
variabletype: value.options ? "categorical" : "continuous"
|
variabletype: value.options ? "categorical" : "continuous"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metadata field type is inferred by sniffing the data. This has some risks.
|
// Metadata field type is inferred by sniffing the data. This has some risks.
|
||||||
// Caveats:
|
// Caveats:
|
||||||
// * Values have been converted to native JS objects by the JSON parser.
|
// * Values have been converted to native JS objects by the JSON parser.
|
||||||
// * Lots of assumptions about he REST API behaving properly (eg, min/max
|
// * Lots of assumptions about he REST API behaving properly (eg, min/max
|
||||||
// are the same type, etc).
|
// are the same type, etc).
|
||||||
let type;
|
let type;
|
||||||
if (schema[key].variabletype === "continuous" && value.range) {
|
if (schema[key].variabletype === "continuous" && value.range) {
|
||||||
// Use min/max as a proxy for all data.
|
// Use min/max as a proxy for all data.
|
||||||
const min = value.range.min;
|
const min = value.range.min;
|
||||||
const max = value.range.max;
|
const max = value.range.max;
|
||||||
type =
|
type =
|
||||||
typeof min !== "number" || typeof max !== "number"
|
typeof min !== "number" || typeof max !== "number"
|
||||||
? "string"
|
? "string"
|
||||||
: Number.isSafeInteger(min) && Number.isSafeInteger(max)
|
: Number.isSafeInteger(min) && Number.isSafeInteger(max)
|
||||||
? "int"
|
? "int"
|
||||||
: "float";
|
: "float";
|
||||||
} else {
|
} else {
|
||||||
// use an option value as a proxy for all data
|
// use an option value as a proxy for all data
|
||||||
const aVal = value.options[0];
|
const aVal = value.options[0];
|
||||||
type =
|
type =
|
||||||
typeof aVal !== "number"
|
typeof aVal !== "number"
|
||||||
? "string"
|
? "string"
|
||||||
: Number.isSafeInteger(aVal) ? "int" : "float";
|
: Number.isSafeInteger(aVal) ? "int" : "float";
|
||||||
}
|
}
|
||||||
schema[key].type = type;
|
schema[key].type = type;
|
||||||
});
|
});
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,254 +1,254 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
|
|
||||||
// BitArray is a 2D bitarray with size [length, nBitWidth].
|
// BitArray is a 2D bitarray with size [length, nBitWidth].
|
||||||
// Each bit is referred to as a `dimension`. Dimensions may be
|
// Each bit is referred to as a `dimension`. Dimensions may be
|
||||||
// dynamically allocated and deallocated. The overall length
|
// dynamically allocated and deallocated. The overall length
|
||||||
// of the BitArray is fixed at creation time (for simplicity).
|
// of the BitArray is fixed at creation time (for simplicity).
|
||||||
//
|
//
|
||||||
// Organization of the bitarray is dimension-major. As dimensions
|
// Organization of the bitarray is dimension-major. As dimensions
|
||||||
// are added, the underlying store is grown 32 bits at a time.
|
// are added, the underlying store is grown 32 bits at a time.
|
||||||
// NOTE: currently does not deallocate / shrink.
|
// NOTE: currently does not deallocate / shrink.
|
||||||
//
|
//
|
||||||
// Primary operations on the BitArray are:
|
// Primary operations on the BitArray are:
|
||||||
// - set & clear dimension
|
// - set & clear dimension
|
||||||
// - test dimension
|
// - test dimension
|
||||||
// - various performance or convenience operations to optimize bulk ops
|
// - various performance or convenience operations to optimize bulk ops
|
||||||
//
|
//
|
||||||
// The underlying data structure uses TypedArrays for performance.
|
// The underlying data structure uses TypedArrays for performance.
|
||||||
//
|
//
|
||||||
class BitArray {
|
class BitArray {
|
||||||
constructor(length) {
|
constructor(length) {
|
||||||
// Initially allocate a 32 bit wide array. allocDimension() will expand
|
// Initially allocate a 32 bit wide array. allocDimension() will expand
|
||||||
// as necessary.
|
// as necessary.
|
||||||
//
|
//
|
||||||
// Int32Array is (counterintuitively) used to accomadate JS numeric casting
|
// Int32Array is (counterintuitively) used to accomadate JS numeric casting
|
||||||
// (to/from primitive number type).
|
// (to/from primitive number type).
|
||||||
//
|
//
|
||||||
|
|
||||||
// Fixed for the life of this object.
|
// Fixed for the life of this object.
|
||||||
this.length = length;
|
this.length = length;
|
||||||
|
|
||||||
// Bitarray width. width is always greater than 32*dimensionCount.
|
// Bitarray width. width is always greater than 32*dimensionCount.
|
||||||
this.width = 1; // underlying number of 32 bit arrays
|
this.width = 1; // underlying number of 32 bit arrays
|
||||||
this.dimensionCount = 0; // num allocated dimensions
|
this.dimensionCount = 0; // num allocated dimensions
|
||||||
|
|
||||||
this.bitmask = new Int32Array(this.width); // dimension allocation mask
|
this.bitmask = new Int32Array(this.width); // dimension allocation mask
|
||||||
this.bitarray = new Int32Array(this.width * this.length);
|
this.bitarray = new Int32Array(this.width * this.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the number of records that are selected, ie, have a one bit in
|
// Return the number of records that are selected, ie, have a one bit in
|
||||||
// all allocated dimensions.
|
// all allocated dimensions.
|
||||||
//
|
//
|
||||||
get selectionCount() {
|
get selectionCount() {
|
||||||
return this.countAllOnes();
|
return this.countAllOnes();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count all records that have a 'one' bit in allocated dimensions.
|
// Count all records that have a 'one' bit in allocated dimensions.
|
||||||
//
|
//
|
||||||
countAllOnes() {
|
countAllOnes() {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (let i = 0; i < this.width; i++) {
|
for (let i = 0; i < this.width; i++) {
|
||||||
const bitmask = this.bitmask[i];
|
const bitmask = this.bitmask[i];
|
||||||
for (let j = i * this.length, len = j + this.length; j < len; j++) {
|
for (let j = i * this.length, len = j + this.length; j < len; j++) {
|
||||||
if (this.bitarray[i * this.length + j] === bitmask) count++;
|
if (this.bitarray[i * this.length + j] === bitmask) count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// count trailing zeros - hard to do fast in JS!
|
// count trailing zeros - hard to do fast in JS!
|
||||||
// https://en.wikipedia.org/wiki/Find_first_set#CTZ
|
// https://en.wikipedia.org/wiki/Find_first_set#CTZ
|
||||||
static ctz(v) {
|
static ctz(v) {
|
||||||
let c = 32;
|
let c = 32;
|
||||||
v &= -v; // isolate lowest non-zero bit
|
v &= -v; // isolate lowest non-zero bit
|
||||||
if (v) c--;
|
if (v) c--;
|
||||||
if (v & 0x0000ffff) c -= 16;
|
if (v & 0x0000ffff) c -= 16;
|
||||||
if (v & 0x00ff00ff) c -= 8;
|
if (v & 0x00ff00ff) c -= 8;
|
||||||
if (v & 0x0f0f0f0f) c -= 4;
|
if (v & 0x0f0f0f0f) c -= 4;
|
||||||
if (v & 0x33333333) c -= 2;
|
if (v & 0x33333333) c -= 2;
|
||||||
if (v & 0x55555555) c -= 1;
|
if (v & 0x55555555) c -= 1;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find a free dimension. Return undefined if none
|
// find a free dimension. Return undefined if none
|
||||||
_findFreeDimension() {
|
_findFreeDimension() {
|
||||||
let dim;
|
let dim;
|
||||||
for (let col = 0; col < this.width; col++) {
|
for (let col = 0; col < this.width; col++) {
|
||||||
const bitmask = this.bitmask[col];
|
const bitmask = this.bitmask[col];
|
||||||
const lowestZeroBit = ~this.bitmask[col] & -~this.bitmask[col];
|
const lowestZeroBit = ~this.bitmask[col] & -~this.bitmask[col];
|
||||||
if (lowestZeroBit) {
|
if (lowestZeroBit) {
|
||||||
this.bitmask[col] |= lowestZeroBit;
|
this.bitmask[col] |= lowestZeroBit;
|
||||||
dim = 32 * col + BitArray.ctz(lowestZeroBit);
|
dim = 32 * col + BitArray.ctz(lowestZeroBit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dim;
|
return dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate and return the dimension ID (bit position)
|
// allocate and return the dimension ID (bit position)
|
||||||
//
|
//
|
||||||
allocDimension() {
|
allocDimension() {
|
||||||
let dim = this._findFreeDimension();
|
let dim = this._findFreeDimension();
|
||||||
|
|
||||||
// if we did not find free dimension, expand the bitarray.
|
// if we did not find free dimension, expand the bitarray.
|
||||||
if (dim === undefined) {
|
if (dim === undefined) {
|
||||||
this.width++;
|
this.width++;
|
||||||
|
|
||||||
const biggerBitArray = new Int32Array(this.width * this.length);
|
const biggerBitArray = new Int32Array(this.width * this.length);
|
||||||
biggerBitArray.set(this.bitarray);
|
biggerBitArray.set(this.bitarray);
|
||||||
this.bitarray = biggerBitArray;
|
this.bitarray = biggerBitArray;
|
||||||
|
|
||||||
const biggerBitmask = new Int32Array(this.width);
|
const biggerBitmask = new Int32Array(this.width);
|
||||||
biggerBitmask.set(this.bitmask);
|
biggerBitmask.set(this.bitmask);
|
||||||
this.bitmask = biggerBitmask;
|
this.bitmask = biggerBitmask;
|
||||||
|
|
||||||
dim = this._findFreeDimension();
|
dim = this._findFreeDimension();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dimensionCount++;
|
this.dimensionCount++;
|
||||||
return dim;
|
return dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
// free a dimension for later use. MUST deselect the dimension, as other
|
// free a dimension for later use. MUST deselect the dimension, as other
|
||||||
// code assume the column will be zero valued.
|
// code assume the column will be zero valued.
|
||||||
//
|
//
|
||||||
freeDimension(dim) {
|
freeDimension(dim) {
|
||||||
// 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--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return true if this index is selected in ALL dimensions.
|
// return true if this index is selected in ALL dimensions.
|
||||||
//
|
//
|
||||||
isSelected(index) {
|
isSelected(index) {
|
||||||
const width = this.width;
|
const width = this.width;
|
||||||
const length = this.length;
|
const length = this.length;
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.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;
|
if (!bitmask || bitarray[w * length + index] !== bitmask) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return true if this index is selected in ALL dimensions IGNORING dim
|
// return true if this index is selected in ALL dimensions IGNORING dim
|
||||||
//
|
//
|
||||||
isSelectedIgnoringDim(index, dim) {
|
isSelectedIgnoringDim(index, dim) {
|
||||||
const ignoreOffset = dim >>> 5;
|
const ignoreOffset = dim >>> 5;
|
||||||
const ignoreMask = ~(1 << dim % 32);
|
const ignoreMask = ~(1 << dim % 32);
|
||||||
|
|
||||||
const width = this.width;
|
const width = this.width;
|
||||||
const length = this.length;
|
const length = this.length;
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.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 (w === ignoreOffset) {
|
if (w === ignoreOffset) {
|
||||||
if (
|
if (
|
||||||
bitmask &&
|
bitmask &&
|
||||||
(bitarray[w * length + index] & ignoreMask) !== (bitmask & ignoreMask)
|
(bitarray[w * length + index] & ignoreMask) !== (bitmask & ignoreMask)
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (bitmask && bitarray[w * length + index] !== bitmask) return false;
|
if (bitmask && bitarray[w * length + index] !== bitmask) return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// select index on dimension
|
// select index on dimension
|
||||||
//
|
//
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// deselect index on dimension
|
// deselect index on dimension
|
||||||
//
|
//
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// select all indices on dimension.
|
// select all indices on dimension.
|
||||||
//
|
//
|
||||||
selectAll(dim) {
|
selectAll(dim) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deselect all indices on dimension
|
// deselect all indices on dimension
|
||||||
//
|
//
|
||||||
deselectAll(dim) {
|
deselectAll(dim) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// select range of indices on a dimension, indirect through a sort map.
|
// select range of indices on a dimension, indirect through a sort map.
|
||||||
// Indirect functions are used to map between sort and natural order.
|
// Indirect functions are used to map between sort and natural order.
|
||||||
//
|
//
|
||||||
selectIndirectFromRange(dim, indirect, range) {
|
selectIndirectFromRange(dim, indirect, range) {
|
||||||
const col = dim >>> 5;
|
const col = dim >>> 5;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// deselect range of indices on a dimension, indirect through a sort map.
|
// deselect range of indices on a dimension, indirect through a sort map.
|
||||||
//
|
//
|
||||||
deselectIndirectFromRange(dim, indirect, range) {
|
deselectIndirectFromRange(dim, indirect, range) {
|
||||||
const col = dim >>> 5;
|
const col = dim >>> 5;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill the array with selected|deselected value based upon the
|
// Fill the array with selected|deselected value based upon the
|
||||||
// current selection state.
|
// current selection state.
|
||||||
//
|
//
|
||||||
fillBySelection(result, selectedValue, deselectedValue) {
|
fillBySelection(result, selectedValue, deselectedValue) {
|
||||||
// special case (width === 1) for performance
|
// special case (width === 1) for performance
|
||||||
if (this.width === 1) {
|
if (this.width === 1) {
|
||||||
const bitmask = this.bitmask[0];
|
const bitmask = this.bitmask[0];
|
||||||
const bitarray = this.bitarray;
|
const bitarray = this.bitarray;
|
||||||
for (let i = 0, len = this.length; i < len; i++) {
|
for (let i = 0, len = this.length; i < len; i++) {
|
||||||
result[i] =
|
result[i] =
|
||||||
bitmask && bitarray[i] === bitmask ? selectedValue : deselectedValue;
|
bitmask && bitarray[i] === bitmask ? selectedValue : deselectedValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0, len = this.length; i < len; i++) {
|
for (let i = 0, len = this.length; i < len; i++) {
|
||||||
result[i] = this.isSelected(i) ? selectedValue : deselectedValue;
|
result[i] = this.isSelected(i) ? selectedValue : deselectedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BitArray;
|
export default BitArray;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,132 +1,132 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
|
|
||||||
// Interval operations - very simple version of interval set relationship
|
// Interval operations - very simple version of interval set relationship
|
||||||
// operators. An interval is a multi-interval list of [min, max),
|
// operators. An interval is a multi-interval list of [min, max),
|
||||||
// where min and max are mandatory. Constraints:
|
// where min and max are mandatory. Constraints:
|
||||||
// * min <= max, min >= 0
|
// * min <= max, min >= 0
|
||||||
// * empty interval groups are OK, ie, []
|
// * empty interval groups are OK, ie, []
|
||||||
// * Legal intervals: [], [ [0, 1], ... ]
|
// * Legal intervals: [], [ [0, 1], ... ]
|
||||||
// * Not legal: [ [] ]
|
// * Not legal: [ [] ]
|
||||||
//
|
//
|
||||||
// All intervals are represented by simple JS arrays/numbers.
|
// All intervals are represented by simple JS arrays/numbers.
|
||||||
//
|
//
|
||||||
// Code assumes intervals have a low cardinality; many operations are done
|
// Code assumes intervals have a low cardinality; many operations are done
|
||||||
// with a brute force scan. Little attempt to reduce GC pressure.
|
// with a brute force scan. Little attempt to reduce GC pressure.
|
||||||
//
|
//
|
||||||
class PositiveIntervals {
|
class PositiveIntervals {
|
||||||
// Canonicalize - ensure that:
|
// Canonicalize - ensure that:
|
||||||
// 1. no overlapping intervals
|
// 1. no overlapping intervals
|
||||||
// 2. sorted in order of interval min.
|
// 2. sorted in order of interval min.
|
||||||
//
|
//
|
||||||
static canonicalize(A) {
|
static canonicalize(A) {
|
||||||
if (A.length <= 1) return A;
|
if (A.length <= 1) return A;
|
||||||
let copy = A.slice();
|
let copy = A.slice();
|
||||||
copy.sort((a, b) => a[0] - b[0]);
|
copy.sort((a, b) => a[0] - b[0]);
|
||||||
const res = [];
|
const res = [];
|
||||||
res.push(copy[0]);
|
res.push(copy[0]);
|
||||||
for (let i = 1, len = copy.length; i < len; i++) {
|
for (let i = 1, len = copy.length; i < len; i++) {
|
||||||
if (copy[i][0] > res[res.length - 1][1]) {
|
if (copy[i][0] > res[res.length - 1][1]) {
|
||||||
// non-overlapping, add to result
|
// non-overlapping, add to result
|
||||||
res.push(copy[i]);
|
res.push(copy[i]);
|
||||||
} else if (copy[i][1] > res[res.length - 1][1]) {
|
} else if (copy[i][1] > res[res.length - 1][1]) {
|
||||||
// merge this into previous
|
// merge this into previous
|
||||||
res[res.length - 1][1] = copy[i][1];
|
res[res.length - 1][1] = copy[i][1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return interval with values belonging to both A and B. Essentially
|
// Return interval with values belonging to both A and B. Essentially
|
||||||
// a set union operation.
|
// a set union operation.
|
||||||
//
|
//
|
||||||
static union(A, B) {
|
static union(A, B) {
|
||||||
return PositiveIntervals.canonicalize([...A, ...B]);
|
return PositiveIntervals.canonicalize([...A, ...B]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _flatten(A, B) {
|
static _flatten(A, B) {
|
||||||
let points = []; /* point, A, start */
|
let points = []; /* point, A, start */
|
||||||
for (let a = 0; a < A.length; a++) {
|
for (let a = 0; a < A.length; a++) {
|
||||||
points.push([A[a][0], true, true]);
|
points.push([A[a][0], true, true]);
|
||||||
points.push([A[a][1], true, false]);
|
points.push([A[a][1], true, false]);
|
||||||
}
|
}
|
||||||
for (let b = 0; b < B.length; b++) {
|
for (let b = 0; b < B.length; b++) {
|
||||||
points.push([B[b][0], false, true]);
|
points.push([B[b][0], false, true]);
|
||||||
points.push([B[b][1], false, false]);
|
points.push([B[b][1], false, false]);
|
||||||
}
|
}
|
||||||
// Sort order: point, then start
|
// Sort order: point, then start
|
||||||
points.sort((a, b) => (a[0] !== b[0] ? a[0] - b[0] : a[2] ? 1 : -1));
|
points.sort((a, b) => (a[0] !== b[0] ? a[0] - b[0] : a[2] ? 1 : -1));
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A - B, ie, the interval with all values in A that are not in B. Essentially
|
// A - B, ie, the interval with all values in A that are not in B. Essentially
|
||||||
// a set difference operation.
|
// a set difference operation.
|
||||||
//
|
//
|
||||||
static difference(A, B) {
|
static difference(A, B) {
|
||||||
// Corner cases
|
// Corner cases
|
||||||
if (A.length === 0 || B.length === 0) {
|
if (A.length === 0 || B.length === 0) {
|
||||||
return PositiveIntervals.canonicalize(A);
|
return PositiveIntervals.canonicalize(A);
|
||||||
}
|
}
|
||||||
|
|
||||||
A = PositiveIntervals.canonicalize(A);
|
A = PositiveIntervals.canonicalize(A);
|
||||||
B = PositiveIntervals.canonicalize(B);
|
B = PositiveIntervals.canonicalize(B);
|
||||||
|
|
||||||
const points = PositiveIntervals._flatten(A, B);
|
const points = PositiveIntervals._flatten(A, B);
|
||||||
const res = [];
|
const res = [];
|
||||||
let aDepth = 0;
|
let aDepth = 0;
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
let intervalStart;
|
let intervalStart;
|
||||||
let prevPoint;
|
let prevPoint;
|
||||||
for (let i = 0; i < points.length; i++) {
|
for (let i = 0; i < points.length; i++) {
|
||||||
const p = points[i];
|
const p = points[i];
|
||||||
const before = depth;
|
const before = depth;
|
||||||
const delta = p[2] ? 1 : -1;
|
const delta = p[2] ? 1 : -1;
|
||||||
depth += delta;
|
depth += delta;
|
||||||
if (p[1]) aDepth += delta;
|
if (p[1]) aDepth += delta;
|
||||||
|
|
||||||
if (i === points.length - 1 || p[0] !== points[i + 1][0]) {
|
if (i === points.length - 1 || p[0] !== points[i + 1][0]) {
|
||||||
if (aDepth === 1 && depth === 1) {
|
if (aDepth === 1 && depth === 1) {
|
||||||
intervalStart = p[0];
|
intervalStart = p[0];
|
||||||
} else if (intervalStart !== undefined) {
|
} else if (intervalStart !== undefined) {
|
||||||
res.push([intervalStart, p[0]]);
|
res.push([intervalStart, p[0]]);
|
||||||
intervalStart = undefined;
|
intervalStart = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prevPoint = p[0];
|
prevPoint = p[0];
|
||||||
}
|
}
|
||||||
// guaranteed to be in canonical form
|
// guaranteed to be in canonical form
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return interval with values belonging to A or B. Essentially a set
|
// Return interval with values belonging to A or B. Essentially a set
|
||||||
// intersection.
|
// intersection.
|
||||||
//
|
//
|
||||||
static intersection(A, B) {
|
static intersection(A, B) {
|
||||||
if (A.length === 0 || B.length === 0) {
|
if (A.length === 0 || B.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
A = PositiveIntervals.canonicalize(A);
|
A = PositiveIntervals.canonicalize(A);
|
||||||
B = PositiveIntervals.canonicalize(B);
|
B = PositiveIntervals.canonicalize(B);
|
||||||
|
|
||||||
const points = PositiveIntervals._flatten(A, B);
|
const points = PositiveIntervals._flatten(A, B);
|
||||||
const res = [];
|
const res = [];
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
let intervalStart;
|
let intervalStart;
|
||||||
for (let i = 0; i < points.length; i++) {
|
for (let i = 0; i < points.length; i++) {
|
||||||
const p = points[i];
|
const p = points[i];
|
||||||
const before = depth;
|
const before = depth;
|
||||||
depth += p[2] ? 1 : -1;
|
depth += p[2] ? 1 : -1;
|
||||||
if (depth === 2) {
|
if (depth === 2) {
|
||||||
intervalStart = p[0];
|
intervalStart = p[0];
|
||||||
} else if (intervalStart !== undefined) {
|
} else if (intervalStart !== undefined) {
|
||||||
res.push([intervalStart, p[0]]);
|
res.push([intervalStart, p[0]]);
|
||||||
intervalStart = undefined;
|
intervalStart = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// guaranteed to be in canonical form
|
// guaranteed to be in canonical form
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PositiveIntervals;
|
export default PositiveIntervals;
|
||||||
|
|||||||
@@ -1,98 +1,98 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// jshint esversion: 6
|
// jshint esversion: 6
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Utility functions, private to this module.
|
Utility functions, private to this module.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// fill an array or typedarray with a sequential range of numbers,
|
// fill an array or typedarray with a sequential range of numbers,
|
||||||
// starting with `start`
|
// starting with `start`
|
||||||
//
|
//
|
||||||
export function fillRange(arr, start = 0) {
|
export function fillRange(arr, start = 0) {
|
||||||
for (let i = 0, len = arr.length; i < len; i++) {
|
for (let i = 0, len = arr.length; i < len; i++) {
|
||||||
arr[i] = i + start;
|
arr[i] = i + start;
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
// Search for `value` in the sorted array `arr`, in the range [first, last).
|
||||||
// Return the first (left most) index where arr[index] >= value.
|
// Return the first (left most) index where arr[index] >= value.
|
||||||
//
|
//
|
||||||
// In other words, return array index I where:
|
// In other words, return array index I where:
|
||||||
// arr[i] < value for all tarr[lo:I]
|
// arr[i] < value for all tarr[lo:I]
|
||||||
// arr[i] >= value for all tarr[I:last]
|
// arr[i] >= value for all tarr[I:last]
|
||||||
//
|
//
|
||||||
// The same semantics/behavior as:
|
// The same semantics/behavior as:
|
||||||
// C++: lower_bound()
|
// C++: lower_bound()
|
||||||
// Python: bisect.bisect_left()
|
// Python: bisect.bisect_left()
|
||||||
//
|
//
|
||||||
// XXX: it is likely that there would be minimal performance hit from creating
|
// XXX: it is likely that there would be minimal performance hit from creating
|
||||||
// a factory version of lowerBound that takes an accessor (rather than having
|
// a factory version of lowerBound that takes an accessor (rather than having
|
||||||
// a special-cased version for lining the indirection).
|
// a special-cased version for lining the indirection).
|
||||||
//
|
//
|
||||||
export function lowerBound(valueArray, value, first, last) {
|
export function lowerBound(valueArray, value, first, last) {
|
||||||
// this is just a binary search
|
// this is just a binary search
|
||||||
while (first < last) {
|
while (first < last) {
|
||||||
const middle = (first + last) >>> 1;
|
const middle = (first + last) >>> 1;
|
||||||
if (valueArray[middle] < value) {
|
if (valueArray[middle] < value) {
|
||||||
first = middle + 1;
|
first = middle + 1;
|
||||||
} else {
|
} else {
|
||||||
last = middle;
|
last = middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inlined performance optimization - used to indirect through a sort map.
|
// Inlined performance optimization - used to indirect through a sort map.
|
||||||
//
|
//
|
||||||
export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
export function lowerBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||||
// this is just a binary search
|
// this is just a binary search
|
||||||
while (first < last) {
|
while (first < last) {
|
||||||
const middle = (first + last) >>> 1;
|
const middle = (first + last) >>> 1;
|
||||||
if (valueArray[indexArray[middle]] < value) {
|
if (valueArray[indexArray[middle]] < value) {
|
||||||
first = middle + 1;
|
first = middle + 1;
|
||||||
} else {
|
} else {
|
||||||
last = middle;
|
last = middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for `value in the sorted array `arr`, in the range [first, last).
|
// Search for `value in the sorted array `arr`, in the range [first, last).
|
||||||
// Return the first value where arr[index] > value.
|
// Return the first value where arr[index] > value.
|
||||||
//
|
//
|
||||||
// In other words, return array index I, where:
|
// In other words, return array index I, where:
|
||||||
// arr[i] <= value for all tarr[lo:I]
|
// arr[i] <= value for all tarr[lo:I]
|
||||||
// arr[i] > value for all tarr[I:last]
|
// arr[i] > value for all tarr[I:last]
|
||||||
//
|
//
|
||||||
// The same semantics/behavior as:
|
// The same semantics/behavior as:
|
||||||
// C++: upper_bound()
|
// C++: upper_bound()
|
||||||
// Python: bisect.bisect_right()
|
// Python: bisect.bisect_right()
|
||||||
//
|
//
|
||||||
export function upperBound(valueArray, value, first, last) {
|
export function upperBound(valueArray, value, first, last) {
|
||||||
// this is just a binary search
|
// this is just a binary search
|
||||||
while (first < last) {
|
while (first < last) {
|
||||||
const middle = (first + last) >>> 1;
|
const middle = (first + last) >>> 1;
|
||||||
if (valueArray[middle] > value) {
|
if (valueArray[middle] > value) {
|
||||||
last = middle;
|
last = middle;
|
||||||
} else {
|
} else {
|
||||||
first = middle + 1;
|
first = middle + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline performance optimization
|
// Inline performance optimization
|
||||||
//
|
//
|
||||||
export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
export function upperBoundIndirect(valueArray, indexArray, value, first, last) {
|
||||||
// this is just a binary search
|
// this is just a binary search
|
||||||
while (first < last) {
|
while (first < last) {
|
||||||
const middle = (first + last) >>> 1;
|
const middle = (first + last) >>> 1;
|
||||||
if (valueArray[indexArray[middle]] > value) {
|
if (valueArray[indexArray[middle]] > value) {
|
||||||
last = middle;
|
last = middle;
|
||||||
} else {
|
} else {
|
||||||
first = middle + 1;
|
first = middle + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ from .web import webapp
|
|||||||
|
|
||||||
REACTIVE_LIMIT = 1_000_000
|
REACTIVE_LIMIT = 1_000_000
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__, static_folder="web/static")
|
||||||
cache = Cache(app, config={"CACHE_TYPE": "simple", "CACHE_DEFAULT_TIMEOUT": 860000})
|
cache = Cache(app, config={"CACHE_TYPE": "simple", "CACHE_DEFAULT_TIMEOUT": 860000})
|
||||||
Compress(app)
|
Compress(app)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import os
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, render_template, url_for, current_app
|
Blueprint, render_template, send_from_directory, current_app
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("webapp", __name__, template_folder="templates")
|
bp = Blueprint("webapp", __name__, template_folder="templates")
|
||||||
|
|
||||||
|
|
||||||
@@ -21,4 +23,4 @@ def swag():
|
|||||||
# renders swagger documentation
|
# renders swagger documentation
|
||||||
@bp.route("/favicon.png")
|
@bp.route("/favicon.png")
|
||||||
def favicon():
|
def favicon():
|
||||||
return url_for("static", filename="img/favicon.png")
|
return send_from_directory(os.path.join(bp.root_path, "static/img/"), "favicon.png")
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ class EndPoints(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Local
|
# Local
|
||||||
self.url_base = "http://0.0.0.0:5005/api/" + "v0.1/"
|
self.local_url = "http://127.0.0.1:5005/"
|
||||||
|
self.version = "v0.1"
|
||||||
|
self.url_base = "{local_url}api/{version}/".format(local_url=self.local_url, version=self.version)
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
|
|
||||||
def test_cells(self):
|
def test_cells(self):
|
||||||
@@ -51,3 +53,8 @@ class EndPoints(unittest.TestCase):
|
|||||||
url = "{base}{endpoint}".format(base=self.url_base, endpoint="diffexpression")
|
url = "{base}{endpoint}".format(base=self.url_base, endpoint="diffexpression")
|
||||||
result = self.session.post(url, data=json.dumps({"celllist1": ["AAACATACAACCAC-1", "AACCGATGGTCATG-1"], "celllist2": ["CCGATAGACCTAAG-1", "GGTGGAGAAGTAGA-1"]}), headers={'content-type': 'application/json'})
|
result = self.session.post(url, data=json.dumps({"celllist1": ["AAACATACAACCAC-1", "AACCGATGGTCATG-1"], "celllist2": ["CCGATAGACCTAAG-1", "GGTGGAGAAGTAGA-1"]}), headers={'content-type': 'application/json'})
|
||||||
assert result.status_code == 200
|
assert result.status_code == 200
|
||||||
|
|
||||||
|
def test_static(self):
|
||||||
|
url = "{url}{endpoint}/{file}".format(url=self.local_url, endpoint="static", file="js/service-worker.js")
|
||||||
|
result = self.session.get(url)
|
||||||
|
assert result.status_code == 200
|
||||||
|
|||||||
Reference in New Issue
Block a user