source: Dev/branches/jos-branch/d3/src/geom/voronoi.js @ 217

Last change on this file since 217 was 76, checked in by fpvanagthoven, 14 years ago

d3

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