UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

354 lines (351 loc) 9.18 kB
/** * Binary Search Bounds * https://github.com/mikolalysenko/interval-tree-1d * Mikola Lysenko * * Inlined because of Content Security Policy issue caused by the use of `new Function(...)` syntax in an upstream dependency. * Issue reported here: https://github.com/mikolalysenko/binary-search-bounds/issues/5 **/ import bounds from './binarySearchBounds'; 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 get() { if (this.root) { return this.root.count; } return 0; } }); Object.defineProperty(tproto, 'intervals', { get: function get() { if (this.root) { return this.root.intervals([]); } return []; } }); export default function createWrapper(intervals) { if (!intervals || intervals.length === 0) { return new IntervalTree(null); } return new IntervalTree(createIntervalTree(intervals)); }