ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

d3-delaunay Voronoi 图完整指南:delaunay.voronoi(bounds)、单元格查询与 Canvas/SVG 渲染

d3-delaunay Voronoi 图完整指南:delaunay.voronoi(bounds)、单元格查询与 Canvas/SVG 渲染 d3-delaunay Voronoi 图完整指南delaunay.voronoi(bounds)、单元格查询与 Canvas/SVG 渲染【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本文基于 d3 官方 API 文档 docs/d3-delaunay/voronoi.md完整讲解 d3 7.x 中 Voronoi 图的创建、几何属性访问、空间查询contains / neighbors、四种渲染方法与单元格多边形获取方式并结合本仓库的依赖关系、API 索引与文档测试说明每个方法的行为边界与适用场景。读完后可直接用delaunay.voronoi(bounds)在 SVG 或 Canvas 中绘制 Voronoi 网格、单元格并实现最近点查询与 Lloyd 松弛等进阶应用。什么是 Voronoi 图d3 如何构建它给定一组点Voronoi 图泰森多边形把平面划分为若干单元格每个单元格代表平面上距离对应点最近的区域。Voronoi 图与 Delaunay 三角剖分互为对偶dual——这一点在官方文档 Delaunay triangulations 中同样有强调两个模块共享同一套底层几何结构。从本仓库的 d3-delaunay 模块概览文档 可以看到其算法背景该库是一个快速计算二维点集 Voronoi 图的库基于 Delaunator 的扫描线sweep类算法计算 Delaunay 三角剖分Voronoi 图是通过**连接 Delaunay 三角剖分中相邻三角形的 circumcenter外接圆圆心**构建的。这解释了后文voronoi.circumcenters属性的含义Delaunay 三角形的 circumcenter 序列恰好就是 Voronoi 单元格多边形的顶点坐标。在本仓库d3 伞包版本 7.9.0见 package.json中d3-delaunay 的实现代码以 npm 依赖形式引入d3-delaunay: ^6.0.4见 package.jsonyarn.lock 锁定到 6.0.4并在伞包入口 src/index.js 中通过export * from d3-delaunay;重新导出因此d3.Delaunay、Voronoi 相关 API 均可直接从d3命名空间使用。官方文档中各方法标注的源码位置为 d3-delaunay 包的src/delaunay.js与src/voronoi.js。创建 Voronoi 图delaunay.voronoi(bounds)delaunay.voronoi(bounds)在已有 Delaunay 三角剖分对象上调用voronoi返回对应的 Voronoi 图const delaunay d3.Delaunay.from([[0, 0], [0, 100], [100, 0], [100, 100]]); const voronoi delaunay.voronoi([0, 0, 640, 480]);关键行为约定均出自 voronoi.mdbounds参数为渲染视口[*xmin*, *ymin*, *xmax*, *ymax*]渲染时 Voronoi 网格会被裁剪clip到该范围。注意它是“渲染裁剪框”不是点集的包围盒若未指定bounds默认值为[0, 0, 960, 500]退化情形仍然返回 Voronoi 图0 个、1 个、2 个点以及共线点等不存在三角剖分的情况voronoi依然可用不会抛错。这使你在处理稀疏或动态增减点的数据时无需做特殊分支。由于 Voronoi 由 Delaunay 派生创建入口有两种性能取向不同见 delaunay.md 中的说明// 快路径直接传入扁平坐标数组通常为 Float64Array const delaunay new d3.Delaunay(Float64Array.of(0, 0, 0, 1, 1, 0, 1, 1)); const voronoi delaunay.voronoi([0, 0, 640, 480]); // 通用路径传入 [[x, y], ...] 或对象数组 访问器函数 // 官方提示Delaunay.from 会物化一个新的扁平 xy 数组因此通常比 new Delaunay 慢 const delaunay2 d3.Delaunay.from(points, (d) d.x, (d) d.y);底层几何属性delaunay、circumcenters、vectors 与 boundsVoronoi 对象暴露四个底层几何属性供不经过渲染方法直接取数的高性能场景使用。voronoi.delaunay该 Voronoi 图关联的 Delaunay 三角剖分对象即创建它的原始对象可反向访问points、triangles、hull等剖分结构。voronoi.circumcenters返回一个 Float64Array[*cx0*, *cy0*, *cx1*, *cy1*, …]每个连续坐标对是相应 Delaunay 三角形的外接圆圆心circumcenter。由于 Voronoi 图正是由相邻三角形 circumcenter 连线构成这些 circumcenter 就是 Voronoi 单元格多边形的顶点坐标。若你要自行构建单元格路径而不走renderCell这是最底层的取数入口。voronoi.vectors返回一个 Float64Array[*vx0*, *vy0*, *wx0*, *wy0*, …]其中每个非零四元组描述一个位于凸包outer hull上的开放无界单元格给出两条开放半直线open half-lines的方向。换句话说视口内的完整单元格由 circumcenter 序列闭合而成而外缘单元格的“无穷远处”边线则由 vectors 提供的方向向量在bounds范围内延长裁剪。voronoi.xmin /voronoi.ymin /voronoi.xmax /voronoi.ymax渲染视口[*xmin*, *ymin*, *xmax*, *ymax*]的读写属性。文档特别强调这些值只影响三个渲染方法render、renderBounds、renderCell不影响contains等查询语义。因此调整视口裁剪范围时只需改这四个值再重新渲染无需重建 Delaunay 剖分。空间查询contains 与 neighborsvoronoi.contains(i,x,y)const inside voronoi.contains(i, x, y); // true 表示 ⟨x, y⟩ 落在单元格 i 内返回单元格i是否包含指定点 ⟨x,y⟩——等价于判断点i是否为全图中离 ⟨x,y⟩ 最近的点。文档明确说明该方法的判断不受 Voronoi 图视口 bounds 影响即使 ⟨x,y⟩ 落在 bounds 之外语义仍然成立。voronoi.neighbors(i)返回一个可迭代对象iterable给出与单元格i共享边的所有单元格的下标。一个重要的语义细节Voronoi 邻居一定是 Delaunay 图上的邻居但反之不成立——当两个 Delaunay 邻居之间的公共边被 Voronoi 视口裁剪掉时它们就不再互为 Voronoi 邻居。这意味着若你在做邻接统计、聚类分析用voronoi.neighbors得到的关系是“视口内实际可见的邻接”与delaunay.neighbors(i)的纯图论邻接可能不同。典型用法const delaunay d3.Delaunay.from(points); const voronoi delaunay.voronoi([0, 0, 640, 480]); const i delaunay.find(120, 300); // 找到离 (120, 300) 最近的点 const N Array.from(voronoi.neighbors(i)); // 该点单元格的可见邻居下标官方文档中的交互演示正是这个组合在 1000 个高斯随机点d3.randomNormal.source(d3.randomLcg(42))生成上随指针移动用delaunay.find定位当前点、用voronoi.neighbors取出邻居并连线高亮。渲染方法render、renderBounds、renderCell三个渲染方法遵循统一的 context 契约context必须实现 Canvas Path Methods API 中对应的路径方法若不传 context则返回 SVG path 字符串。因此同一套 API 可同时服务 Canvas 2D 与 SVG 两条渲染路径。voronoi.render(context)渲染整个 Voronoi 单元格网格。context需实现moveTo与lineTo// SVG直接得到 path 字符串 const d voronoi.render(); pathEl.setAttribute(d, d); // Canvas交给 2D context voronoi.render(canvas.getContext(2d));svg width640 height480 path idmesh fillnone strokecurrentColor stroke-opacity0.5/path /svg script typemodule import * as d3 from d3; const delaunay d3.Delaunay.from(points); const voronoi delaunay.voronoi([0, 0, 640, 480]); document.querySelector(#mesh).setAttribute(d, voronoi.render()); /scriptvoronoi.renderBounds(context)渲染视口外框。context需实现rect。文档给出了它的精确等价形式context.rect(voronoi.xmin, voronoi.ymin, voronoi.xmax - voronoi.xmin, voronoi.ymax - voronoi.ymin)典型用途是在网格外加边框官方renderBounds演示图中即在 Voronoi 网格外围叠加了一个 frame。不传 context 时同样返回 SVG path 字符串。voronoi.renderCell(i,context)渲染下标为i的单个单元格。context需实现moveTo、lineTo与closePath比网格渲染多一个closePath因为单元格是闭合多边形。不传 context 时返回该单元格闭合轮廓的 SVG path 字符串——这是给单元格做独立填充例如按属性着色、单格高亮最常用的入口// 高亮第 i 个单元格 const cellPath voronoi.renderCell(i); highlightEl.setAttribute(d, cellPath); highlightEl.setAttribute(fill, orange);获取单元格多边形cellPolygons 与 cellPolygon如果需要多边形数据本身而非路径字符串有两个取数方法voronoi.cellPolygons()返回一个可迭代对象遍历所有非空单元格的多边形每个多边形对象带有其单元格下标作为属性。适合批量处理for (const {index, ring} of voronoi.cellPolygons()) { // index: 单元格下标ring: [[x0,y0], [x1,y1], ..., [x0,y0]] area[index] d3.polygonArea(ring); }voronoi.cellPolygon(i)返回点i对应单元格的凸闭合多边形[[*x0*, *y0*], [*x1*, *y1*], …, [*x0*, *y0*]]。这是做几何计算面积、质心、形变动画插值等的直接入口官方文档将其与renderCell互参引用——二者是同一几何数据的两种消费形式一个给几何计算一个给路径渲染。动态更新voronoi.update() 与 Lloyd 松弛voronoi.update()当点被**原地修改in-place**后调用voronoi.update()重建 Voronoi 图及底层三角剖分。文档明确指出它服务于 **Lloyd 松弛Lloyds relaxation**这一经典应用并说明其内部就是对底层剖分调用delaunay.update。一个典型的 Lloyd 松弛示意把每个点向其 Voronoi 单元的重心这里用多边形顶点均值近似移动若干轮点集会趋向均匀分布const delaunay d3.Delaunay.from(points); // points 为扁平 [x0,y0,x1,y1,...] const voronoi delaunay.voronoi([0, 0, 640, 480]); for (let iter 0; iter 10; iter) { for (let i 0; i points.length; i 2) { const ring voronoi.cellPolygon(i / 2); let sx 0, sy 0; for (const [x, y] of ring) { sx x; sy y; } points[i] sx / ring.length; points[i 1] sy / ring.length; } delaunay.update(); // 原地重建剖分voronoi.update() 等价 }要点update的前提是“点在原地被改”即修改同一数组内容而不是替换数组否则应重新Delaunay.from松弛后circumcenters、vectors与各渲染方法都会反映新几何无需重新delaunay.voronoi(bounds)由于 bounds 只约束渲染松弛过程中点移入/移出视口不会改变contains的判定。完整实战SVG 中的交互式邻居高亮下面把官方 voronoi.md 中neighbors一节交互演示的核心逻辑整理为可直接运行的 SVG 版本覆盖“创建剖分 → 设定视口 → 指针查询 → 渲染高亮”的完整链路svg idsvg width640 height480 path idmesh fillnone strokecurrentColor/path path idcell fillorange fill-opacity0.4/path path idlinks strokered stroke-width2/path /svg script typemodule import * as d3 from d3; const points Array.from({length: 200}, () [ d3.randomNormal() , d3.randomNormal() ]); const W 640, H 480; const X d3.scaleLinear().domain(d3.extent(points, d d[0])).range([0, W]); const Y d3.scaleLinear().domain(d3.extent(points, d d[1])).range([H, 0]); const delaunay d3.Delaunay.from(points, d X(d[0]), d Y(d[1])); const voronoi delaunay.voronoi([0, 0, W, H]); // 视口与画布一致 const mesh document.querySelector(#mesh); const cell document.querySelector(#cell); const links document.querySelector(#links); mesh.setAttribute(d, voronoi.render()); // 网格只需画一次 const svg document.querySelector(#svg); svg.addEventListener(pointermove, (event) { const [px, py] d3.pointer(event); const i delaunay.find(px, py); // 最近点 cell.setAttribute(d, voronoi.renderCell(i)); // 高亮其单元格 const N Array.from(voronoi.neighbors(i)); // 可见邻居 let d M${X(points[i][0])},${Y(points[i][1])}; for (const j of N) d L${X(points[j][0])},${Y(points[j][1])}M${X(points[i][0])},${Y(points[i][1])}; links.setAttribute(d, d); }); /script这个流程印证了各 API 的职责分工delaunay.find负责 O(log n) 级别的最近点定位voronoi.neighbors提供邻接关系renderCell提供单格高亮render提供一次性网格。本仓库中的交叉验证API 索引docs/api.md 的 d3-delaunay 一节完整列出了本文覆盖的全部方法——delaunay.voronoi、voronoi.delaunay、voronoi.circumcenters、voronoi.vectors、voronoi.xmin/ymin/xmax/ymax、voronoi.contains、voronoi.neighbors、voronoi.render/renderBounds/renderCell、voronoi.cellPolygons/cellPolygon、voronoi.update并与 docs/d3-delaunay/voronoi.md 中的锚点一一对应可作为快速跳转索引模块概览docs/d3-delaunay.md 说明了 Voronoi 由 Delaunator 扫描线算法得到的 Delaunay 剖分派生连接相邻三角形 circumcenter并链接到 Delaunay triangulations 与 Voronoi diagrams 两篇专题文档链接有效性本仓库的 test/docs-test.js 会递归扫描 docs 目录全部 Markdown校验内部链接与{#anchor}锚点是否真实存在——即本文引用的voronoi_render、voronoi_neighbors等锚点均受该测试约束版本前提以上行为以 d3 7.9.0 伞包package.json及其锁定的 d3-delaunay 6.0.4yarn.lock为准src/index.js的export * from d3-delaunaysrc/index.js保证了所有方法从d3命名空间直接可用。小结API用途输入/输出delaunay.voronoi(bounds)创建 Voronoi 图视口[xmin,ymin,xmax,ymax]默认[0,0,960,500]voronoi.delaunay反向获取 Delaunay 剖分属性voronoi.circumcenters单元格顶点三角形外接圆心Float64Arrayvoronoi.vectors外缘开放单元格的半直线方向Float64Arrayvoronoi.xmin/ymin/xmax/ymax渲染视口只影响渲染可读写voronoi.contains(i,x,y)点是否属于单元格 i不受视口影响booleanvoronoi.neighbors(i)共享边的单元格下标iterablevoronoi.render(context)渲染网格需moveTo/lineTo省略则返回 path 字符串voronoi.renderBounds(context)渲染视口外框需rectvoronoi.renderCell(i,context)渲染单个单元格需moveTo/lineTo/closePathvoronoi.cellPolygons()遍历非空单元格多边形含下标iterablevoronoi.cellPolygon(i)取单元格 i 的凸闭合多边形坐标数组voronoi.update()原地修改点后重建Lloyd 松弛调用delaunay.update掌握这套 API 后你可以在 d3 7.x 中完成从“一组点”到“可交互空间划分”的完整链路用delaunay.voronoi(bounds)建立带视口裁剪的 Voronoi 图用contains/neighbors做最近点与邻接查询用render/renderCell/cellPolygon分别服务网格绘制、单格高亮与几何计算并借助update()在点集动态演化如 Lloyd 松弛时原地重建整个图。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表