UNPKG

29.5 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-polygon')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-polygon'], factory) :
4 (factory((global.d3 = global.d3 || {}),global.d3,global.d3));
5}(this, function (exports,d3Array,d3Polygon) { 'use strict';
6
7 var epsilon = 1e-10;
8
9 function epsilonesque(n) {
10 return n <= epsilon && n >= -epsilon;
11 }
12
13 // IN: vectors or vertices
14 // OUT: dot product
15 function dot(v0, v1) {
16 return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z;
17 }
18
19 // IN: two vertex objects, v0 and v1
20 // OUT: true if they are linearly dependent, false otherwise
21 // from https://math.stackexchange.com/questions/1144357/how-can-i-prove-that-two-vectors-in-%E2%84%9D3-are-linearly-independent-iff-their-cro
22 function linearDependent(v0, v1) {
23 return (
24 epsilonesque(v0.x * v1.y - v0.y * v1.x) &&
25 epsilonesque(v0.y * v1.z - v0.z * v1.y) &&
26 epsilonesque(v0.z * v1.x - v0.x * v1.z)
27 );
28 }
29
30 // IN: an array of 2D-points [x,y]
31 // OUT: true if the set defines a convex polygon (non-intersecting, hole-free, non-concave)
32 // from https://gist.github.com/annatomka/82715127b74473859054, adapted to [x,y] syntax (instead of {x: ..., y: ...}) and optimizations
33 function polygonDirection(polygon) {
34 var sign, crossproduct, p0, p1, p2, v0, v1, i;
35
36 //begin: initialization
37 p0 = polygon[polygon.length - 2];
38 p1 = polygon[polygon.length - 1];
39 p2 = polygon[0];
40 v0 = vect(p0, p1);
41 v1 = vect(p1, p2);
42 crossproduct = calculateCrossproduct(v0, v1);
43 // console.log(`[[${p0}], [${p1}], [${p2}]] => (${v0}) x (${v1}) = ${crossproduct}`);
44 sign = Math.sign(crossproduct);
45 //end: initialization
46
47 p0 = p1; // p0 = polygon[polygon.length - 1];
48 p1 = p2; // p1 = polygon[0];
49 p2 = polygon[1];
50 v0 = v1;
51 v1 = vect(p1, p2);
52 crossproduct = calculateCrossproduct(v0, v1);
53 // console.log(`[[${p0}], [${p1}], [${p2}]] => (${v0}) x (${v1}) = ${crossproduct}`);
54 if (Math.sign(crossproduct) !== sign) {
55 return undefined;
56 } //different signs in cross products means concave polygon
57
58 //iterate on remaining 3 consecutive points
59 for (i = 2; i < polygon.length - 1; i++) {
60 p0 = p1;
61 p1 = p2;
62 p2 = polygon[i];
63 v0 = v1;
64 v1 = vect(p1, p2);
65 crossproduct = calculateCrossproduct(v0, v1);
66 // console.log(`[[${p0}], [${p1}], [${p2}]] => (${v0}) x (${v1}) = ${crossproduct}`);
67 if (Math.sign(crossproduct) !== sign) {
68 return undefined;
69 } //different signs in cross products means concave polygon
70 }
71
72 return sign;
73 }
74
75 function vect(from, to) {
76 return [to[0] - from[0], to[1] - from[1]];
77 }
78
79 function calculateCrossproduct(v0, v1) {
80 return v0[0] * v1[1] - v0[1] * v1[0];
81 }
82
83 // ConflictList and ConflictListNode
84
85 function ConflictListNode (face, vert) {
86 this.face = face;
87 this.vert = vert;
88 this.nextf = null;
89 this.prevf = null;
90 this.nextv = null;
91 this.prevv = null;
92 }
93
94 // IN: boolean forFace
95 function ConflictList (forFace) {
96 this.forFace = forFace;
97 this.head = null;
98 }
99
100 // IN: ConflictListNode cln
101 ConflictList.prototype.add = function(cln) {
102 if (this.head === null) {
103 this.head = cln;
104 } else {
105 if (this.forFace) { // Is FaceList
106 this.head.prevv = cln;
107 cln.nextv = this.head;
108 this.head = cln;
109 } else { // Is VertexList
110 this.head.prevf = cln;
111 cln.nextf = this.head;
112 this.head = cln;
113 }
114 }
115 }
116
117 ConflictList.prototype.isEmpty = function() {
118 return this.head === null;
119 }
120
121 // Array of faces visible
122 ConflictList.prototype.fill = function(visible) {
123 if (this.forFace) {
124 return;
125 }
126 var curr = this.head;
127 do {
128 visible.push(curr.face);
129 curr.face.marked = true;
130 curr = curr.nextf;
131 } while (curr !== null);
132 }
133
134 ConflictList.prototype.removeAll = function() {
135 if (this.forFace) { // Remove all vertices from Face
136 var curr = this.head;
137 do {
138 if (curr.prevf === null) { // Node is head
139 if (curr.nextf === null) {
140 curr.vert.conflicts.head = null;
141 } else {
142 curr.nextf.prevf = null;
143 curr.vert.conflicts.head = curr.nextf;
144 }
145 } else { // Node is not head
146 if (curr.nextf != null) {
147 curr.nextf.prevf = curr.prevf;
148 }
149 curr.prevf.nextf = curr.nextf;
150 }
151 curr = curr.nextv;
152 if (curr != null) {
153 curr.prevv = null;
154 }
155 } while (curr != null);
156 } else { // Remove all JFaces from vertex
157 var curr = this.head;
158 do {
159 if (curr.prevv == null) { // Node is head
160 if (curr.nextv == null) {
161 curr.face.conflicts.head = null;
162 } else {
163 curr.nextv.prevv = null;
164 curr.face.conflicts.head = curr.nextv;
165 }
166 } else { // Node is not head
167 if (curr.nextv != null) {
168 curr.nextv.prevv = curr.prevv;
169 }
170 curr.prevv.nextv = curr.nextv;
171 }
172 curr = curr.nextf;
173 if (curr != null)
174 curr.prevf = null;
175 } while (curr != null);
176 }
177 }
178
179 // IN: list of vertices
180 ConflictList.prototype.getVertices = function() {
181 var list = [],
182 curr = this.head;
183 while (curr !== null) {
184 list.push(curr.vert);
185 curr = curr.nextv;
186 }
187 return list;
188 }
189
190 // IN: coordinates x, y, z
191 function Vertex (x, y, z, weight, orig, isDummy) {
192 this.x = x;
193 this.y = y;
194 this.weight = epsilon;
195 this.index = 0;
196 this.conflicts = new ConflictList(false);
197 this.neighbours = null; // Potential trouble
198 this.nonClippedPolygon = null;
199 this.polygon = null;
200 this.originalObject = null;
201 this.isDummy = false;
202
203 if (orig !== undefined) {
204 this.originalObject = orig;
205 }
206 if (isDummy != undefined) {
207 this.isDummy = isDummy;
208 }
209 if (weight != null) {
210 this.weight = weight;
211 }
212 if (z != null) {
213 this.z = z;
214 } else {
215 this.z = this.projectZ(this.x, this.y, this.weight);
216 }
217 }
218
219 Vertex.prototype.projectZ = function(x, y, weight) {
220 return ((x*x) + (y*y) - weight);
221 }
222
223 Vertex.prototype.setWeight = function(weight) {
224 this.weight = weight;
225 this.z = this.projectZ(this.x, this.y, this.weight);
226 }
227
228 Vertex.prototype.subtract = function(v) {
229 return new Vertex(v.x - this.x, v.y - this.y, v.z - this.z);
230 }
231
232 Vertex.prototype.crossproduct = function(v) {
233 return new Vertex((this.y * v.z) - (this.z * v.y), (this.z * v.x) - (this.x * v.z), (this.x * v.y) - (this.y * v.x));
234 }
235
236 Vertex.prototype.equals = function(v) {
237 return (this.x === v.x && this.y === v.y && this.z === v.z);
238 }
239
240 // Plane3D and Point2D
241
242 // IN: Face face
243 function Plane3D (face) {
244 var p1 = face.verts[0];
245 var p2 = face.verts[1];
246 var p3 = face.verts[2];
247 this.a = p1.y * (p2.z-p3.z) + p2.y * (p3.z-p1.z) + p3.y * (p1.z-p2.z);
248 this.b = p1.z * (p2.x-p3.x) + p2.z * (p3.x-p1.x) + p3.z * (p1.x-p2.x);
249 this.c = p1.x * (p2.y-p3.y) + p2.x * (p3.y-p1.y) + p3.x * (p1.y-p2.y);
250 this.d = -1 * (p1.x * (p2.y*p3.z - p3.y*p2.z) + p2.x * (p3.y*p1.z - p1.y*p3.z) + p3.x * (p1.y*p2.z - p2.y*p1.z));
251 }
252
253 Plane3D.prototype.getNormZPlane = function() {
254 return [
255 -1 * (this.a / this.c),
256 -1 * (this.b / this.c),
257 -1 * (this.d / this.c)
258 ];
259 }
260
261 // OUT: point2D
262 Plane3D.prototype.getDualPointMappedToPlane = function() {
263 var nplane = this.getNormZPlane();
264 var dualPoint = new Point2D(nplane[0]/2, nplane[1]/2);
265 return dualPoint;
266 }
267
268 // IN: doubles x and y
269 function Point2D (x, y) {
270 this.x = x;
271 this.y = y;
272 }
273
274 // Vector
275
276 // IN: coordinates x, y, z
277 function Vector (x, y, z) {
278 this.x = x;
279 this.y = y;
280 this.z = z;
281 }
282
283 Vector.prototype.negate = function() {
284 this.x *= -1;
285 this.y *= -1;
286 this.z *= -1;
287 }
288
289 // Normalizes X Y and Z in-place
290 Vector.prototype.normalize = function() {
291 var lenght = Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
292 if (lenght > 0) {
293 this.x /= lenght;
294 this.y /= lenght;
295 this.z /= lenght;
296 }
297 }
298
299 // HEdge
300
301 // IN: vertex orig, vertex dest, Face face
302 function HEdge (orig, dest, face) {
303 this.next = null;
304 this.prev = null;
305 this.twin = null;
306 this.orig = orig;
307 this.dest = dest;
308 this.iFace = face;
309 }
310
311 HEdge.prototype.isHorizon = function() {
312 return this.twin !== null && !this.iFace.marked && this.twin.iFace.marked;
313 }
314
315 // IN: array horizon
316 HEdge.prototype.findHorizon = function(horizon) {
317 if (this.isHorizon()) {
318 if (horizon.length > 0 && this === horizon[0]) {
319 return;
320 } else {
321 horizon.push(this);
322 this.next.findHorizon(horizon);
323 }
324 } else {
325 if (this.twin !== null) {
326 this.twin.next.findHorizon(horizon);
327 }
328 }
329 }
330
331 // IN: vertices origin and dest
332 HEdge.prototype.isEqual = function(origin, dest) {
333 return ((this.orig.equals(origin) && this.dest.equals(dest)) || (this.orig.equals(dest) && this.dest.equals(origin)));
334 }
335
336 // from https://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript
337 // (above link provided by https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
338
339 function d3WeightedVoronoiError(message) {
340 this.message = message;
341 this.stack = new Error().stack;
342 }
343
344 d3WeightedVoronoiError.prototype.name = 'd3WeightedVoronoiError';
345 d3WeightedVoronoiError.prototype = new Error();
346
347 // IN: Vertices a, b, c
348 function Face(a, b, c, orient) {
349 this.conflicts = new ConflictList(true);
350 this.verts = [a, b, c];
351 this.marked = false;
352 var t = a.subtract(b).crossproduct(b.subtract(c));
353
354 this.normal = new Vector(-t.x, -t.y, -t.z);
355 this.normal.normalize();
356 this.createEdges();
357 this.dualPoint = null;
358
359 if (orient != undefined) {
360 this.orient(orient);
361 }
362 }
363
364 // OUT: Point2D
365 Face.prototype.getDualPoint = function () {
366 if (this.dualPoint == null) {
367 var plane3d = new Plane3D(this);
368 this.dualPoint = plane3d.getDualPointMappedToPlane();
369 }
370 return this.dualPoint;
371 };
372
373 Face.prototype.isVisibleFromBelow = function () {
374 return this.normal.z < -1.4259414393190911e-9;
375 };
376
377 Face.prototype.createEdges = function () {
378 this.edges = [];
379 this.edges[0] = new HEdge(this.verts[0], this.verts[1], this);
380 this.edges[1] = new HEdge(this.verts[1], this.verts[2], this);
381 this.edges[2] = new HEdge(this.verts[2], this.verts[0], this);
382 this.edges[0].next = this.edges[1];
383 this.edges[0].prev = this.edges[2];
384 this.edges[1].next = this.edges[2];
385 this.edges[1].prev = this.edges[0];
386 this.edges[2].next = this.edges[0];
387 this.edges[2].prev = this.edges[1];
388 };
389
390 // IN: vertex orient
391 Face.prototype.orient = function (orient) {
392 if (!(dot(this.normal, orient) < dot(this.normal, this.verts[0]))) {
393 var temp = this.verts[1];
394 this.verts[1] = this.verts[2];
395 this.verts[2] = temp;
396 this.normal.negate();
397 this.createEdges();
398 }
399 };
400
401 // IN: two vertices v0 and v1
402 Face.prototype.getEdge = function (v0, v1) {
403 for (var i = 0; i < 3; i++) {
404 if (this.edges[i].isEqual(v0, v1)) {
405 return this.edges[i];
406 }
407 }
408 return null;
409 };
410
411 // IN: Face face, vertices v0 and v1
412 Face.prototype.link = function (face, v0, v1) {
413 if (face instanceof Face) {
414 var twin = face.getEdge(v0, v1);
415 if (twin === null) {
416 throw new d3WeightedVoronoiError('when linking, twin is null');
417 }
418 var edge = this.getEdge(v0, v1);
419 if (edge === null) {
420 throw new d3WeightedVoronoiError('when linking, twin is null');
421 }
422 twin.twin = edge;
423 edge.twin = twin;
424 } else {
425 var twin = face; // face is a hEdge
426 var edge = this.getEdge(twin.orig, twin.dest);
427 twin.twin = edge;
428 edge.twin = twin;
429 }
430 };
431
432 // IN: vertex v
433 Face.prototype.conflict = function (v) {
434 return dot(this.normal, v) > dot(this.normal, this.verts[0]) + epsilon;
435 };
436
437 Face.prototype.getHorizon = function () {
438 for (var i = 0; i < 3; i++) {
439 if (this.edges[i].twin !== null && this.edges[i].twin.isHorizon()) {
440 return this.edges[i];
441 }
442 }
443 return null;
444 };
445
446 Face.prototype.removeConflict = function () {
447 this.conflicts.removeAll();
448 };
449
450 function ConvexHull() {
451 this.points = [];
452 this.facets = [];
453 this.created = [];
454 this.horizon = [];
455 this.visible = [];
456 this.current = 0;
457 }
458
459 // IN: sites (x,y,z)
460 ConvexHull.prototype.init = function (boundingSites, sites) {
461 this.points = [];
462 for (var i = 0; i < sites.length; i++) {
463 this.points[i] = new Vertex(sites[i].x, sites[i].y, sites[i].z, null, sites[i], false);
464 }
465 this.points = this.points.concat(boundingSites);
466 };
467
468 ConvexHull.prototype.permutate = function () {
469 var pointSize = this.points.length;
470 for (var i = pointSize - 1; i > 0; i--) {
471 var ra = Math.floor(Math.random() * i);
472 var temp = this.points[ra];
473 temp.index = i;
474 var currentItem = this.points[i];
475 currentItem.index = ra;
476 this.points.splice(ra, 1, currentItem);
477 this.points.splice(i, 1, temp);
478 }
479 };
480
481 (ConvexHull.prototype.prep = function () {
482 if (this.points.length <= 3) {
483 throw new d3WeightedVoronoiError('Less than 4 points');
484 }
485 for (var i = 0; i < this.points.length; i++) {
486 this.points[i].index = i;
487 }
488
489 var v0, v1, v2, v3;
490 var f1, f2, f3, f0;
491 v0 = this.points[0];
492 v1 = this.points[1];
493 v2 = v3 = null;
494
495 // Searching for a third vertex, not aligned with the 2 firsts
496 for (var i = 2; i < this.points.length; i++) {
497 if (!(linearDependent(v0, this.points[i]) && linearDependent(v1, this.points[i]))) {
498 v2 = this.points[i];
499 v2.index = 2;
500 this.points[2].index = i;
501 this.points.splice(i, 1, this.points[2]);
502 this.points.splice(2, 1, v2);
503 break;
504 }
505 }
506 if (v2 === null) {
507 throw new d3WeightedVoronoiError('Not enough non-planar Points (v2 is null)');
508 }
509
510 // Create first JFace
511 f0 = new Face(v0, v1, v2);
512 // Searching for a fourth vertex, not coplanar to the 3 firsts
513 for (var i = 3; i < this.points.length; i++) {
514 if (!epsilonesque(dot(f0.normal, f0.verts[0]) - dot(f0.normal, this.points[i]))) {
515 v3 = this.points[i];
516 v3.index = 3;
517 this.points[3].index = i;
518 this.points.splice(i, 1, this.points[3]);
519 this.points.splice(3, 1, v3);
520 break;
521 }
522 }
523 if (v3 === null) {
524 throw new d3WeightedVoronoiError('Not enough non-planar Points (v3 is null)');
525 }
526
527 f0.orient(v3);
528 f1 = new Face(v0, v2, v3, v1);
529 f2 = new Face(v0, v1, v3, v2);
530 f3 = new Face(v1, v2, v3, v0);
531 this.addFacet(f0);
532 this.addFacet(f1);
533 this.addFacet(f2);
534 this.addFacet(f3);
535 // Connect facets
536 f0.link(f1, v0, v2);
537 f0.link(f2, v0, v1);
538 f0.link(f3, v1, v2);
539 f1.link(f2, v0, v3);
540 f1.link(f3, v2, v3);
541 f2.link(f3, v3, v1);
542 this.current = 4;
543
544 var v;
545 for (var i = this.current; i < this.points.length; i++) {
546 v = this.points[i];
547 if (f0.conflict(v)) {
548 this.addConflict(f0, v);
549 }
550 if (f1.conflict(v)) {
551 this.addConflict(f1, v);
552 }
553 if (f2.conflict(v)) {
554 this.addConflict(f2, v);
555 }
556 if (f3.conflict(v)) {
557 this.addConflict(f3, v);
558 }
559 }
560 }),
561 // IN: Faces old1 old2 and fn
562 (ConvexHull.prototype.addConflicts = function (old1, old2, fn) {
563 var l1 = old1.conflicts.getVertices();
564 var l2 = old2.conflicts.getVertices();
565 var nCL = [];
566 var v1, v2;
567 var i, l;
568 i = l = 0;
569 // Fill the possible new Conflict List
570 while (i < l1.length || l < l2.length) {
571 if (i < l1.length && l < l2.length) {
572 v1 = l1[i];
573 v2 = l2[l];
574 // If the index is the same, it's the same vertex and only 1 has to be added
575 if (v1.index === v2.index) {
576 nCL.push(v1);
577 i++;
578 l++;
579 } else if (v1.index > v2.index) {
580 nCL.push(v1);
581 i++;
582 } else {
583 nCL.push(v2);
584 l++;
585 }
586 } else if (i < l1.length) {
587 nCL.push(l1[i++]);
588 } else {
589 nCL.push(l2[l++]);
590 }
591 }
592 // Check if the possible conflicts are real conflicts
593 for (var i = nCL.length - 1; i >= 0; i--) {
594 v1 = nCL[i];
595 if (fn.conflict(v1)) this.addConflict(fn, v1);
596 }
597 });
598
599 // IN: Face face, Vertex v
600 ConvexHull.prototype.addConflict = function (face, vert) {
601 var e = new ConflictListNode(face, vert);
602 face.conflicts.add(e);
603 vert.conflicts.add(e);
604 };
605
606 // IN: Face f
607 ConvexHull.prototype.removeConflict = function (f) {
608 f.removeConflict();
609 var index = f.index;
610 f.index = -1;
611 if (index === this.facets.length - 1) {
612 this.facets.splice(this.facets.length - 1, 1);
613 return;
614 }
615 if (index >= this.facets.length || index < 0) return;
616 var last = this.facets.splice(this.facets.length - 1, 1);
617 last[0].index = index;
618 this.facets.splice(index, 1, last[0]);
619 };
620
621 // IN: Face face
622 ConvexHull.prototype.addFacet = function (face) {
623 face.index = this.facets.length;
624 this.facets.push(face);
625 };
626
627 ConvexHull.prototype.compute = function () {
628 this.prep();
629 while (this.current < this.points.length) {
630 var next = this.points[this.current];
631 if (next.conflicts.isEmpty()) {
632 // No conflict, point in hull
633 this.current++;
634 continue;
635 }
636 this.created = []; // TODO: make sure this is okay and doesn't dangle references
637 this.horizon = [];
638 this.visible = [];
639 // The visible faces are also marked
640 next.conflicts.fill(this.visible);
641 // Horizon edges are orderly added to the horizon list
642 var e;
643 for (var jF = 0; jF < this.visible.length; jF++) {
644 e = this.visible[jF].getHorizon();
645 if (e !== null) {
646 e.findHorizon(this.horizon);
647 break;
648 }
649 }
650 var last = null,
651 first = null;
652 // Iterate over horizon edges and create new faces oriented with the marked face 3rd unused point
653 for (var hEi = 0; hEi < this.horizon.length; hEi++) {
654 var hE = this.horizon[hEi];
655 var fn = new Face(next, hE.orig, hE.dest, hE.twin.next.dest);
656 fn.conflicts = new ConflictList(true);
657 // Add to facet list
658 this.addFacet(fn);
659 this.created.push(fn);
660 // Add new conflicts
661 this.addConflicts(hE.iFace, hE.twin.iFace, fn);
662 // Link the new face with the horizon edge
663 fn.link(hE);
664 if (last !== null) fn.link(last, next, hE.orig);
665 last = fn;
666 if (first === null) first = fn;
667 }
668 // Links the first and the last created JFace
669 if (first !== null && last !== null) {
670 last.link(first, next, this.horizon[0].orig);
671 }
672 if (this.created.length != 0) {
673 // update conflict graph
674 for (var f = 0; f < this.visible.length; f++) {
675 this.removeConflict(this.visible[f]);
676 }
677 this.current++;
678 this.created = [];
679 }
680 }
681 return this.facets;
682 };
683
684 ConvexHull.prototype.clear = function () {
685 this.points = [];
686 this.facets = [];
687 this.created = [];
688 this.horizon = [];
689 this.visible = [];
690 this.current = 0;
691 };
692
693 function polygonClip(clip, subject) {
694 // Version 0.0.0. Copyright 2017 Mike Bostock.
695
696 // Clips the specified subject polygon to the specified clip polygon;
697 // requires the clip polygon to be counterclockwise and convex.
698 // https://en.wikipedia.org/wiki/Sutherland–Hodgman_algorithm
699 // https://observablehq.com/@d3/polygonclip
700
701 var input,
702 closed = polygonClosed(subject),
703 i = -1,
704 n = clip.length - polygonClosed(clip),
705 j,
706 m,
707 a = clip[n - 1],
708 b,
709 c,
710 d,
711 intersection;
712
713 while (++i < n) {
714 input = subject.slice();
715 subject.length = 0;
716 b = clip[i];
717 c = input[(m = input.length - closed) - 1];
718 j = -1;
719 while (++j < m) {
720 d = input[j];
721 if (polygonInside(d, a, b)) {
722 if (!polygonInside(c, a, b)) {
723 intersection = polygonIntersect(c, d, a, b);
724 if (isFinite(intersection[0])) {
725 subject.push(intersection);
726 }
727 }
728 subject.push(d);
729 } else if (polygonInside(c, a, b)) {
730 intersection = polygonIntersect(c, d, a, b);
731 if (isFinite(intersection[0])) {
732 subject.push(intersection);
733 }
734 }
735 c = d;
736 }
737 if (closed) subject.push(subject[0]);
738 a = b;
739 }
740
741 return subject;
742 }
743
744 function polygonInside(p, a, b) {
745 return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
746 }
747
748 // Intersect two infinite lines cd and ab.
749 // Return Infinity if cd and ab colinear
750 function polygonIntersect(c, d, a, b) {
751 var x1 = c[0],
752 x3 = a[0],
753 x21 = d[0] - x1,
754 x43 = b[0] - x3,
755 y1 = c[1],
756 y3 = a[1],
757 y21 = d[1] - y1,
758 y43 = b[1] - y3,
759 ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
760 return [x1 + ua * x21, y1 + ua * y21];
761 }
762
763 // Returns true if the polygon is closed.
764 function polygonClosed(coordinates) {
765 var a = coordinates[0],
766 b = coordinates[coordinates.length - 1];
767 return !(a[0] - b[0] || a[1] - b[1]);
768 }
769
770 // IN: HEdge edge
771 function getFacesOfDestVertex(edge) {
772 var faces = [];
773 var previous = edge;
774 var first = edge.dest;
775 var site = first.originalObject;
776 var neighbours = [];
777 do {
778 previous = previous.twin.prev;
779 var siteOrigin = previous.orig.originalObject;
780 if (!siteOrigin.isDummy) {
781 neighbours.push(siteOrigin);
782 }
783 var iFace = previous.iFace;
784 if (iFace.isVisibleFromBelow()) {
785 faces.push(iFace);
786 }
787 } while (previous !== edge);
788 site.neighbours = neighbours;
789 return faces;
790 }
791
792 // IN: Omega = convex bounding polygon
793 // IN: S = unique set of sites with weights
794 // OUT: Set of lines making up the voronoi power diagram
795 function computePowerDiagramIntegrated (sites, boundingSites, clippingPolygon) {
796 var convexHull = new ConvexHull();
797 convexHull.clear();
798 convexHull.init(boundingSites, sites);
799
800 var facets = convexHull.compute(sites);
801 var polygons = [];
802 var verticesVisited = [];
803 var facetCount = facets.length;
804
805 for (var i = 0; i < facetCount; i++) {
806 var facet = facets[i];
807 if (facet.isVisibleFromBelow()) {
808 for (var e = 0; e < 3; e++) {
809 // go through the edges and start to build the polygon by going through the double connected edge list
810 var edge = facet.edges[e];
811 var destVertex = edge.dest;
812 var site = destVertex.originalObject;
813
814 if (!verticesVisited[destVertex.index]) {
815 verticesVisited[destVertex.index] = true;
816 if (site.isDummy) {
817 // Check if this is one of the sites making the bounding polygon
818 continue;
819 }
820 // faces around the vertices which correspond to the polygon corner points
821 var faces = getFacesOfDestVertex(edge);
822 var protopoly = [];
823 var lastX = null;
824 var lastY = null;
825 var dx = 1;
826 var dy = 1;
827 for (var j = 0; j < faces.length; j++) {
828 var point = faces[j].getDualPoint();
829 var x1 = point.x;
830 var y1 = point.y;
831 if (lastX !== null) {
832 dx = lastX - x1;
833 dy = lastY - y1;
834 if (dx < 0) {
835 dx = -dx;
836 }
837 if (dy < 0) {
838 dy = -dy;
839 }
840 }
841 if (dx > epsilon || dy > epsilon) {
842 protopoly.push([x1, y1]);
843 lastX = x1;
844 lastY = y1;
845 }
846 }
847
848 site.nonClippedPolygon = protopoly.reverse();
849 if (!site.isDummy && d3Polygon.polygonLength(site.nonClippedPolygon) > 0) {
850 var clippedPoly = polygonClip(clippingPolygon, site.nonClippedPolygon);
851 site.polygon = clippedPoly;
852 clippedPoly.site = site;
853 if (clippedPoly.length > 0) {
854 polygons.push(clippedPoly);
855 }
856 }
857 }
858 }
859 }
860 }
861 return polygons;
862 }
863
864 function weightedVoronoi() {
865 /////// Inputs ///////
866 var x = function (d) {
867 return d.x;
868 }; // accessor to the x value
869 var y = function (d) {
870 return d.y;
871 }; // accessor to the y value
872 var weight = function (d) {
873 return d.weight;
874 }; // accessor to the weight
875 var clip = [
876 [0, 0],
877 [0, 1],
878 [1, 1],
879 [1, 0],
880 ]; // clipping polygon
881 var extent = [
882 [0, 0],
883 [1, 1],
884 ]; // extent of the clipping polygon
885 var size = [1, 1]; // [width, height] of the clipping polygon
886
887 ///////////////////////
888 ///////// API /////////
889 ///////////////////////
890
891 function _weightedVoronoi(data) {
892 var formatedSites;
893
894 //begin: map sites to the expected format of PowerDiagram
895 formatedSites = data.map(function (d) {
896 return new Vertex(x(d), y(d), null, weight(d), d, false);
897 });
898 //end: map sites to the expected format of PowerDiagram
899
900 return computePowerDiagramIntegrated(formatedSites, boundingSites(), clip);
901 }
902
903 _weightedVoronoi.x = function (_) {
904 if (!arguments.length) {
905 return x;
906 }
907
908 x = _;
909 return _weightedVoronoi;
910 };
911
912 _weightedVoronoi.y = function (_) {
913 if (!arguments.length) {
914 return y;
915 }
916
917 y = _;
918 return _weightedVoronoi;
919 };
920
921 _weightedVoronoi.weight = function (_) {
922 if (!arguments.length) {
923 return weight;
924 }
925
926 weight = _;
927 return _weightedVoronoi;
928 };
929
930 _weightedVoronoi.clip = function (_) {
931 var direction, xExtent, yExtent;
932
933 if (!arguments.length) {
934 return clip;
935 }
936
937 xExtent = d3Array.extent(
938 _.map(function (c) {
939 return c[0];
940 })
941 );
942 yExtent = d3Array.extent(
943 _.map(function (c) {
944 return c[1];
945 })
946 );
947 direction = polygonDirection(_);
948 if (direction === undefined) {
949 clip = d3Polygon.polygonHull(_); // ensure clip to be a convex, hole-free, counterclockwise polygon
950 } else if (direction === 1) {
951 clip = _.reverse(); // already convex, order array in the same direction as d3-polygon.polygonHull(...)
952 } else {
953 clip = _;
954 }
955 extent = [
956 [xExtent[0], yExtent[0]],
957 [xExtent[1], yExtent[1]],
958 ];
959 size = [xExtent[1] - xExtent[0], yExtent[1] - yExtent[0]];
960 return _weightedVoronoi;
961 };
962
963 _weightedVoronoi.extent = function (_) {
964 if (!arguments.length) {
965 return extent;
966 }
967
968 clip = [_[0], [_[0][0], _[1][1]], _[1], [_[1][0], _[0][1]]];
969 extent = _;
970 size = [_[1][0] - _[0][0], _[1][1] - _[0][1]];
971 return _weightedVoronoi;
972 };
973
974 _weightedVoronoi.size = function (_) {
975 if (!arguments.length) {
976 return size;
977 }
978
979 clip = [
980 [0, 0],
981 [0, _[1]],
982 [_[0], _[1]],
983 [_[0], 0],
984 ];
985 extent = [[0, 0], _];
986 size = _;
987 return _weightedVoronoi;
988 };
989
990 ///////////////////////
991 /////// Private ///////
992 ///////////////////////
993
994 function boundingSites() {
995 var minX,
996 maxX,
997 minY,
998 maxY,
999 width,
1000 height,
1001 x0,
1002 x1,
1003 y0,
1004 y1,
1005 boundingData = [],
1006 boundingSites = [];
1007
1008 minX = extent[0][0];
1009 maxX = extent[1][0];
1010 minY = extent[0][1];
1011 maxY = extent[1][1];
1012 width = maxX - minX;
1013 height = maxY - minY;
1014 x0 = minX - width;
1015 x1 = maxX + width;
1016 y0 = minY - height;
1017 y1 = maxY + height;
1018
1019 // MUST be counterclockwise
1020 // if not, may produce 'TypeError: Cannot set property 'twin' of null' during computation
1021 // don't know how to test as it is not exposed
1022 boundingData[0] = [x0, y0];
1023 boundingData[1] = [x0, y1];
1024 boundingData[2] = [x1, y1];
1025 boundingData[3] = [x1, y0];
1026
1027 for (var i = 0; i < 4; i++) {
1028 boundingSites.push(
1029 new Vertex(
1030 boundingData[i][0],
1031 boundingData[i][1],
1032 null,
1033 epsilon,
1034 new Vertex(boundingData[i][0], boundingData[i][1], null, epsilon, null, true),
1035 true
1036 )
1037 );
1038 }
1039
1040 return boundingSites;
1041 }
1042
1043 return _weightedVoronoi;
1044 }
1045
1046 exports.weightedVoronoi = weightedVoronoi;
1047 exports.d3WeightedVoronoiError = d3WeightedVoronoiError;
1048
1049 Object.defineProperty(exports, '__esModule', { value: true });
1050
1051}));
\No newline at end of file