toolkit/javascript/d3/src/geom/voronoi.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 // Adapted from Nicolas Garcia Belmonte's JIT implementation:
       
     2 // http://blog.thejit.org/2010/02/12/voronoi-tessellation/
       
     3 // http://blog.thejit.org/assets/voronoijs/voronoi.js
       
     4 // See lib/jit/LICENSE for details.
       
     5 
       
     6 // Notes:
       
     7 //
       
     8 // This implementation does not clip the returned polygons, so if you want to
       
     9 // clip them to a particular shape you will need to do that either in SVG or by
       
    10 // post-processing with d3.geom.polygon's clip method.
       
    11 //
       
    12 // If any vertices are coincident or have NaN positions, the behavior of this
       
    13 // method is undefined. Most likely invalid polygons will be returned. You
       
    14 // should filter invalid points, and consolidate coincident points, before
       
    15 // computing the tessellation.
       
    16 
       
    17 /**
       
    18  * @param vertices [[x1, y1], [x2, y2], …]
       
    19  * @returns polygons [[[x1, y1], [x2, y2], …], …]
       
    20  */
       
    21 d3.geom.voronoi = function(vertices) {
       
    22   var polygons = vertices.map(function() { return []; });
       
    23 
       
    24   d3_voronoi_tessellate(vertices, function(e) {
       
    25     var s1,
       
    26         s2,
       
    27         x1,
       
    28         x2,
       
    29         y1,
       
    30         y2;
       
    31     if (e.a === 1 && e.b >= 0) {
       
    32       s1 = e.ep.r;
       
    33       s2 = e.ep.l;
       
    34     } else {
       
    35       s1 = e.ep.l;
       
    36       s2 = e.ep.r;
       
    37     }
       
    38     if (e.a === 1) {
       
    39       y1 = s1 ? s1.y : -1e6;
       
    40       x1 = e.c - e.b * y1;
       
    41       y2 = s2 ? s2.y : 1e6;
       
    42       x2 = e.c - e.b * y2;
       
    43     } else {
       
    44       x1 = s1 ? s1.x : -1e6;
       
    45       y1 = e.c - e.a * x1;
       
    46       x2 = s2 ? s2.x : 1e6;
       
    47       y2 = e.c - e.a * x2;
       
    48     }
       
    49     var v1 = [x1, y1],
       
    50         v2 = [x2, y2];
       
    51     polygons[e.region.l.index].push(v1, v2);
       
    52     polygons[e.region.r.index].push(v1, v2);
       
    53   });
       
    54 
       
    55   // Reconnect the polygon segments into counterclockwise loops.
       
    56   return polygons.map(function(polygon, i) {
       
    57     var cx = vertices[i][0],
       
    58         cy = vertices[i][1];
       
    59     polygon.forEach(function(v) {
       
    60       v.angle = Math.atan2(v[0] - cx, v[1] - cy);
       
    61     });
       
    62     return polygon.sort(function(a, b) {
       
    63       return a.angle - b.angle;
       
    64     }).filter(function(d, i) {
       
    65       return !i || (d.angle - polygon[i - 1].angle > 1e-10);
       
    66     });
       
    67   });
       
    68 };
       
    69 
       
    70 var d3_voronoi_opposite = {"l": "r", "r": "l"};
       
    71 
       
    72 function d3_voronoi_tessellate(vertices, callback) {
       
    73 
       
    74   var Sites = {
       
    75     list: vertices
       
    76       .map(function(v, i) {
       
    77         return {
       
    78           index: i,
       
    79           x: v[0],
       
    80           y: v[1]
       
    81         };
       
    82       })
       
    83       .sort(function(a, b) {
       
    84         return a.y < b.y ? -1
       
    85           : a.y > b.y ? 1
       
    86           : a.x < b.x ? -1
       
    87           : a.x > b.x ? 1
       
    88           : 0;
       
    89       }),
       
    90     bottomSite: null
       
    91   };
       
    92 
       
    93   var EdgeList = {
       
    94     list: [],
       
    95     leftEnd: null,
       
    96     rightEnd: null,
       
    97 
       
    98     init: function() {
       
    99       EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
       
   100       EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
       
   101       EdgeList.leftEnd.r = EdgeList.rightEnd;
       
   102       EdgeList.rightEnd.l = EdgeList.leftEnd;
       
   103       EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
       
   104     },
       
   105 
       
   106     createHalfEdge: function(edge, side) {
       
   107       return {
       
   108         edge: edge,
       
   109         side: side,
       
   110         vertex: null,
       
   111         "l": null,
       
   112         "r": null
       
   113       };
       
   114     },
       
   115 
       
   116     insert: function(lb, he) {
       
   117       he.l = lb;
       
   118       he.r = lb.r;
       
   119       lb.r.l = he;
       
   120       lb.r = he;
       
   121     },
       
   122 
       
   123     leftBound: function(p) {
       
   124       var he = EdgeList.leftEnd;
       
   125       do {
       
   126         he = he.r;
       
   127       } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
       
   128       he = he.l;
       
   129       return he;
       
   130     },
       
   131 
       
   132     del: function(he) {
       
   133       he.l.r = he.r;
       
   134       he.r.l = he.l;
       
   135       he.edge = null;
       
   136     },
       
   137 
       
   138     right: function(he) {
       
   139       return he.r;
       
   140     },
       
   141 
       
   142     left: function(he) {
       
   143       return he.l;
       
   144     },
       
   145 
       
   146     leftRegion: function(he) {
       
   147       return he.edge == null
       
   148           ? Sites.bottomSite
       
   149           : he.edge.region[he.side];
       
   150     },
       
   151 
       
   152     rightRegion: function(he) {
       
   153       return he.edge == null
       
   154           ? Sites.bottomSite
       
   155           : he.edge.region[d3_voronoi_opposite[he.side]];
       
   156     }
       
   157   };
       
   158 
       
   159   var Geom = {
       
   160 
       
   161     bisect: function(s1, s2) {
       
   162       var newEdge = {
       
   163         region: {"l": s1, "r": s2},
       
   164         ep: {"l": null, "r": null}
       
   165       };
       
   166 
       
   167       var dx = s2.x - s1.x,
       
   168           dy = s2.y - s1.y,
       
   169           adx = dx > 0 ? dx : -dx,
       
   170           ady = dy > 0 ? dy : -dy;
       
   171 
       
   172       newEdge.c = s1.x * dx + s1.y * dy
       
   173           + (dx * dx + dy * dy) * .5;
       
   174 
       
   175       if (adx > ady) {
       
   176         newEdge.a = 1;
       
   177         newEdge.b = dy / dx;
       
   178         newEdge.c /= dx;
       
   179       } else {
       
   180         newEdge.b = 1;
       
   181         newEdge.a = dx / dy;
       
   182         newEdge.c /= dy;
       
   183       }
       
   184 
       
   185       return newEdge;
       
   186     },
       
   187 
       
   188     intersect: function(el1, el2) {
       
   189       var e1 = el1.edge,
       
   190           e2 = el2.edge;
       
   191       if (!e1 || !e2 || (e1.region.r == e2.region.r)) {
       
   192         return null;
       
   193       }
       
   194       var d = (e1.a * e2.b) - (e1.b * e2.a);
       
   195       if (Math.abs(d) < 1e-10) {
       
   196         return null;
       
   197       }
       
   198       var xint = (e1.c * e2.b - e2.c * e1.b) / d,
       
   199           yint = (e2.c * e1.a - e1.c * e2.a) / d,
       
   200           e1r = e1.region.r,
       
   201           e2r = e2.region.r,
       
   202           el,
       
   203           e;
       
   204       if ((e1r.y < e2r.y) ||
       
   205          (e1r.y == e2r.y && e1r.x < e2r.x)) {
       
   206         el = el1;
       
   207         e = e1;
       
   208       } else {
       
   209         el = el2;
       
   210         e = e2;
       
   211       }
       
   212       var rightOfSite = (xint >= e.region.r.x);
       
   213       if ((rightOfSite && (el.side === "l")) ||
       
   214         (!rightOfSite && (el.side === "r"))) {
       
   215         return null;
       
   216       }
       
   217       return {
       
   218         x: xint,
       
   219         y: yint
       
   220       };
       
   221     },
       
   222 
       
   223     rightOf: function(he, p) {
       
   224       var e = he.edge,
       
   225           topsite = e.region.r,
       
   226           rightOfSite = (p.x > topsite.x);
       
   227 
       
   228       if (rightOfSite && (he.side === "l")) {
       
   229         return 1;
       
   230       }
       
   231       if (!rightOfSite && (he.side === "r")) {
       
   232         return 0;
       
   233       }
       
   234       if (e.a === 1) {
       
   235         var dyp = p.y - topsite.y,
       
   236             dxp = p.x - topsite.x,
       
   237             fast = 0,
       
   238             above = 0;
       
   239 
       
   240         if ((!rightOfSite && (e.b < 0)) ||
       
   241           (rightOfSite && (e.b >= 0))) {
       
   242           above = fast = (dyp >= e.b * dxp);
       
   243         } else {
       
   244           above = ((p.x + p.y * e.b) > e.c);
       
   245           if (e.b < 0) {
       
   246             above = !above;
       
   247           }
       
   248           if (!above) {
       
   249             fast = 1;
       
   250           }
       
   251         }
       
   252         if (!fast) {
       
   253           var dxs = topsite.x - e.region.l.x;
       
   254           above = (e.b * (dxp * dxp - dyp * dyp)) <
       
   255             (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b));
       
   256 
       
   257           if (e.b < 0) {
       
   258             above = !above;
       
   259           }
       
   260         }
       
   261       } else /* e.b == 1 */ {
       
   262         var yl = e.c - e.a * p.x,
       
   263             t1 = p.y - yl,
       
   264             t2 = p.x - topsite.x,
       
   265             t3 = yl - topsite.y;
       
   266 
       
   267         above = (t1 * t1) > (t2 * t2 + t3 * t3);
       
   268       }
       
   269       return he.side === "l" ? above : !above;
       
   270     },
       
   271 
       
   272     endPoint: function(edge, side, site) {
       
   273       edge.ep[side] = site;
       
   274       if (!edge.ep[d3_voronoi_opposite[side]]) return;
       
   275       callback(edge);
       
   276     },
       
   277 
       
   278     distance: function(s, t) {
       
   279       var dx = s.x - t.x,
       
   280           dy = s.y - t.y;
       
   281       return Math.sqrt(dx * dx + dy * dy);
       
   282     }
       
   283   };
       
   284 
       
   285   var EventQueue = {
       
   286     list: [],
       
   287 
       
   288     insert: function(he, site, offset) {
       
   289       he.vertex = site;
       
   290       he.ystar = site.y + offset;
       
   291       for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) {
       
   292         var next = list[i];
       
   293         if (he.ystar > next.ystar ||
       
   294           (he.ystar == next.ystar &&
       
   295           site.x > next.vertex.x)) {
       
   296           continue;
       
   297         } else {
       
   298           break;
       
   299         }
       
   300       }
       
   301       list.splice(i, 0, he);
       
   302     },
       
   303 
       
   304     del: function(he) {
       
   305       for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {}
       
   306       ls.splice(i, 1);
       
   307     },
       
   308 
       
   309     empty: function() { return EventQueue.list.length === 0; },
       
   310 
       
   311     nextEvent: function(he) {
       
   312       for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) {
       
   313         if (ls[i] == he) return ls[i+1];
       
   314       }
       
   315       return null;
       
   316     },
       
   317 
       
   318     min: function() {
       
   319       var elem = EventQueue.list[0];
       
   320       return {
       
   321         x: elem.vertex.x,
       
   322         y: elem.ystar
       
   323       };
       
   324     },
       
   325 
       
   326     extractMin: function() {
       
   327       return EventQueue.list.shift();
       
   328     }
       
   329   };
       
   330 
       
   331   EdgeList.init();
       
   332   Sites.bottomSite = Sites.list.shift();
       
   333 
       
   334   var newSite = Sites.list.shift(), newIntStar;
       
   335   var lbnd, rbnd, llbnd, rrbnd, bisector;
       
   336   var bot, top, temp, p, v;
       
   337   var e, pm;
       
   338 
       
   339   while (true) {
       
   340     if (!EventQueue.empty()) {
       
   341       newIntStar = EventQueue.min();
       
   342     }
       
   343     if (newSite && (EventQueue.empty()
       
   344       || newSite.y < newIntStar.y
       
   345       || (newSite.y == newIntStar.y
       
   346       && newSite.x < newIntStar.x))) { //new site is smallest
       
   347       lbnd = EdgeList.leftBound(newSite);
       
   348       rbnd = EdgeList.right(lbnd);
       
   349       bot = EdgeList.rightRegion(lbnd);
       
   350       e = Geom.bisect(bot, newSite);
       
   351       bisector = EdgeList.createHalfEdge(e, "l");
       
   352       EdgeList.insert(lbnd, bisector);
       
   353       p = Geom.intersect(lbnd, bisector);
       
   354       if (p) {
       
   355         EventQueue.del(lbnd);
       
   356         EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
       
   357       }
       
   358       lbnd = bisector;
       
   359       bisector = EdgeList.createHalfEdge(e, "r");
       
   360       EdgeList.insert(lbnd, bisector);
       
   361       p = Geom.intersect(bisector, rbnd);
       
   362       if (p) {
       
   363         EventQueue.insert(bisector, p, Geom.distance(p, newSite));
       
   364       }
       
   365       newSite = Sites.list.shift();
       
   366     } else if (!EventQueue.empty()) { //intersection is smallest
       
   367       lbnd = EventQueue.extractMin();
       
   368       llbnd = EdgeList.left(lbnd);
       
   369       rbnd = EdgeList.right(lbnd);
       
   370       rrbnd = EdgeList.right(rbnd);
       
   371       bot = EdgeList.leftRegion(lbnd);
       
   372       top = EdgeList.rightRegion(rbnd);
       
   373       v = lbnd.vertex;
       
   374       Geom.endPoint(lbnd.edge, lbnd.side, v);
       
   375       Geom.endPoint(rbnd.edge, rbnd.side, v);
       
   376       EdgeList.del(lbnd);
       
   377       EventQueue.del(rbnd);
       
   378       EdgeList.del(rbnd);
       
   379       pm = "l";
       
   380       if (bot.y > top.y) {
       
   381         temp = bot;
       
   382         bot = top;
       
   383         top = temp;
       
   384         pm = "r";
       
   385       }
       
   386       e = Geom.bisect(bot, top);
       
   387       bisector = EdgeList.createHalfEdge(e, pm);
       
   388       EdgeList.insert(llbnd, bisector);
       
   389       Geom.endPoint(e, d3_voronoi_opposite[pm], v);
       
   390       p = Geom.intersect(llbnd, bisector);
       
   391       if (p) {
       
   392         EventQueue.del(llbnd);
       
   393         EventQueue.insert(llbnd, p, Geom.distance(p, bot));
       
   394       }
       
   395       p = Geom.intersect(bisector, rrbnd);
       
   396       if (p) {
       
   397         EventQueue.insert(bisector, p, Geom.distance(p, bot));
       
   398       }
       
   399     } else {
       
   400       break;
       
   401     }
       
   402   }//end while
       
   403 
       
   404   for (lbnd = EdgeList.right(EdgeList.leftEnd);
       
   405       lbnd != EdgeList.rightEnd;
       
   406       lbnd = EdgeList.right(lbnd)) {
       
   407     callback(lbnd.edge);
       
   408   }
       
   409 }