Add d3.geom.hull to d3.v3.js

This commit is contained in:
Bryan Housel
2014-04-10 15:25:04 -04:00
parent a928d4894d
commit b8303e3708
2 changed files with 126 additions and 0 deletions
+1
View File
@@ -115,6 +115,7 @@ D3_FILES = \
node_modules/d3/src/geo/path.js \
node_modules/d3/src/geo/stream.js \
node_modules/d3/src/geom/polygon.js \
node_modules/d3/src/geom/hull.js \
node_modules/d3/src/selection/index.js \
node_modules/d3/src/transition/index.js \
node_modules/d3/src/xhr/index.js \
+125
View File
@@ -4370,6 +4370,131 @@ function d3_geom_polygonClosed(coordinates) {
b = coordinates[coordinates.length - 1];
return !(a[0] - b[0] || a[1] - b[1]);
}
function d3_geom_pointX(d) {
return d[0];
}
function d3_geom_pointY(d) {
return d[1];
}
/**
* Computes the 2D convex hull of a set of points using Graham's scanning
* algorithm. The algorithm has been implemented as described in Cormen,
* Leiserson, and Rivest's Introduction to Algorithms. The running time of
* this algorithm is O(n log n), where n is the number of input points.
*
* @param vertices [[x1, y1], [x2, y2], …]
* @returns polygon [[x1, y1], [x2, y2], …]
*/
d3.geom.hull = function(vertices) {
var x = d3_geom_pointX,
y = d3_geom_pointY;
if (arguments.length) return hull(vertices);
function hull(data) {
if (data.length < 3) return [];
var fx = d3_functor(x),
fy = d3_functor(y),
n = data.length,
vertices, // TODO use parallel arrays
plen = n - 1,
points = [],
stack = [],
d,
i, j, h = 0, x1, y1, x2, y2, u, v, a, sp;
if (fx === d3_geom_pointX && y === d3_geom_pointY) vertices = data;
else for (i = 0, vertices = []; i < n; ++i) {
vertices.push([+fx.call(this, d = data[i], i), +fy.call(this, d, i)]);
}
// find the starting ref point: leftmost point with the minimum y coord
for (i = 1; i < n; ++i) {
if (vertices[i][1] < vertices[h][1]
|| vertices[i][1] == vertices[h][1]
&& vertices[i][0] < vertices[h][0]) h = i;
}
// calculate polar angles from ref point and sort
for (i = 0; i < n; ++i) {
if (i === h) continue;
y1 = vertices[i][1] - vertices[h][1];
x1 = vertices[i][0] - vertices[h][0];
points.push({angle: Math.atan2(y1, x1), index: i});
}
points.sort(function(a, b) { return a.angle - b.angle; });
// toss out duplicate angles
a = points[0].angle;
v = points[0].index;
u = 0;
for (i = 1; i < plen; ++i) {
j = points[i].index;
if (a == points[i].angle) {
// keep angle for point most distant from the reference
x1 = vertices[v][0] - vertices[h][0];
y1 = vertices[v][1] - vertices[h][1];
x2 = vertices[j][0] - vertices[h][0];
y2 = vertices[j][1] - vertices[h][1];
if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) {
points[i].index = -1;
continue;
} else {
points[u].index = -1;
}
}
a = points[i].angle;
u = i;
v = j;
}
// initialize the stack
stack.push(h);
for (i = 0, j = 0; i < 2; ++j) {
if (points[j].index > -1) {
stack.push(points[j].index);
i++;
}
}
sp = stack.length;
// do graham's scan
for (; j < plen; ++j) {
if (points[j].index < 0) continue; // skip tossed out points
while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) {
--sp;
}
stack[sp++] = points[j].index;
}
// construct the hull
var poly = [];
for (i = sp - 1; i >= 0; --i) poly.push(data[stack[i]]);
return poly;
}
hull.x = function(_) {
return arguments.length ? (x = _, hull) : x;
};
hull.y = function(_) {
return arguments.length ? (y = _, hull) : y;
};
return hull;
};
// are three points in counter-clockwise order?
function d3_geom_hullCCW(i1, i2, i3, v) {
var t, a, b, c, d, e, f;
t = v[i1]; a = t[0]; b = t[1];
t = v[i2]; c = t[0]; d = t[1];
t = v[i3]; e = t[0]; f = t[1];
return (f - b) * (c - a) - (d - b) * (e - a) > 0;
}
var d3_ease_default = function() { return d3_identity; };