Merge branch 'master' into csweaver/api-v2-init

This commit is contained in:
Charlotte Weaver
2018-08-13 15:48:06 -07:00
committed by GitHub
11 changed files with 1189 additions and 1180 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

+31 -31
View File
@@ -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;
}; };
+18 -18
View File
@@ -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
View File
@@ -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;
} }
+254 -254
View File
@@ -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;
+98 -98
View File
@@ -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
View File
@@ -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)
+4 -2
View File
@@ -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 -1
View File
@@ -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