dc
Version:
A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js
448 lines (402 loc) • 11.3 kB
JavaScript
/*
This code pulled from
https://github.com/mikolalysenko/binary-search-bounds
https://github.com/mikolalysenko/interval-tree-1d
and concatenated to eliminate modules
The MIT License (MIT)
Copyright (c) 2013-2015 Mikola Lysenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// search-bounds.js
"use strict"
function compileSearch(funcName, predicate, reversed, extraArgs, 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[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"], earlyOut),
compileSearch("P", "c(x,y)" + predicate + "0", reversed, ["y", "c"], earlyOut),
"function dispatchBsearch", suffix, "(a,y,c,l,h){\
if(typeof(c)==='function'){\
return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\
}else{\
return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\
}}\
return dispatchBsearch", suffix].join(""))
return result()
}
var bounds = {
ge: compileBoundsSearch(">=", false, "GE"),
gt: compileBoundsSearch(">", false, "GT"),
lt: compileBoundsSearch("<", true, "LT"),
le: compileBoundsSearch("<=", true, "LE"),
eq: compileBoundsSearch("-", true, "EQ", true)
}
// interval-tree.js
var NOT_FOUND = 0
var SUCCESS = 1
var EMPTY = 2
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 = bounds.ge(this.leftPoints, interval, compareBegin)
var r = bounds.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 = bounds.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 = bounds.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 lysenkoIntervalTree(intervals) {
if(!intervals || intervals.length === 0) {
return new IntervalTree(null)
}
return new IntervalTree(createIntervalTree(intervals))
}