Spaces:
Sleeping
Sleeping
| import "../core/functor"; | |
| import "../math/trigonometry"; | |
| import "geom"; | |
| import "point"; | |
| /** | |
| * Computes the 2D convex hull of a set of points using the monotone chain | |
| * algorithm: | |
| * http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain) | |
| * | |
| * The runtime of this algorithm is O(n log n), where n is the number of input | |
| * points. However in practice it outperforms other O(n log n) hulls. | |
| * | |
| * @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) { | |
| // Hull of < 3 points is not well-defined | |
| if (data.length < 3) return []; | |
| var fx = d3_functor(x), | |
| fy = d3_functor(y), | |
| i, | |
| n = data.length, | |
| points = [], // of the form [[x0, y0, 0], ..., [xn, yn, n]] | |
| flippedPoints = []; | |
| for (i = 0 ; i < n; i++) { | |
| points.push([+fx.call(this, data[i], i), +fy.call(this, data[i], i), i]); | |
| } | |
| // sort ascending by x-coord first, y-coord second | |
| points.sort(d3_geom_hullOrder); | |
| // we flip bottommost points across y axis so we can use the upper hull routine on both | |
| for (i = 0; i < n; i++) flippedPoints.push([points[i][0], -points[i][1]]); | |
| var upper = d3_geom_hullUpper(points), | |
| lower = d3_geom_hullUpper(flippedPoints); | |
| // construct the polygon, removing possible duplicate endpoints | |
| var skipLeft = lower[0] === upper[0], | |
| skipRight = lower[lower.length - 1] === upper[upper.length - 1], | |
| polygon = []; | |
| // add upper hull in r->l order | |
| // then add lower hull in l->r order | |
| for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); | |
| for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); | |
| return polygon; | |
| } | |
| hull.x = function(_) { | |
| return arguments.length ? (x = _, hull) : x; | |
| }; | |
| hull.y = function(_) { | |
| return arguments.length ? (y = _, hull) : y; | |
| }; | |
| return hull; | |
| }; | |
| // finds the 'upper convex hull' (see wiki link above) | |
| // assumes points arg has >=3 elements, is sorted by x, unique in y | |
| // returns array of indices into points in left to right order | |
| function d3_geom_hullUpper(points) { | |
| var n = points.length, | |
| hull = [0, 1], | |
| hs = 2; // hull size | |
| for (var i = 2; i < n; i++) { | |
| while (hs > 1 && d3_cross2d(points[hull[hs-2]], points[hull[hs-1]], points[i]) <= 0) --hs; | |
| hull[hs++] = i; | |
| } | |
| // we slice to make sure that the points we 'popped' from hull don't stay behind | |
| return hull.slice(0, hs); | |
| } | |
| // comparator for ascending sort by x-coord first, y-coord second | |
| function d3_geom_hullOrder(a, b) { | |
| return a[0] - b[0] || a[1] - b[1]; | |
| } | |