diff options
author | Dan McGee <dan@archlinux.org> | 2011-12-05 17:01:02 -0600 |
---|---|---|
committer | Dan McGee <dan@archlinux.org> | 2011-12-05 17:01:02 -0600 |
commit | ffdaac4d8bb29afae9cd7c7fc96e0eb730fc2f2a (patch) | |
tree | 99fce3fbdab27e13bd8c8b374d78df2ca78c23eb /media | |
parent | f3a3ce5623ccecca92bf4b885615e1836be3508c (diff) |
Add D3 geometry package
This will be needed for the PGP visualization.
Signed-off-by: Dan McGee <dan@archlinux.org>
Diffstat (limited to 'media')
-rw-r--r-- | media/d3.geom.js | 835 | ||||
-rw-r--r-- | media/d3.geom.min.js | 1 |
2 files changed, 836 insertions, 0 deletions
diff --git a/media/d3.geom.js b/media/d3.geom.js new file mode 100644 index 00000000..d860c2bf --- /dev/null +++ b/media/d3.geom.js @@ -0,0 +1,835 @@ +(function(){d3.geom = {}; +/** + * Computes a contour for a given input grid function using the <a + * href="http://en.wikipedia.org/wiki/Marching_squares">marching + * squares</a> algorithm. Returns the contour polygon as an array of points. + * + * @param grid a two-input function(x, y) that returns true for values + * inside the contour and false for values outside the contour. + * @param start an optional starting point [x, y] on the grid. + * @returns polygon [[x1, y1], [x2, y2], …] + */ +d3.geom.contour = function(grid, start) { + var s = start || d3_geom_contourStart(grid), // starting point + c = [], // contour polygon + x = s[0], // current x position + y = s[1], // current y position + dx = 0, // next x direction + dy = 0, // next y direction + pdx = NaN, // previous x direction + pdy = NaN, // previous y direction + i = 0; + + do { + // determine marching squares index + i = 0; + if (grid(x-1, y-1)) i += 1; + if (grid(x, y-1)) i += 2; + if (grid(x-1, y )) i += 4; + if (grid(x, y )) i += 8; + + // determine next direction + if (i === 6) { + dx = pdy === -1 ? -1 : 1; + dy = 0; + } else if (i === 9) { + dx = 0; + dy = pdx === 1 ? -1 : 1; + } else { + dx = d3_geom_contourDx[i]; + dy = d3_geom_contourDy[i]; + } + + // update contour polygon + if (dx != pdx && dy != pdy) { + c.push([x, y]); + pdx = dx; + pdy = dy; + } + + x += dx; + y += dy; + } while (s[0] != x || s[1] != y); + + return c; +}; + +// lookup tables for marching directions +var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], + d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; + +function d3_geom_contourStart(grid) { + var x = 0, + y = 0; + + // search for a starting point; begin at origin + // and proceed along outward-expanding diagonals + while (true) { + if (grid(x,y)) { + return [x,y]; + } + if (x === 0) { + x = y + 1; + y = 0; + } else { + x = x - 1; + y = y + 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) { + if (vertices.length < 3) return []; + + var len = vertices.length, + plen = len - 1, + points = [], + stack = [], + i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + + // find the starting ref point: leftmost point with the minimum y coord + for (i=1; i<len; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = (vertices[i][0] < vertices[h][0] ? i : h); + } + } + + // calculate polar angles from ref point and sort + for (i=0; i<len; ++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; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + 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 === -1) 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=0; i<sp; ++i) { + poly.push(vertices[stack[i]]); + } + return poly; +} + +// 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; +} +// Note: requires coordinates to be counterclockwise and convex! +d3.geom.polygon = function(coordinates) { + + coordinates.area = function() { + var i = 0, + n = coordinates.length, + a = coordinates[n - 1][0] * coordinates[0][1], + b = coordinates[n - 1][1] * coordinates[0][0]; + while (++i < n) { + a += coordinates[i - 1][0] * coordinates[i][1]; + b += coordinates[i - 1][1] * coordinates[i][0]; + } + return (b - a) * .5; + }; + + coordinates.centroid = function(k) { + var i = -1, + n = coordinates.length - 1, + x = 0, + y = 0, + a, + b, + c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = coordinates[i]; + b = coordinates[i + 1]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [x * k, y * k]; + }; + + // The Sutherland-Hodgman clipping algorithm. + coordinates.clip = function(subject) { + var input, + i = -1, + n = coordinates.length, + j, + m, + a = coordinates[n - 1], + b, + c, + d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + + return coordinates; +}; + +function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); +} + +// Intersect two infinite lines cd and ab. +function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], + y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], + x13 = x1 - x3, + x21 = x2 - x1, + x43 = x4 - x3, + y13 = y1 - y3, + y21 = y2 - y1, + y43 = y4 - y3, + ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); + return [x1 + ua * x21, y1 + ua * y21]; +} +// Adapted from Nicolas Garcia Belmonte's JIT implementation: +// http://blog.thejit.org/2010/02/12/voronoi-tessellation/ +// http://blog.thejit.org/assets/voronoijs/voronoi.js +// See lib/jit/LICENSE for details. + +// Notes: +// +// This implementation does not clip the returned polygons, so if you want to +// clip them to a particular shape you will need to do that either in SVG or by +// post-processing with d3.geom.polygon's clip method. +// +// If any vertices are coincident or have NaN positions, the behavior of this +// method is undefined. Most likely invalid polygons will be returned. You +// should filter invalid points, and consolidate coincident points, before +// computing the tessellation. + +/** + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygons [[[x1, y1], [x2, y2], …], …] + */ +d3.geom.voronoi = function(vertices) { + var polygons = vertices.map(function() { return []; }); + + d3_voronoi_tessellate(vertices, function(e) { + var s1, + s2, + x1, + x2, + y1, + y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -1e6; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : 1e6; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -1e6; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : 1e6; + y2 = e.c - e.a * x2; + } + var v1 = [x1, y1], + v2 = [x2, y2]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + + // Reconnect the polygon segments into counterclockwise loops. + return polygons.map(function(polygon, i) { + var cx = vertices[i][0], + cy = vertices[i][1]; + polygon.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + return polygon.sort(function(a, b) { + return a.angle - b.angle; + }).filter(function(d, i) { + return !i || (d.angle - polygon[i - 1].angle > 1e-10); + }); + }); +}; + +var d3_voronoi_opposite = {"l": "r", "r": "l"}; + +function d3_voronoi_tessellate(vertices, callback) { + + var Sites = { + list: vertices + .map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }) + .sort(function(a, b) { + return a.y < b.y ? -1 + : a.y > b.y ? 1 + : a.x < b.x ? -1 + : a.x > b.x ? 1 + : 0; + }), + bottomSite: null + }; + + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + "l": null, + "r": null + }; + }, + + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + + right: function(he) { + return he.r; + }, + + left: function(he) { + return he.l; + }, + + leftRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[he.side]; + }, + + rightRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[d3_voronoi_opposite[he.side]]; + } + }; + + var Geom = { + + bisect: function(s1, s2) { + var newEdge = { + region: {"l": s1, "r": s2}, + ep: {"l": null, "r": null} + }; + + var dx = s2.x - s1.x, + dy = s2.y - s1.y, + adx = dx > 0 ? dx : -dx, + ady = dy > 0 ? dy : -dy; + + newEdge.c = s1.x * dx + s1.y * dy + + (dx * dx + dy * dy) * .5; + + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + + return newEdge; + }, + + intersect: function(el1, el2) { + var e1 = el1.edge, + e2 = el2.edge; + if (!e1 || !e2 || (e1.region.r == e2.region.r)) { + return null; + } + var d = (e1.a * e2.b) - (e1.b * e2.a); + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, + yint = (e2.c * e1.a - e1.c * e2.a) / d, + e1r = e1.region.r, + e2r = e2.region.r, + el, + e; + if ((e1r.y < e2r.y) || + (e1r.y == e2r.y && e1r.x < e2r.x)) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = (xint >= e.region.r.x); + if ((rightOfSite && (el.side === "l")) || + (!rightOfSite && (el.side === "r"))) { + return null; + } + return { + x: xint, + y: yint + }; + }, + + rightOf: function(he, p) { + var e = he.edge, + topsite = e.region.r, + rightOfSite = (p.x > topsite.x); + + if (rightOfSite && (he.side === "l")) { + return 1; + } + if (!rightOfSite && (he.side === "r")) { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, + dxp = p.x - topsite.x, + fast = 0, + above = 0; + + if ((!rightOfSite && (e.b < 0)) || + (rightOfSite && (e.b >= 0))) { + above = fast = (dyp >= e.b * dxp); + } else { + above = ((p.x + p.y * e.b) > e.c); + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = (e.b * (dxp * dxp - dyp * dyp)) < + (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); + + if (e.b < 0) { + above = !above; + } + } + } else /* e.b == 1 */ { + var yl = e.c - e.a * p.x, + t1 = p.y - yl, + t2 = p.x - topsite.x, + t3 = yl - topsite.y; + + above = (t1 * t1) > (t2 * t2 + t3 * t3); + } + return he.side === "l" ? above : !above; + }, + + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_voronoi_opposite[side]]) return; + callback(edge); + }, + + distance: function(s, t) { + var dx = s.x - t.x, + dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + + var EventQueue = { + list: [], + + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) { + var next = list[i]; + if (he.ystar > next.ystar || + (he.ystar == next.ystar && + site.x > next.vertex.x)) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + + del: function(he) { + for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {} + ls.splice(i, 1); + }, + + empty: function() { return EventQueue.list.length === 0; }, + + nextEvent: function(he) { + for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) { + if (ls[i] == he) return ls[i+1]; + } + return null; + }, + + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + + extractMin: function() { + return EventQueue.list.shift(); + } + }; + + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() + || newSite.y < newIntStar.y + || (newSite.y == newIntStar.y + && newSite.x < newIntStar.x))) { //new site is smallest + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { //intersection is smallest + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_voronoi_opposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + }//end while + + for (lbnd = EdgeList.right(EdgeList.leftEnd); + lbnd != EdgeList.rightEnd; + lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } +} +/** +* @param vertices [[x1, y1], [x2, y2], …] +* @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] + */ +d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { return []; }), + triangles = []; + + // Use the Voronoi tessellation to determine Delaunay edges. + d3_voronoi_tessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + + // Reconnect the edges into counterclockwise triangles. + edges.forEach(function(edge, i) { + var v = vertices[i], + cx = v[0], + cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([v, edge[j], edge[j + 1]]); + } + }); + + return triangles; +}; +// Constructs a new quadtree for the specified array of points. A quadtree is a +// two-dimensional recursive spatial subdivision. This implementation uses +// square partitions, dividing each square into four equally-sized squares. Each +// point exists in a unique node; if multiple points are in the same position, +// some points may be stored on internal nodes rather than leaf nodes. Quadtrees +// can be used to accelerate various spatial operations, such as the Barnes-Hut +// approximation for computing n-body forces, or collision detection. +d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var p, + i = -1, + n = points.length; + + // Type conversion for deprecated API. + if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); + + // Allow bounds to be specified explicitly. + if (arguments.length < 5) { + if (arguments.length === 3) { + y2 = x2 = y1; + y1 = x1; + } else { + x1 = y1 = Infinity; + x2 = y2 = -Infinity; + + // Compute bounds. + while (++i < n) { + p = points[i]; + if (p.x < x1) x1 = p.x; + if (p.y < y1) y1 = p.y; + if (p.x > x2) x2 = p.x; + if (p.y > y2) y2 = p.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, p, x1, y1, x2, y2) { + if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points + if (n.leaf) { + var v = n.point; + if (v) { + // 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 ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { + insertChild(n, p, x1, y1, x2, y2); + } else { + n.point = null; + insertChild(n, v, x1, y1, x2, y2); + insertChild(n, p, x1, y1, x2, y2); + } + } else { + n.point = p; + } + } else { + insertChild(n, p, x1, y1, x2, y2); + } + } + + // Recursively inserts the specified point p into a descendant of node n. The + // bounds are defined by [x1, x2] and [y1, y2]. + function insertChild(n, p, x1, y1, x2, y2) { + // Compute the split point, and the quadrant in which to insert p. + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .5, + right = p.x >= sx, + bottom = p.y >= sy, + i = (bottom << 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 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, p, x1, y1, x2, y2); + } + + // Create the root node. + var root = d3_geom_quadtreeNode(); + + root.add = function(p) { + insert(root, p, x1, y1, x2, y2); + }; + + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); + }; + + // Insert all points. + points.forEach(root.add); + return root; +}; + +function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null + }; +} + +function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .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_quadtreePoint(p) { + return { + x: p[0], + y: p[1] + }; +} +})(); diff --git a/media/d3.geom.min.js b/media/d3.geom.min.js new file mode 100644 index 00000000..2dc6395d --- /dev/null +++ b/media/d3.geom.min.js @@ -0,0 +1 @@ +(function(){function c(a){var b=0,c=0;for(;;){if(a(b,c))return[b,c];b===0?(b=c+1,c=0):(b-=1,c+=1)}}function d(a,b,c,d){var e,f,g,h,i,j,k;return e=d[a],f=e[0],g=e[1],e=d[b],h=e[0],i=e[1],e=d[c],j=e[0],k=e[1],(k-g)*(h-f)-(i-g)*(j-f)>0}function e(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function f(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function h(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.y<b.y?-1:a.y>b.y?1:a.x<b.x?-1:a.x>b.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[g[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y<i.y||h.y==i.y&&h.x<i.x?(j=a,k=c):(j=b,k=d);var l=f>=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)<j*f*(1+2*g/j+c.b*c.b),c.b<0&&(i=!i)}}else{var k=c.c-c.a*b.x,l=b.y-k,m=b.x-d.x,n=k-d.y;i=l*l>m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[g[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;d<g;d++){var h=e[d];if(a.ystar>h.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;b<d&&c[b]!=a;++b);c.splice(b,1)},empty:function(){return f.list.length===0},nextEvent:function(a){for(var b=0,c=f.list,d=c.length;b<d;++b)if(c[b]==a)return c[b+1];return null},min:function(){var a=f.list[0];return{x:a.vertex.x,y:a.ystar}},extractMin:function(){return f.list.shift()}};d.init(),c.bottomSite=c.list.shift();var h=c.list.shift(),i,j,k,l,m,n,o,p,q,r,s,t,u;for(;;){f.empty()||(i=f.min());if(h&&(f.empty()||h.y<i.y||h.y==i.y&&h.x<i.x))j=d.leftBound(h),k=d.right(j),o=d.rightRegion(j),t=e.bisect(o,h),n=d.createHalfEdge(t,"l"),d.insert(j,n),r=e.intersect(j,n),r&&(f.del(j),f.insert(j,r,e.distance(r,h))),j=n,n=d.createHalfEdge(t,"r"),d.insert(j,n),r=e.intersect(n,k),r&&f.insert(n,r,e.distance(r,h)),h=c.list.shift();else if(!f.empty())j=f.extractMin(),l=d.left(j),k=d.right(j),m=d.right(k),o=d.leftRegion(j),p=d.rightRegion(k),s=j.vertex,e.endPoint(j.edge,j.side,s),e.endPoint(k.edge,k.side,s),d.del(j),f.del(k),d.del(k),u="l",o.y>p.y&&(q=o,o=p,p=q,u="r"),t=e.bisect(o,p),n=d.createHalfEdge(t,u),d.insert(l,n),e.endPoint(t,g[u],s),r=e.intersect(l,n),r&&(f.del(l),f.insert(l,r,e.distance(r,o))),r=e.intersect(n,m),r&&f.insert(n,r,e.distance(r,o));else break}for(j=d.right(d.leftEnd);j!=d.rightEnd;j=d.right(j))b(j.edge)}function i(){return{leaf:!0,nodes:[],point:null}}function j(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&j(a,i[0],c,d,g,h),i[1]&&j(a,i[1],g,d,e,h),i[2]&&j(a,i[2],c,h,g,f),i[3]&&j(a,i[3],g,h,e,f)}}function k(a){return{x:a[0],y:a[1]}}d3.geom={},d3.geom.contour=function(d,e){var f=e||c(d),g=[],h=f[0],i=f[1],j=0,k=0,l=NaN,m=NaN,n=0;do n=0,d(h-1,i-1)&&(n+=1),d(h,i-1)&&(n+=2),d(h-1,i)&&(n+=4),d(h,i)&&(n+=8),n===6?(j=m===-1?-1:1,k=0):n===9?(j=0,k=l===1?-1:1):(j=a[n],k=b[n]),j!=l&&k!=m&&(g.push([h,i]),l=j,m=k),h+=j,i+=k;while(f[0]!=h||f[1]!=i);return g};var a=[1,0,1,1,-1,0,-1,1,0,0,0,0,-1,0,-1,NaN],b=[0,-1,0,0,0,-1,0,0,1,-1,1,1,0,-1,0,NaN];d3.geom.hull=function(a){if(a.length<3)return[];var b=a.length,c=b-1,e=[],f=[],g,h,i=0,j,k,l,m,n,o,p,q;for(g=1;g<b;++g)a[g][1]<a[i][1]?i=g:a[g][1]==a[i][1]&&(i=a[g][0]<a[i][0]?g:i);for(g=0;g<b;++g){if(g===i)continue;k=a[g][1]-a[i][1],j=a[g][0]-a[i][0],e.push({angle:Math.atan2(k,j),index:g})}e.sort(function(a,b){return a.angle-b.angle}),p=e[0].angle,o=e[0].index,n=0;for(g=1;g<c;++g)h=e[g].index,p==e[g].angle?(j=a[o][0]-a[i][0],k=a[o][1]-a[i][1],l=a[h][0]-a[i][0],m=a[h][1]-a[i][1],j*j+k*k>=l*l+m*m?e[g].index=-1:(e[n].index=-1,p=e[g].angle,n=g,o=h)):(p=e[g].angle,n=g,o=h);f.push(i);for(g=0,h=0;g<2;++h)e[h].index!==-1&&(f.push(e[h].index),g++);q=f.length;for(;h<c;++h){if(e[h].index===-1)continue;while(!d(f[q-2],f[q-1],e[h].index,a))--q;f[q++]=e[h].index}var r=[];for(g=0;g<q;++g)r.push(a[f[g]]);return r},d3.geom.polygon=function(a){return a.area=function(){var b=0,c=a.length,d=a[c-1][0]*a[0][1],e=a[c-1][1]*a[0][0];while(++b<c)d+=a[b-1][0]*a[b][1],e+=a[b-1][1]*a[b][0];return(e-d)*.5},a.centroid=function(b){var c=-1,d=a.length-1,e=0,f=0,g,h,i;arguments.length||(b=-1/(6*a.area()));while(++c<d)g=a[c],h=a[c+1],i=g[0]*h[1]-h[0]*g[1],e+=(g[0]+h[0])*i,f+=(g[1]+h[1])*i;return[e*b,f*b]},a.clip=function(b){var c,d=-1,g=a.length,h,i,j=a[g-1],k,l,m;while(++d<g){c=b.slice(),b.length=0,k=a[d],l=c[(i=c.length)-1],h=-1;while(++h<i)m=c[h],e(m,j,k)?(e(l,j,k)||b.push(f(l,m,j,k)),b.push(m)):e(l,j,k)&&b.push(f(l,m,j,k)),l=m;j=k}return b},a},d3.geom.voronoi=function(a){var b=a.map(function(){return[]});return h(a,function(a){var c,d,e,f,g,h;a.a===1&&a.b>=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var g={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return h(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h<i;h++)c.push([e,b[h],b[h+1]])}),c},d3.geom.quadtree=function(a,b,c,d,e){function n(a,b,c,d,e,f){if(isNaN(b.x)||isNaN(b.y))return;if(a.leaf){var g=a.point;g?Math.abs(g.x-b.x)+Math.abs(g.y-b.y)<.01?o(a,b,c,d,e,f):(a.point=null,o(a,g,c,d,e,f),o(a,b,c,d,e,f)):a.point=b}else o(a,b,c,d,e,f)}function o(a,b,c,d,e,f){var g=(c+e)*.5,h=(d+f)*.5,j=b.x>=g,k=b.y>=h,l=(k<<1)+j;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=i()),j?c=g:e=g,k?d=h:f=h,n(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(k));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++g<h)f=a[g],f.x<b&&(b=f.x),f.y<c&&(c=f.y),f.x>d&&(d=f.x),f.y>e&&(e=f.y);var l=d-b,m=e-c;l>m?e=c+l:d=b+m}var p=i();return p.add=function(a){n(p,a,b,c,d,e)},p.visit=function(a){j(a,p,b,c,d,e)},a.forEach(p.add),p}})();
\ No newline at end of file |