|
1 |
| -import {create, group, path, select, Delaunay} from "d3"; |
| 1 | +import {bisector, create, extent, group, path, select, Delaunay} from "d3"; |
2 | 2 | import {Curve} from "../curve.js";
|
3 | 3 | import {constant, maybeTuple, maybeZ} from "../options.js";
|
4 | 4 | import {Mark} from "../plot.js";
|
@@ -266,3 +266,120 @@ export function voronoi(data, options) {
|
266 | 266 | export function voronoiMesh(data, options) {
|
267 | 267 | return delaunayMark(VoronoiMesh, data, options);
|
268 | 268 | }
|
| 269 | + |
| 270 | +class GabrielMesh extends AbstractDelaunayMark { |
| 271 | + constructor(data, options) { |
| 272 | + super(data, options, voronoiMeshDefaults); |
| 273 | + this.fill = "none"; |
| 274 | + } |
| 275 | + _accept(delaunay) { |
| 276 | + const {points, triangles} = delaunay; |
| 277 | + return (i) => { |
| 278 | + const a = triangles[i]; |
| 279 | + const b = triangles[i % 3 === 2 ? i - 2 : i + 1]; |
| 280 | + return [a, b].includes(delaunay.find((points[2 * a] + points[2 * b]) / 2, (points[2 * a + 1] + points[2 * b + 1]) / 2, a)); |
| 281 | + }; |
| 282 | + } |
| 283 | + _render(delaunay) { |
| 284 | + const p = new path(); |
| 285 | + const {points, halfedges, triangles} = delaunay; |
| 286 | + const accept = this._accept(delaunay); |
| 287 | + for (let i = 0, n = triangles.length; i < n; ++i) { |
| 288 | + const j = halfedges[i]; |
| 289 | + if (i < j) continue; |
| 290 | + if (accept(i)) { |
| 291 | + const a = triangles[i]; |
| 292 | + const b = triangles[i % 3 === 2 ? i - 2 : i + 1]; |
| 293 | + p.moveTo(points[2 * a], points[2 * a + 1]); |
| 294 | + p.lineTo(points[2 * b], points[2 * b + 1]); |
| 295 | + } |
| 296 | + } |
| 297 | + return "" + p; |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +class UrquhartMesh extends GabrielMesh { |
| 302 | + constructor(data, options) { |
| 303 | + super(data, options, voronoiMeshDefaults); |
| 304 | + this.fill = "none"; |
| 305 | + } |
| 306 | + _accept(delaunay, score = euclidean2) { |
| 307 | + const {halfedges, points, triangles} = delaunay; |
| 308 | + const n = triangles.length; |
| 309 | + const removed = new Uint8Array(n); |
| 310 | + for (let e = 0; e < n; e += 3) { |
| 311 | + const p0 = triangles[e], p1 = triangles[e + 1], p2 = triangles[e + 2]; |
| 312 | + const p01 = score(points, p0, p1), p12 = score(points, p1, p2), p20 = score(points, p2, p0); |
| 313 | + removed[p20 > p01 && p20 > p12 ? Math.max(e + 2, halfedges[e + 2]) |
| 314 | + : p12 > p01 && p12 > p20 ? Math.max(e + 1, halfedges[e + 1]) |
| 315 | + : Math.max(e, halfedges[e])] = 1; |
| 316 | + } |
| 317 | + return (i) => !removed[i]; |
| 318 | + } |
| 319 | +} |
| 320 | + |
| 321 | +function euclidean2(points, i, j) { |
| 322 | + return (points[i * 2] - points[j * 2]) ** 2 + (points[i * 2 + 1] - points[j * 2 + 1]) ** 2; |
| 323 | +} |
| 324 | + |
| 325 | +class MSTMesh extends GabrielMesh { |
| 326 | + constructor(data, options) { |
| 327 | + super(data, options, voronoiMeshDefaults); |
| 328 | + this.fill = "none"; |
| 329 | + } |
| 330 | + _accept(delaunay, score = euclidean2) { |
| 331 | + const {points, triangles} = delaunay; |
| 332 | + const set = new Uint8Array(points.length / 2); |
| 333 | + const tree = new Set(); |
| 334 | + const heap = []; |
| 335 | + |
| 336 | + const bisect = bisector(([v]) => -v).left; |
| 337 | + function heap_insert(x, v) { |
| 338 | + heap.splice(bisect(heap, -v), 0, [v, x]); |
| 339 | + } |
| 340 | + function heap_pop() { |
| 341 | + return heap.length && heap.pop()[1]; |
| 342 | + } |
| 343 | + |
| 344 | + // Initialize the heap with the outgoing edges of vertex zero. |
| 345 | + set[0] = 1; |
| 346 | + for (const i of delaunay.neighbors(0)) { |
| 347 | + heap_insert([0, i], score(points, 0, i)); |
| 348 | + } |
| 349 | + |
| 350 | + // For each remaining minimum edge in the heap… |
| 351 | + let edge; |
| 352 | + while (edge = heap_pop()) { |
| 353 | + const [i, j] = edge; |
| 354 | + |
| 355 | + // If j is already connected, skip; otherwise add the new edge to point j. |
| 356 | + if (set[j]) continue; |
| 357 | + set[j] = 1; |
| 358 | + tree.add(`${extent([i, j])}`); |
| 359 | + |
| 360 | + // Add each unconnected neighbor k of point j to the heap. |
| 361 | + for (const k of delaunay.neighbors(j)) { |
| 362 | + if (set[k]) continue; |
| 363 | + heap_insert([j, k], score(points, j, k)); |
| 364 | + } |
| 365 | + } |
| 366 | + |
| 367 | + return (i) => { |
| 368 | + const a = triangles[i]; |
| 369 | + const b = triangles[i % 3 === 2 ? i - 2 : i + 1]; |
| 370 | + return tree.has(`${extent([a, b])}`); |
| 371 | + }; |
| 372 | + } |
| 373 | +} |
| 374 | + |
| 375 | +export function gabrielMesh(data, options) { |
| 376 | + return delaunayMark(GabrielMesh, data, options); |
| 377 | +} |
| 378 | + |
| 379 | +export function urquhartMesh(data, options) { |
| 380 | + return delaunayMark(UrquhartMesh, data, options); |
| 381 | +} |
| 382 | + |
| 383 | +export function mstMesh(data, options) { |
| 384 | + return delaunayMark(MSTMesh, data, options); |
| 385 | +} |
0 commit comments