You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
999 lines
23 KiB
999 lines
23 KiB
// https://d3js.org/d3-voronoi/ v1.1.4 Copyright 2018 Mike Bostock |
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : |
|
typeof define === 'function' && define.amd ? define(['exports'], factory) : |
|
(factory((global.d3 = global.d3 || {}))); |
|
}(this, (function (exports) { 'use strict'; |
|
|
|
function constant(x) { |
|
return function() { |
|
return x; |
|
}; |
|
} |
|
|
|
function x(d) { |
|
return d[0]; |
|
} |
|
|
|
function y(d) { |
|
return d[1]; |
|
} |
|
|
|
function RedBlackTree() { |
|
this._ = null; // root node |
|
} |
|
|
|
function RedBlackNode(node) { |
|
node.U = // parent node |
|
node.C = // color - true for red, false for black |
|
node.L = // left node |
|
node.R = // right node |
|
node.P = // previous node |
|
node.N = null; // next node |
|
} |
|
|
|
RedBlackTree.prototype = { |
|
constructor: RedBlackTree, |
|
|
|
insert: function(after, node) { |
|
var parent, grandpa, uncle; |
|
|
|
if (after) { |
|
node.P = after; |
|
node.N = after.N; |
|
if (after.N) after.N.P = node; |
|
after.N = node; |
|
if (after.R) { |
|
after = after.R; |
|
while (after.L) after = after.L; |
|
after.L = node; |
|
} else { |
|
after.R = node; |
|
} |
|
parent = after; |
|
} else if (this._) { |
|
after = RedBlackFirst(this._); |
|
node.P = null; |
|
node.N = after; |
|
after.P = after.L = node; |
|
parent = after; |
|
} else { |
|
node.P = node.N = null; |
|
this._ = node; |
|
parent = null; |
|
} |
|
node.L = node.R = null; |
|
node.U = parent; |
|
node.C = true; |
|
|
|
after = node; |
|
while (parent && parent.C) { |
|
grandpa = parent.U; |
|
if (parent === grandpa.L) { |
|
uncle = grandpa.R; |
|
if (uncle && uncle.C) { |
|
parent.C = uncle.C = false; |
|
grandpa.C = true; |
|
after = grandpa; |
|
} else { |
|
if (after === parent.R) { |
|
RedBlackRotateLeft(this, parent); |
|
after = parent; |
|
parent = after.U; |
|
} |
|
parent.C = false; |
|
grandpa.C = true; |
|
RedBlackRotateRight(this, grandpa); |
|
} |
|
} else { |
|
uncle = grandpa.L; |
|
if (uncle && uncle.C) { |
|
parent.C = uncle.C = false; |
|
grandpa.C = true; |
|
after = grandpa; |
|
} else { |
|
if (after === parent.L) { |
|
RedBlackRotateRight(this, parent); |
|
after = parent; |
|
parent = after.U; |
|
} |
|
parent.C = false; |
|
grandpa.C = true; |
|
RedBlackRotateLeft(this, grandpa); |
|
} |
|
} |
|
parent = after.U; |
|
} |
|
this._.C = false; |
|
}, |
|
|
|
remove: function(node) { |
|
if (node.N) node.N.P = node.P; |
|
if (node.P) node.P.N = node.N; |
|
node.N = node.P = null; |
|
|
|
var parent = node.U, |
|
sibling, |
|
left = node.L, |
|
right = node.R, |
|
next, |
|
red; |
|
|
|
if (!left) next = right; |
|
else if (!right) next = left; |
|
else next = RedBlackFirst(right); |
|
|
|
if (parent) { |
|
if (parent.L === node) parent.L = next; |
|
else parent.R = next; |
|
} else { |
|
this._ = next; |
|
} |
|
|
|
if (left && right) { |
|
red = next.C; |
|
next.C = node.C; |
|
next.L = left; |
|
left.U = next; |
|
if (next !== right) { |
|
parent = next.U; |
|
next.U = node.U; |
|
node = next.R; |
|
parent.L = node; |
|
next.R = right; |
|
right.U = next; |
|
} else { |
|
next.U = parent; |
|
parent = next; |
|
node = next.R; |
|
} |
|
} else { |
|
red = node.C; |
|
node = next; |
|
} |
|
|
|
if (node) node.U = parent; |
|
if (red) return; |
|
if (node && node.C) { node.C = false; return; } |
|
|
|
do { |
|
if (node === this._) break; |
|
if (node === parent.L) { |
|
sibling = parent.R; |
|
if (sibling.C) { |
|
sibling.C = false; |
|
parent.C = true; |
|
RedBlackRotateLeft(this, parent); |
|
sibling = parent.R; |
|
} |
|
if ((sibling.L && sibling.L.C) |
|
|| (sibling.R && sibling.R.C)) { |
|
if (!sibling.R || !sibling.R.C) { |
|
sibling.L.C = false; |
|
sibling.C = true; |
|
RedBlackRotateRight(this, sibling); |
|
sibling = parent.R; |
|
} |
|
sibling.C = parent.C; |
|
parent.C = sibling.R.C = false; |
|
RedBlackRotateLeft(this, parent); |
|
node = this._; |
|
break; |
|
} |
|
} else { |
|
sibling = parent.L; |
|
if (sibling.C) { |
|
sibling.C = false; |
|
parent.C = true; |
|
RedBlackRotateRight(this, parent); |
|
sibling = parent.L; |
|
} |
|
if ((sibling.L && sibling.L.C) |
|
|| (sibling.R && sibling.R.C)) { |
|
if (!sibling.L || !sibling.L.C) { |
|
sibling.R.C = false; |
|
sibling.C = true; |
|
RedBlackRotateLeft(this, sibling); |
|
sibling = parent.L; |
|
} |
|
sibling.C = parent.C; |
|
parent.C = sibling.L.C = false; |
|
RedBlackRotateRight(this, parent); |
|
node = this._; |
|
break; |
|
} |
|
} |
|
sibling.C = true; |
|
node = parent; |
|
parent = parent.U; |
|
} while (!node.C); |
|
|
|
if (node) node.C = false; |
|
} |
|
}; |
|
|
|
function RedBlackRotateLeft(tree, node) { |
|
var p = node, |
|
q = node.R, |
|
parent = p.U; |
|
|
|
if (parent) { |
|
if (parent.L === p) parent.L = q; |
|
else parent.R = q; |
|
} else { |
|
tree._ = q; |
|
} |
|
|
|
q.U = parent; |
|
p.U = q; |
|
p.R = q.L; |
|
if (p.R) p.R.U = p; |
|
q.L = p; |
|
} |
|
|
|
function RedBlackRotateRight(tree, node) { |
|
var p = node, |
|
q = node.L, |
|
parent = p.U; |
|
|
|
if (parent) { |
|
if (parent.L === p) parent.L = q; |
|
else parent.R = q; |
|
} else { |
|
tree._ = q; |
|
} |
|
|
|
q.U = parent; |
|
p.U = q; |
|
p.L = q.R; |
|
if (p.L) p.L.U = p; |
|
q.R = p; |
|
} |
|
|
|
function RedBlackFirst(node) { |
|
while (node.L) node = node.L; |
|
return node; |
|
} |
|
|
|
function createEdge(left, right, v0, v1) { |
|
var edge = [null, null], |
|
index = edges.push(edge) - 1; |
|
edge.left = left; |
|
edge.right = right; |
|
if (v0) setEdgeEnd(edge, left, right, v0); |
|
if (v1) setEdgeEnd(edge, right, left, v1); |
|
cells[left.index].halfedges.push(index); |
|
cells[right.index].halfedges.push(index); |
|
return edge; |
|
} |
|
|
|
function createBorderEdge(left, v0, v1) { |
|
var edge = [v0, v1]; |
|
edge.left = left; |
|
return edge; |
|
} |
|
|
|
function setEdgeEnd(edge, left, right, vertex) { |
|
if (!edge[0] && !edge[1]) { |
|
edge[0] = vertex; |
|
edge.left = left; |
|
edge.right = right; |
|
} else if (edge.left === right) { |
|
edge[1] = vertex; |
|
} else { |
|
edge[0] = vertex; |
|
} |
|
} |
|
|
|
// Liang–Barsky line clipping. |
|
function clipEdge(edge, x0, y0, x1, y1) { |
|
var a = edge[0], |
|
b = edge[1], |
|
ax = a[0], |
|
ay = a[1], |
|
bx = b[0], |
|
by = b[1], |
|
t0 = 0, |
|
t1 = 1, |
|
dx = bx - ax, |
|
dy = by - ay, |
|
r; |
|
|
|
r = x0 - ax; |
|
if (!dx && r > 0) return; |
|
r /= dx; |
|
if (dx < 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} else if (dx > 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} |
|
|
|
r = x1 - ax; |
|
if (!dx && r < 0) return; |
|
r /= dx; |
|
if (dx < 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} else if (dx > 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} |
|
|
|
r = y0 - ay; |
|
if (!dy && r > 0) return; |
|
r /= dy; |
|
if (dy < 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} else if (dy > 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} |
|
|
|
r = y1 - ay; |
|
if (!dy && r < 0) return; |
|
r /= dy; |
|
if (dy < 0) { |
|
if (r > t1) return; |
|
if (r > t0) t0 = r; |
|
} else if (dy > 0) { |
|
if (r < t0) return; |
|
if (r < t1) t1 = r; |
|
} |
|
|
|
if (!(t0 > 0) && !(t1 < 1)) return true; // TODO Better check? |
|
|
|
if (t0 > 0) edge[0] = [ax + t0 * dx, ay + t0 * dy]; |
|
if (t1 < 1) edge[1] = [ax + t1 * dx, ay + t1 * dy]; |
|
return true; |
|
} |
|
|
|
function connectEdge(edge, x0, y0, x1, y1) { |
|
var v1 = edge[1]; |
|
if (v1) return true; |
|
|
|
var v0 = edge[0], |
|
left = edge.left, |
|
right = edge.right, |
|
lx = left[0], |
|
ly = left[1], |
|
rx = right[0], |
|
ry = right[1], |
|
fx = (lx + rx) / 2, |
|
fy = (ly + ry) / 2, |
|
fm, |
|
fb; |
|
|
|
if (ry === ly) { |
|
if (fx < x0 || fx >= x1) return; |
|
if (lx > rx) { |
|
if (!v0) v0 = [fx, y0]; |
|
else if (v0[1] >= y1) return; |
|
v1 = [fx, y1]; |
|
} else { |
|
if (!v0) v0 = [fx, y1]; |
|
else if (v0[1] < y0) return; |
|
v1 = [fx, y0]; |
|
} |
|
} else { |
|
fm = (lx - rx) / (ry - ly); |
|
fb = fy - fm * fx; |
|
if (fm < -1 || fm > 1) { |
|
if (lx > rx) { |
|
if (!v0) v0 = [(y0 - fb) / fm, y0]; |
|
else if (v0[1] >= y1) return; |
|
v1 = [(y1 - fb) / fm, y1]; |
|
} else { |
|
if (!v0) v0 = [(y1 - fb) / fm, y1]; |
|
else if (v0[1] < y0) return; |
|
v1 = [(y0 - fb) / fm, y0]; |
|
} |
|
} else { |
|
if (ly < ry) { |
|
if (!v0) v0 = [x0, fm * x0 + fb]; |
|
else if (v0[0] >= x1) return; |
|
v1 = [x1, fm * x1 + fb]; |
|
} else { |
|
if (!v0) v0 = [x1, fm * x1 + fb]; |
|
else if (v0[0] < x0) return; |
|
v1 = [x0, fm * x0 + fb]; |
|
} |
|
} |
|
} |
|
|
|
edge[0] = v0; |
|
edge[1] = v1; |
|
return true; |
|
} |
|
|
|
function clipEdges(x0, y0, x1, y1) { |
|
var i = edges.length, |
|
edge; |
|
|
|
while (i--) { |
|
if (!connectEdge(edge = edges[i], x0, y0, x1, y1) |
|
|| !clipEdge(edge, x0, y0, x1, y1) |
|
|| !(Math.abs(edge[0][0] - edge[1][0]) > epsilon |
|
|| Math.abs(edge[0][1] - edge[1][1]) > epsilon)) { |
|
delete edges[i]; |
|
} |
|
} |
|
} |
|
|
|
function createCell(site) { |
|
return cells[site.index] = { |
|
site: site, |
|
halfedges: [] |
|
}; |
|
} |
|
|
|
function cellHalfedgeAngle(cell, edge) { |
|
var site = cell.site, |
|
va = edge.left, |
|
vb = edge.right; |
|
if (site === vb) vb = va, va = site; |
|
if (vb) return Math.atan2(vb[1] - va[1], vb[0] - va[0]); |
|
if (site === va) va = edge[1], vb = edge[0]; |
|
else va = edge[0], vb = edge[1]; |
|
return Math.atan2(va[0] - vb[0], vb[1] - va[1]); |
|
} |
|
|
|
function cellHalfedgeStart(cell, edge) { |
|
return edge[+(edge.left !== cell.site)]; |
|
} |
|
|
|
function cellHalfedgeEnd(cell, edge) { |
|
return edge[+(edge.left === cell.site)]; |
|
} |
|
|
|
function sortCellHalfedges() { |
|
for (var i = 0, n = cells.length, cell, halfedges, j, m; i < n; ++i) { |
|
if ((cell = cells[i]) && (m = (halfedges = cell.halfedges).length)) { |
|
var index = new Array(m), |
|
array = new Array(m); |
|
for (j = 0; j < m; ++j) index[j] = j, array[j] = cellHalfedgeAngle(cell, edges[halfedges[j]]); |
|
index.sort(function(i, j) { return array[j] - array[i]; }); |
|
for (j = 0; j < m; ++j) array[j] = halfedges[index[j]]; |
|
for (j = 0; j < m; ++j) halfedges[j] = array[j]; |
|
} |
|
} |
|
} |
|
|
|
function clipCells(x0, y0, x1, y1) { |
|
var nCells = cells.length, |
|
iCell, |
|
cell, |
|
site, |
|
iHalfedge, |
|
halfedges, |
|
nHalfedges, |
|
start, |
|
startX, |
|
startY, |
|
end, |
|
endX, |
|
endY, |
|
cover = true; |
|
|
|
for (iCell = 0; iCell < nCells; ++iCell) { |
|
if (cell = cells[iCell]) { |
|
site = cell.site; |
|
halfedges = cell.halfedges; |
|
iHalfedge = halfedges.length; |
|
|
|
// Remove any dangling clipped edges. |
|
while (iHalfedge--) { |
|
if (!edges[halfedges[iHalfedge]]) { |
|
halfedges.splice(iHalfedge, 1); |
|
} |
|
} |
|
|
|
// Insert any border edges as necessary. |
|
iHalfedge = 0, nHalfedges = halfedges.length; |
|
while (iHalfedge < nHalfedges) { |
|
end = cellHalfedgeEnd(cell, edges[halfedges[iHalfedge]]), endX = end[0], endY = end[1]; |
|
start = cellHalfedgeStart(cell, edges[halfedges[++iHalfedge % nHalfedges]]), startX = start[0], startY = start[1]; |
|
if (Math.abs(endX - startX) > epsilon || Math.abs(endY - startY) > epsilon) { |
|
halfedges.splice(iHalfedge, 0, edges.push(createBorderEdge(site, end, |
|
Math.abs(endX - x0) < epsilon && y1 - endY > epsilon ? [x0, Math.abs(startX - x0) < epsilon ? startY : y1] |
|
: Math.abs(endY - y1) < epsilon && x1 - endX > epsilon ? [Math.abs(startY - y1) < epsilon ? startX : x1, y1] |
|
: Math.abs(endX - x1) < epsilon && endY - y0 > epsilon ? [x1, Math.abs(startX - x1) < epsilon ? startY : y0] |
|
: Math.abs(endY - y0) < epsilon && endX - x0 > epsilon ? [Math.abs(startY - y0) < epsilon ? startX : x0, y0] |
|
: null)) - 1); |
|
++nHalfedges; |
|
} |
|
} |
|
|
|
if (nHalfedges) cover = false; |
|
} |
|
} |
|
|
|
// If there weren’t any edges, have the closest site cover the extent. |
|
// It doesn’t matter which corner of the extent we measure! |
|
if (cover) { |
|
var dx, dy, d2, dc = Infinity; |
|
|
|
for (iCell = 0, cover = null; iCell < nCells; ++iCell) { |
|
if (cell = cells[iCell]) { |
|
site = cell.site; |
|
dx = site[0] - x0; |
|
dy = site[1] - y0; |
|
d2 = dx * dx + dy * dy; |
|
if (d2 < dc) dc = d2, cover = cell; |
|
} |
|
} |
|
|
|
if (cover) { |
|
var v00 = [x0, y0], v01 = [x0, y1], v11 = [x1, y1], v10 = [x1, y0]; |
|
cover.halfedges.push( |
|
edges.push(createBorderEdge(site = cover.site, v00, v01)) - 1, |
|
edges.push(createBorderEdge(site, v01, v11)) - 1, |
|
edges.push(createBorderEdge(site, v11, v10)) - 1, |
|
edges.push(createBorderEdge(site, v10, v00)) - 1 |
|
); |
|
} |
|
} |
|
|
|
// Lastly delete any cells with no edges; these were entirely clipped. |
|
for (iCell = 0; iCell < nCells; ++iCell) { |
|
if (cell = cells[iCell]) { |
|
if (!cell.halfedges.length) { |
|
delete cells[iCell]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
var circlePool = []; |
|
|
|
var firstCircle; |
|
|
|
function Circle() { |
|
RedBlackNode(this); |
|
this.x = |
|
this.y = |
|
this.arc = |
|
this.site = |
|
this.cy = null; |
|
} |
|
|
|
function attachCircle(arc) { |
|
var lArc = arc.P, |
|
rArc = arc.N; |
|
|
|
if (!lArc || !rArc) return; |
|
|
|
var lSite = lArc.site, |
|
cSite = arc.site, |
|
rSite = rArc.site; |
|
|
|
if (lSite === rSite) return; |
|
|
|
var bx = cSite[0], |
|
by = cSite[1], |
|
ax = lSite[0] - bx, |
|
ay = lSite[1] - by, |
|
cx = rSite[0] - bx, |
|
cy = rSite[1] - by; |
|
|
|
var d = 2 * (ax * cy - ay * cx); |
|
if (d >= -epsilon2) return; |
|
|
|
var ha = ax * ax + ay * ay, |
|
hc = cx * cx + cy * cy, |
|
x = (cy * ha - ay * hc) / d, |
|
y = (ax * hc - cx * ha) / d; |
|
|
|
var circle = circlePool.pop() || new Circle; |
|
circle.arc = arc; |
|
circle.site = cSite; |
|
circle.x = x + bx; |
|
circle.y = (circle.cy = y + by) + Math.sqrt(x * x + y * y); // y bottom |
|
|
|
arc.circle = circle; |
|
|
|
var before = null, |
|
node = circles._; |
|
|
|
while (node) { |
|
if (circle.y < node.y || (circle.y === node.y && circle.x <= node.x)) { |
|
if (node.L) node = node.L; |
|
else { before = node.P; break; } |
|
} else { |
|
if (node.R) node = node.R; |
|
else { before = node; break; } |
|
} |
|
} |
|
|
|
circles.insert(before, circle); |
|
if (!before) firstCircle = circle; |
|
} |
|
|
|
function detachCircle(arc) { |
|
var circle = arc.circle; |
|
if (circle) { |
|
if (!circle.P) firstCircle = circle.N; |
|
circles.remove(circle); |
|
circlePool.push(circle); |
|
RedBlackNode(circle); |
|
arc.circle = null; |
|
} |
|
} |
|
|
|
var beachPool = []; |
|
|
|
function Beach() { |
|
RedBlackNode(this); |
|
this.edge = |
|
this.site = |
|
this.circle = null; |
|
} |
|
|
|
function createBeach(site) { |
|
var beach = beachPool.pop() || new Beach; |
|
beach.site = site; |
|
return beach; |
|
} |
|
|
|
function detachBeach(beach) { |
|
detachCircle(beach); |
|
beaches.remove(beach); |
|
beachPool.push(beach); |
|
RedBlackNode(beach); |
|
} |
|
|
|
function removeBeach(beach) { |
|
var circle = beach.circle, |
|
x = circle.x, |
|
y = circle.cy, |
|
vertex = [x, y], |
|
previous = beach.P, |
|
next = beach.N, |
|
disappearing = [beach]; |
|
|
|
detachBeach(beach); |
|
|
|
var lArc = previous; |
|
while (lArc.circle |
|
&& Math.abs(x - lArc.circle.x) < epsilon |
|
&& Math.abs(y - lArc.circle.cy) < epsilon) { |
|
previous = lArc.P; |
|
disappearing.unshift(lArc); |
|
detachBeach(lArc); |
|
lArc = previous; |
|
} |
|
|
|
disappearing.unshift(lArc); |
|
detachCircle(lArc); |
|
|
|
var rArc = next; |
|
while (rArc.circle |
|
&& Math.abs(x - rArc.circle.x) < epsilon |
|
&& Math.abs(y - rArc.circle.cy) < epsilon) { |
|
next = rArc.N; |
|
disappearing.push(rArc); |
|
detachBeach(rArc); |
|
rArc = next; |
|
} |
|
|
|
disappearing.push(rArc); |
|
detachCircle(rArc); |
|
|
|
var nArcs = disappearing.length, |
|
iArc; |
|
for (iArc = 1; iArc < nArcs; ++iArc) { |
|
rArc = disappearing[iArc]; |
|
lArc = disappearing[iArc - 1]; |
|
setEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); |
|
} |
|
|
|
lArc = disappearing[0]; |
|
rArc = disappearing[nArcs - 1]; |
|
rArc.edge = createEdge(lArc.site, rArc.site, null, vertex); |
|
|
|
attachCircle(lArc); |
|
attachCircle(rArc); |
|
} |
|
|
|
function addBeach(site) { |
|
var x = site[0], |
|
directrix = site[1], |
|
lArc, |
|
rArc, |
|
dxl, |
|
dxr, |
|
node = beaches._; |
|
|
|
while (node) { |
|
dxl = leftBreakPoint(node, directrix) - x; |
|
if (dxl > epsilon) node = node.L; else { |
|
dxr = x - rightBreakPoint(node, directrix); |
|
if (dxr > epsilon) { |
|
if (!node.R) { |
|
lArc = node; |
|
break; |
|
} |
|
node = node.R; |
|
} else { |
|
if (dxl > -epsilon) { |
|
lArc = node.P; |
|
rArc = node; |
|
} else if (dxr > -epsilon) { |
|
lArc = node; |
|
rArc = node.N; |
|
} else { |
|
lArc = rArc = node; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
createCell(site); |
|
var newArc = createBeach(site); |
|
beaches.insert(lArc, newArc); |
|
|
|
if (!lArc && !rArc) return; |
|
|
|
if (lArc === rArc) { |
|
detachCircle(lArc); |
|
rArc = createBeach(lArc.site); |
|
beaches.insert(newArc, rArc); |
|
newArc.edge = rArc.edge = createEdge(lArc.site, newArc.site); |
|
attachCircle(lArc); |
|
attachCircle(rArc); |
|
return; |
|
} |
|
|
|
if (!rArc) { // && lArc |
|
newArc.edge = createEdge(lArc.site, newArc.site); |
|
return; |
|
} |
|
|
|
// else lArc !== rArc |
|
detachCircle(lArc); |
|
detachCircle(rArc); |
|
|
|
var lSite = lArc.site, |
|
ax = lSite[0], |
|
ay = lSite[1], |
|
bx = site[0] - ax, |
|
by = site[1] - ay, |
|
rSite = rArc.site, |
|
cx = rSite[0] - ax, |
|
cy = rSite[1] - ay, |
|
d = 2 * (bx * cy - by * cx), |
|
hb = bx * bx + by * by, |
|
hc = cx * cx + cy * cy, |
|
vertex = [(cy * hb - by * hc) / d + ax, (bx * hc - cx * hb) / d + ay]; |
|
|
|
setEdgeEnd(rArc.edge, lSite, rSite, vertex); |
|
newArc.edge = createEdge(lSite, site, null, vertex); |
|
rArc.edge = createEdge(site, rSite, null, vertex); |
|
attachCircle(lArc); |
|
attachCircle(rArc); |
|
} |
|
|
|
function leftBreakPoint(arc, directrix) { |
|
var site = arc.site, |
|
rfocx = site[0], |
|
rfocy = site[1], |
|
pby2 = rfocy - directrix; |
|
|
|
if (!pby2) return rfocx; |
|
|
|
var lArc = arc.P; |
|
if (!lArc) return -Infinity; |
|
|
|
site = lArc.site; |
|
var lfocx = site[0], |
|
lfocy = site[1], |
|
plby2 = lfocy - directrix; |
|
|
|
if (!plby2) return lfocx; |
|
|
|
var hl = lfocx - rfocx, |
|
aby2 = 1 / pby2 - 1 / plby2, |
|
b = hl / plby2; |
|
|
|
if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; |
|
|
|
return (rfocx + lfocx) / 2; |
|
} |
|
|
|
function rightBreakPoint(arc, directrix) { |
|
var rArc = arc.N; |
|
if (rArc) return leftBreakPoint(rArc, directrix); |
|
var site = arc.site; |
|
return site[1] === directrix ? site[0] : Infinity; |
|
} |
|
|
|
var epsilon = 1e-6; |
|
var epsilon2 = 1e-12; |
|
var beaches; |
|
var cells; |
|
var circles; |
|
var edges; |
|
|
|
function triangleArea(a, b, c) { |
|
return (a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]); |
|
} |
|
|
|
function lexicographic(a, b) { |
|
return b[1] - a[1] |
|
|| b[0] - a[0]; |
|
} |
|
|
|
function Diagram(sites, extent) { |
|
var site = sites.sort(lexicographic).pop(), |
|
x, |
|
y, |
|
circle; |
|
|
|
edges = []; |
|
cells = new Array(sites.length); |
|
beaches = new RedBlackTree; |
|
circles = new RedBlackTree; |
|
|
|
while (true) { |
|
circle = firstCircle; |
|
if (site && (!circle || site[1] < circle.y || (site[1] === circle.y && site[0] < circle.x))) { |
|
if (site[0] !== x || site[1] !== y) { |
|
addBeach(site); |
|
x = site[0], y = site[1]; |
|
} |
|
site = sites.pop(); |
|
} else if (circle) { |
|
removeBeach(circle.arc); |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
sortCellHalfedges(); |
|
|
|
if (extent) { |
|
var x0 = +extent[0][0], |
|
y0 = +extent[0][1], |
|
x1 = +extent[1][0], |
|
y1 = +extent[1][1]; |
|
clipEdges(x0, y0, x1, y1); |
|
clipCells(x0, y0, x1, y1); |
|
} |
|
|
|
this.edges = edges; |
|
this.cells = cells; |
|
|
|
beaches = |
|
circles = |
|
edges = |
|
cells = null; |
|
} |
|
|
|
Diagram.prototype = { |
|
constructor: Diagram, |
|
|
|
polygons: function() { |
|
var edges = this.edges; |
|
|
|
return this.cells.map(function(cell) { |
|
var polygon = cell.halfedges.map(function(i) { return cellHalfedgeStart(cell, edges[i]); }); |
|
polygon.data = cell.site.data; |
|
return polygon; |
|
}); |
|
}, |
|
|
|
triangles: function() { |
|
var triangles = [], |
|
edges = this.edges; |
|
|
|
this.cells.forEach(function(cell, i) { |
|
if (!(m = (halfedges = cell.halfedges).length)) return; |
|
var site = cell.site, |
|
halfedges, |
|
j = -1, |
|
m, |
|
s0, |
|
e1 = edges[halfedges[m - 1]], |
|
s1 = e1.left === site ? e1.right : e1.left; |
|
|
|
while (++j < m) { |
|
s0 = s1; |
|
e1 = edges[halfedges[j]]; |
|
s1 = e1.left === site ? e1.right : e1.left; |
|
if (s0 && s1 && i < s0.index && i < s1.index && triangleArea(site, s0, s1) < 0) { |
|
triangles.push([site.data, s0.data, s1.data]); |
|
} |
|
} |
|
}); |
|
|
|
return triangles; |
|
}, |
|
|
|
links: function() { |
|
return this.edges.filter(function(edge) { |
|
return edge.right; |
|
}).map(function(edge) { |
|
return { |
|
source: edge.left.data, |
|
target: edge.right.data |
|
}; |
|
}); |
|
}, |
|
|
|
find: function(x, y, radius) { |
|
var that = this, i0, i1 = that._found || 0, n = that.cells.length, cell; |
|
|
|
// Use the previously-found cell, or start with an arbitrary one. |
|
while (!(cell = that.cells[i1])) if (++i1 >= n) return null; |
|
var dx = x - cell.site[0], dy = y - cell.site[1], d2 = dx * dx + dy * dy; |
|
|
|
// Traverse the half-edges to find a closer cell, if any. |
|
do { |
|
cell = that.cells[i0 = i1], i1 = null; |
|
cell.halfedges.forEach(function(e) { |
|
var edge = that.edges[e], v = edge.left; |
|
if ((v === cell.site || !v) && !(v = edge.right)) return; |
|
var vx = x - v[0], vy = y - v[1], v2 = vx * vx + vy * vy; |
|
if (v2 < d2) d2 = v2, i1 = v.index; |
|
}); |
|
} while (i1 !== null); |
|
|
|
that._found = i0; |
|
|
|
return radius == null || d2 <= radius * radius ? cell.site : null; |
|
} |
|
}; |
|
|
|
function voronoi() { |
|
var x$$1 = x, |
|
y$$1 = y, |
|
extent = null; |
|
|
|
function voronoi(data) { |
|
return new Diagram(data.map(function(d, i) { |
|
var s = [Math.round(x$$1(d, i, data) / epsilon) * epsilon, Math.round(y$$1(d, i, data) / epsilon) * epsilon]; |
|
s.index = i; |
|
s.data = d; |
|
return s; |
|
}), extent); |
|
} |
|
|
|
voronoi.polygons = function(data) { |
|
return voronoi(data).polygons(); |
|
}; |
|
|
|
voronoi.links = function(data) { |
|
return voronoi(data).links(); |
|
}; |
|
|
|
voronoi.triangles = function(data) { |
|
return voronoi(data).triangles(); |
|
}; |
|
|
|
voronoi.x = function(_) { |
|
return arguments.length ? (x$$1 = typeof _ === "function" ? _ : constant(+_), voronoi) : x$$1; |
|
}; |
|
|
|
voronoi.y = function(_) { |
|
return arguments.length ? (y$$1 = typeof _ === "function" ? _ : constant(+_), voronoi) : y$$1; |
|
}; |
|
|
|
voronoi.extent = function(_) { |
|
return arguments.length ? (extent = _ == null ? null : [[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]], voronoi) : extent && [[extent[0][0], extent[0][1]], [extent[1][0], extent[1][1]]]; |
|
}; |
|
|
|
voronoi.size = function(_) { |
|
return arguments.length ? (extent = _ == null ? null : [[0, 0], [+_[0], +_[1]]], voronoi) : extent && [extent[1][0] - extent[0][0], extent[1][1] - extent[0][1]]; |
|
}; |
|
|
|
return voronoi; |
|
} |
|
|
|
exports.voronoi = voronoi; |
|
|
|
Object.defineProperty(exports, '__esModule', { value: true }); |
|
|
|
})));
|
|
|