dc.graph
Version:
Graph visualizations integrated with crossfilter and dc.js
431 lines (392 loc) • 10.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.lysenkoIntervalTree = factory());
}(this, (function () { 'use strict';
function compileSearch(funcName, predicate, reversed, extraArgs, useNdarray, earlyOut) {
var code = [
"function ", funcName, "(a,l,h,", extraArgs.join(","), "){",
earlyOut ? "" : "var i=", (reversed ? "l-1" : "h+1"),
";while(l<=h){\
var m=(l+h)>>>1,x=a", useNdarray ? ".get(m)" : "[m]"];
if(earlyOut) {
if(predicate.indexOf("c") < 0) {
code.push(";if(x===y){return m}else if(x<=y){");
} else {
code.push(";var p=c(x,y);if(p===0){return m}else if(p<=0){");
}
} else {
code.push(";if(", predicate, "){i=m;");
}
if(reversed) {
code.push("l=m+1}else{h=m-1}");
} else {
code.push("h=m-1}else{l=m+1}");
}
code.push("}");
if(earlyOut) {
code.push("return -1};");
} else {
code.push("return i};");
}
return code.join("")
}
function compileBoundsSearch(predicate, reversed, suffix, earlyOut) {
var result = new Function([
compileSearch("A", "x" + predicate + "y", reversed, ["y"], false, earlyOut),
compileSearch("B", "x" + predicate + "y", reversed, ["y"], true, earlyOut),
compileSearch("P", "c(x,y)" + predicate + "0", reversed, ["y", "c"], false, earlyOut),
compileSearch("Q", "c(x,y)" + predicate + "0", reversed, ["y", "c"], true, earlyOut),
"function dispatchBsearch", suffix, "(a,y,c,l,h){\
if(a.shape){\
if(typeof(c)==='function'){\
return Q(a,(l===undefined)?0:l|0,(h===undefined)?a.shape[0]-1:h|0,y,c)\
}else{\
return B(a,(c===undefined)?0:c|0,(l===undefined)?a.shape[0]-1:l|0,y)\
}}else{\
if(typeof(c)==='function'){\
return P(a,(l===undefined)?0:l|0,(h===undefined)?a.length-1:h|0,y,c)\
}else{\
return A(a,(c===undefined)?0:c|0,(l===undefined)?a.length-1:l|0,y)\
}}}\
return dispatchBsearch", suffix].join(""));
return result()
}
var searchBounds = {
ge: compileBoundsSearch(">=", false, "GE"),
gt: compileBoundsSearch(">", false, "GT"),
lt: compileBoundsSearch("<", true, "LT"),
le: compileBoundsSearch("<=", true, "LE"),
eq: compileBoundsSearch("-", true, "EQ", true)
};
var NOT_FOUND = 0;
var SUCCESS = 1;
var EMPTY = 2;
var intervalTree = createWrapper;
function IntervalTreeNode(mid, left, right, leftPoints, rightPoints) {
this.mid = mid;
this.left = left;
this.right = right;
this.leftPoints = leftPoints;
this.rightPoints = rightPoints;
this.count = (left ? left.count : 0) + (right ? right.count : 0) + leftPoints.length;
}
var proto = IntervalTreeNode.prototype;
function copy(a, b) {
a.mid = b.mid;
a.left = b.left;
a.right = b.right;
a.leftPoints = b.leftPoints;
a.rightPoints = b.rightPoints;
a.count = b.count;
}
function rebuild(node, intervals) {
var ntree = createIntervalTree(intervals);
node.mid = ntree.mid;
node.left = ntree.left;
node.right = ntree.right;
node.leftPoints = ntree.leftPoints;
node.rightPoints = ntree.rightPoints;
node.count = ntree.count;
}
function rebuildWithInterval(node, interval) {
var intervals = node.intervals([]);
intervals.push(interval);
rebuild(node, intervals);
}
function rebuildWithoutInterval(node, interval) {
var intervals = node.intervals([]);
var idx = intervals.indexOf(interval);
if(idx < 0) {
return NOT_FOUND
}
intervals.splice(idx, 1);
rebuild(node, intervals);
return SUCCESS
}
proto.intervals = function(result) {
result.push.apply(result, this.leftPoints);
if(this.left) {
this.left.intervals(result);
}
if(this.right) {
this.right.intervals(result);
}
return result
};
proto.insert = function(interval) {
var weight = this.count - this.leftPoints.length;
this.count += 1;
if(interval[1] < this.mid) {
if(this.left) {
if(4*(this.left.count+1) > 3*(weight+1)) {
rebuildWithInterval(this, interval);
} else {
this.left.insert(interval);
}
} else {
this.left = createIntervalTree([interval]);
}
} else if(interval[0] > this.mid) {
if(this.right) {
if(4*(this.right.count+1) > 3*(weight+1)) {
rebuildWithInterval(this, interval);
} else {
this.right.insert(interval);
}
} else {
this.right = createIntervalTree([interval]);
}
} else {
var l = searchBounds.ge(this.leftPoints, interval, compareBegin);
var r = searchBounds.ge(this.rightPoints, interval, compareEnd);
this.leftPoints.splice(l, 0, interval);
this.rightPoints.splice(r, 0, interval);
}
};
proto.remove = function(interval) {
var weight = this.count - this.leftPoints;
if(interval[1] < this.mid) {
if(!this.left) {
return NOT_FOUND
}
var rw = this.right ? this.right.count : 0;
if(4 * rw > 3 * (weight-1)) {
return rebuildWithoutInterval(this, interval)
}
var r = this.left.remove(interval);
if(r === EMPTY) {
this.left = null;
this.count -= 1;
return SUCCESS
} else if(r === SUCCESS) {
this.count -= 1;
}
return r
} else if(interval[0] > this.mid) {
if(!this.right) {
return NOT_FOUND
}
var lw = this.left ? this.left.count : 0;
if(4 * lw > 3 * (weight-1)) {
return rebuildWithoutInterval(this, interval)
}
var r = this.right.remove(interval);
if(r === EMPTY) {
this.right = null;
this.count -= 1;
return SUCCESS
} else if(r === SUCCESS) {
this.count -= 1;
}
return r
} else {
if(this.count === 1) {
if(this.leftPoints[0] === interval) {
return EMPTY
} else {
return NOT_FOUND
}
}
if(this.leftPoints.length === 1 && this.leftPoints[0] === interval) {
if(this.left && this.right) {
var p = this;
var n = this.left;
while(n.right) {
p = n;
n = n.right;
}
if(p === this) {
n.right = this.right;
} else {
var l = this.left;
var r = this.right;
p.count -= n.count;
p.right = n.left;
n.left = l;
n.right = r;
}
copy(this, n);
this.count = (this.left?this.left.count:0) + (this.right?this.right.count:0) + this.leftPoints.length;
} else if(this.left) {
copy(this, this.left);
} else {
copy(this, this.right);
}
return SUCCESS
}
for(var l = searchBounds.ge(this.leftPoints, interval, compareBegin); l<this.leftPoints.length; ++l) {
if(this.leftPoints[l][0] !== interval[0]) {
break
}
if(this.leftPoints[l] === interval) {
this.count -= 1;
this.leftPoints.splice(l, 1);
for(var r = searchBounds.ge(this.rightPoints, interval, compareEnd); r<this.rightPoints.length; ++r) {
if(this.rightPoints[r][1] !== interval[1]) {
break
} else if(this.rightPoints[r] === interval) {
this.rightPoints.splice(r, 1);
return SUCCESS
}
}
}
}
return NOT_FOUND
}
};
function reportLeftRange(arr, hi, cb) {
for(var i=0; i<arr.length && arr[i][0] <= hi; ++i) {
var r = cb(arr[i]);
if(r) { return r }
}
}
function reportRightRange(arr, lo, cb) {
for(var i=arr.length-1; i>=0 && arr[i][1] >= lo; --i) {
var r = cb(arr[i]);
if(r) { return r }
}
}
function reportRange(arr, cb) {
for(var i=0; i<arr.length; ++i) {
var r = cb(arr[i]);
if(r) { return r }
}
}
proto.queryPoint = function(x, cb) {
if(x < this.mid) {
if(this.left) {
var r = this.left.queryPoint(x, cb);
if(r) { return r }
}
return reportLeftRange(this.leftPoints, x, cb)
} else if(x > this.mid) {
if(this.right) {
var r = this.right.queryPoint(x, cb);
if(r) { return r }
}
return reportRightRange(this.rightPoints, x, cb)
} else {
return reportRange(this.leftPoints, cb)
}
};
proto.queryInterval = function(lo, hi, cb) {
if(lo < this.mid && this.left) {
var r = this.left.queryInterval(lo, hi, cb);
if(r) { return r }
}
if(hi > this.mid && this.right) {
var r = this.right.queryInterval(lo, hi, cb);
if(r) { return r }
}
if(hi < this.mid) {
return reportLeftRange(this.leftPoints, hi, cb)
} else if(lo > this.mid) {
return reportRightRange(this.rightPoints, lo, cb)
} else {
return reportRange(this.leftPoints, cb)
}
};
function compareNumbers(a, b) {
return a - b
}
function compareBegin(a, b) {
var d = a[0] - b[0];
if(d) { return d }
return a[1] - b[1]
}
function compareEnd(a, b) {
var d = a[1] - b[1];
if(d) { return d }
return a[0] - b[0]
}
function createIntervalTree(intervals) {
if(intervals.length === 0) {
return null
}
var pts = [];
for(var i=0; i<intervals.length; ++i) {
pts.push(intervals[i][0], intervals[i][1]);
}
pts.sort(compareNumbers);
var mid = pts[pts.length>>1];
var leftIntervals = [];
var rightIntervals = [];
var centerIntervals = [];
for(var i=0; i<intervals.length; ++i) {
var s = intervals[i];
if(s[1] < mid) {
leftIntervals.push(s);
} else if(mid < s[0]) {
rightIntervals.push(s);
} else {
centerIntervals.push(s);
}
}
//Split center intervals
var leftPoints = centerIntervals;
var rightPoints = centerIntervals.slice();
leftPoints.sort(compareBegin);
rightPoints.sort(compareEnd);
return new IntervalTreeNode(mid,
createIntervalTree(leftIntervals),
createIntervalTree(rightIntervals),
leftPoints,
rightPoints)
}
//User friendly wrapper that makes it possible to support empty trees
function IntervalTree(root) {
this.root = root;
}
var tproto = IntervalTree.prototype;
tproto.insert = function(interval) {
if(this.root) {
this.root.insert(interval);
} else {
this.root = new IntervalTreeNode(interval[0], null, null, [interval], [interval]);
}
};
tproto.remove = function(interval) {
if(this.root) {
var r = this.root.remove(interval);
if(r === EMPTY) {
this.root = null;
}
return r !== NOT_FOUND
}
return false
};
tproto.queryPoint = function(p, cb) {
if(this.root) {
return this.root.queryPoint(p, cb)
}
};
tproto.queryInterval = function(lo, hi, cb) {
if(lo <= hi && this.root) {
return this.root.queryInterval(lo, hi, cb)
}
};
Object.defineProperty(tproto, "count", {
get: function() {
if(this.root) {
return this.root.count
}
return 0
}
});
Object.defineProperty(tproto, "intervals", {
get: function() {
if(this.root) {
return this.root.intervals([])
}
return []
}
});
function createWrapper(intervals) {
if(!intervals || intervals.length === 0) {
return new IntervalTree(null)
}
return new IntervalTree(createIntervalTree(intervals))
}
return intervalTree;
})));