244 lines
6.8 KiB
JavaScript
244 lines
6.8 KiB
JavaScript
import "../core/functor";
|
||
import "../math/abs";
|
||
import "geom";
|
||
import "point";
|
||
|
||
d3.geom.quadtree = function(points, x1, y1, x2, y2) {
|
||
var x = d3_geom_pointX,
|
||
y = d3_geom_pointY,
|
||
compat;
|
||
|
||
// For backwards-compatibility.
|
||
if (compat = arguments.length) {
|
||
x = d3_geom_quadtreeCompatX;
|
||
y = d3_geom_quadtreeCompatY;
|
||
if (compat === 3) {
|
||
y2 = y1;
|
||
x2 = x1;
|
||
y1 = x1 = 0;
|
||
}
|
||
return quadtree(points);
|
||
}
|
||
|
||
function quadtree(data) {
|
||
var d,
|
||
fx = d3_functor(x),
|
||
fy = d3_functor(y),
|
||
xs,
|
||
ys,
|
||
i,
|
||
n,
|
||
x1_,
|
||
y1_,
|
||
x2_,
|
||
y2_;
|
||
|
||
if (x1 != null) {
|
||
x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
|
||
} else {
|
||
// Compute bounds, and cache points temporarily.
|
||
x2_ = y2_ = -(x1_ = y1_ = Infinity);
|
||
xs = [], ys = [];
|
||
n = data.length;
|
||
if (compat) for (i = 0; i < n; ++i) {
|
||
d = data[i];
|
||
if (d.x < x1_) x1_ = d.x;
|
||
if (d.y < y1_) y1_ = d.y;
|
||
if (d.x > x2_) x2_ = d.x;
|
||
if (d.y > y2_) y2_ = d.y;
|
||
xs.push(d.x);
|
||
ys.push(d.y);
|
||
} else for (i = 0; i < n; ++i) {
|
||
var x_ = +fx(d = data[i], i),
|
||
y_ = +fy(d, i);
|
||
if (x_ < x1_) x1_ = x_;
|
||
if (y_ < y1_) y1_ = y_;
|
||
if (x_ > x2_) x2_ = x_;
|
||
if (y_ > y2_) y2_ = y_;
|
||
xs.push(x_);
|
||
ys.push(y_);
|
||
}
|
||
}
|
||
|
||
// Squarify the bounds.
|
||
var dx = x2_ - x1_,
|
||
dy = y2_ - y1_;
|
||
if (dx > dy) y2_ = y1_ + dx;
|
||
else x2_ = x1_ + dy;
|
||
|
||
// Recursively inserts the specified point p at the node n or one of its
|
||
// descendants. The bounds are defined by [x1, x2] and [y1, y2].
|
||
function insert(n, d, x, y, x1, y1, x2, y2) {
|
||
if (isNaN(x) || isNaN(y)) return; // ignore invalid points
|
||
if (n.leaf) {
|
||
var nx = n.x,
|
||
ny = n.y;
|
||
if (nx != null) {
|
||
// If the point at this leaf node is at the same position as the new
|
||
// point we are adding, we leave the point associated with the
|
||
// internal node while adding the new point to a child node. This
|
||
// avoids infinite recursion.
|
||
if ((abs(nx - x) + abs(ny - y)) < 0.01) {
|
||
insertChild(n, d, x, y, x1, y1, x2, y2);
|
||
} else {
|
||
var nPoint = n.point;
|
||
n.x = n.y = n.point = null;
|
||
insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
|
||
insertChild(n, d, x, y, x1, y1, x2, y2);
|
||
}
|
||
} else {
|
||
n.x = x, n.y = y, n.point = d;
|
||
}
|
||
} else {
|
||
insertChild(n, d, x, y, x1, y1, x2, y2);
|
||
}
|
||
}
|
||
|
||
// Recursively inserts the specified point [x, y] into a descendant of node
|
||
// n. The bounds are defined by [x1, x2] and [y1, y2].
|
||
function insertChild(n, d, x, y, x1, y1, x2, y2) {
|
||
// Compute the split point, and the quadrant in which to insert p.
|
||
var xm = (x1 + x2) * 0.5,
|
||
ym = (y1 + y2) * 0.5,
|
||
right = x >= xm,
|
||
below = y >= ym,
|
||
i = below << 1 | right;
|
||
|
||
// Recursively insert into the child node.
|
||
n.leaf = false;
|
||
n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
|
||
|
||
// Update the bounds as we recurse.
|
||
if (right) x1 = xm; else x2 = xm;
|
||
if (below) y1 = ym; else y2 = ym;
|
||
insert(n, d, x, y, x1, y1, x2, y2);
|
||
}
|
||
|
||
// Create the root node.
|
||
var root = d3_geom_quadtreeNode();
|
||
|
||
root.add = function(d) {
|
||
insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
|
||
};
|
||
|
||
root.visit = function(f) {
|
||
d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
|
||
};
|
||
|
||
// Find the closest point to the specified point.
|
||
// TODO allow the initial search extent to be specified?
|
||
// TODO allow the initial minimum distance to be specified?
|
||
// TODO allow searching below any node?
|
||
root.find = function(point) {
|
||
return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
|
||
};
|
||
|
||
// Insert all points.
|
||
i = -1;
|
||
if (x1 == null) {
|
||
while (++i < n) {
|
||
insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
|
||
}
|
||
--i; // index of last insertion
|
||
} else data.forEach(root.add);
|
||
|
||
// Discard captured fields.
|
||
xs = ys = data = d = null;
|
||
|
||
return root;
|
||
}
|
||
|
||
quadtree.x = function(_) {
|
||
return arguments.length ? (x = _, quadtree) : x;
|
||
};
|
||
|
||
quadtree.y = function(_) {
|
||
return arguments.length ? (y = _, quadtree) : y;
|
||
};
|
||
|
||
quadtree.extent = function(_) {
|
||
if (!arguments.length) return x1 == null ? null : [[x1, y1], [x2, y2]];
|
||
if (_ == null) x1 = y1 = x2 = y2 = null;
|
||
else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], y2 = +_[1][1];
|
||
return quadtree;
|
||
};
|
||
|
||
quadtree.size = function(_) {
|
||
if (!arguments.length) return x1 == null ? null : [x2 - x1, y2 - y1];
|
||
if (_ == null) x1 = y1 = x2 = y2 = null;
|
||
else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
|
||
return quadtree;
|
||
};
|
||
|
||
return quadtree;
|
||
};
|
||
|
||
function d3_geom_quadtreeCompatX(d) { return d.x; }
|
||
function d3_geom_quadtreeCompatY(d) { return d.y; }
|
||
|
||
function d3_geom_quadtreeNode() {
|
||
return {
|
||
leaf: true,
|
||
nodes: [],
|
||
point: null,
|
||
x: null,
|
||
y: null
|
||
};
|
||
}
|
||
|
||
function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
|
||
if (!f(node, x1, y1, x2, y2)) {
|
||
var sx = (x1 + x2) * 0.5,
|
||
sy = (y1 + y2) * 0.5,
|
||
children = node.nodes;
|
||
if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
|
||
if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
|
||
if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
|
||
if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
|
||
}
|
||
}
|
||
|
||
function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
|
||
var minDistance2 = Infinity,
|
||
closestPoint;
|
||
|
||
(function find(node, x1, y1, x2, y2) {
|
||
|
||
// stop searching if this cell can’t contain a closer node
|
||
if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
|
||
|
||
// visit this point
|
||
if (point = node.point) {
|
||
var point,
|
||
dx = x - node.x,
|
||
dy = y - node.y,
|
||
distance2 = dx * dx + dy * dy;
|
||
if (distance2 < minDistance2) {
|
||
var distance = Math.sqrt(minDistance2 = distance2);
|
||
x0 = x - distance, y0 = y - distance;
|
||
x3 = x + distance, y3 = y + distance;
|
||
closestPoint = point;
|
||
}
|
||
}
|
||
|
||
// bisect the current node
|
||
var children = node.nodes,
|
||
xm = (x1 + x2) * 0.5,
|
||
ym = (y1 + y2) * 0.5,
|
||
right = x >= xm,
|
||
below = y >= ym;
|
||
|
||
// visit closest cell first
|
||
for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
|
||
if (node = children[i & 3]) switch (i & 3) {
|
||
case 0: find(node, x1, y1, xm, ym); break;
|
||
case 1: find(node, xm, y1, x2, ym); break;
|
||
case 2: find(node, x1, ym, xm, y2); break;
|
||
case 3: find(node, xm, ym, x2, y2); break;
|
||
}
|
||
}
|
||
})(root, x0, y0, x3, y3);
|
||
|
||
return closestPoint;
|
||
}
|