十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

d3-hierarchy 技术详解:从层级数据建模到树、Treemap 与圆形嵌套布局的完整工作流

d3-hierarchy 技术详解:从层级数据建模到树、Treemap 与圆形嵌套布局的完整工作流 d3-hierarchy 技术详解从层级数据建模到树、Treemap 与圆形嵌套布局的完整工作流【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本文基于 d3 仓库的 d3-hierarchy 模块文档及其子文档系统讲解层级可视化的三类图型节点-连线图、邻接图、包围图、根节点构建函数d3.hierarchy与表格数据转换器d3.stratify的完整 API以及 tree、cluster、partition、pack、treemap 五种布局算子的参数与源码实现要点。读完后你可以从一份 CSV 表格数据出发独立完成一棵整齐树、树状图dendrogram、冰柱图、圆环图、圆形嵌套图或 Treemap 的布局计算与 SVG 绘制。1. 模块定位三类层级图型与七份 API 文档许多数据集天然是层级结构的地理实体普查区、县、州、企业与政府的指挥链、文件系统、软件包即便是非层级数据也可以组织成层级形式例如k-means 聚类结果或系统发育树。一份好的层级可视化支持快速的多尺度推断——既能观察单个元素的微观细节也能把握大分组的宏观结构。d3-hierarchy 模块实现了以下三类经典图型这是 模块总览文档的核心内容节点-连线图Node-link diagrams用离散图元表示节点和连线例如每个节点一个圆、每条父子关系一条线。其中的 “tidy” 树 非常紧凑dendrogram树状图 则把所有叶子放在同一深度两者都有极坐标与笛卡尔形式。缩进树indented trees适合交互式浏览。邻接图Adjacency diagrams通过节点的相对位置表达拓扑结构并可以用节点面积编码一个定量维度如营收、文件体积。“icicle冰柱”图partition 布局使用矩形而 sunburst圆环图使用环形扇段。包围图Enclosure diagrams同样用面积编码但通过“包含/嵌套”表达拓扑。Treemap 递归地将面积划分为矩形圆形嵌套circle-packing 紧密嵌套圆形——空间利用率不如 treemap但更能直观地展示拓扑结构。模块总览文档指向七份完整的 API 参考文档构成本文的展开脉络文档职责hierarchy.md表示与操作层级数据根节点构建、遍历、sum/count/sortstratify.md将表格数据组织为层级tree.md构建 “tidy” 树布局cluster.md构建叶子等深的树状图布局partition.md构建空间填充的邻接图布局pack.md通过紧密嵌套圆形构建包围图treemap.md按定量值递归细分矩形在工程层面本仓库d3 全量包当前版本 7.9.0见 package.json将d3-hierarchy作为依赖d3-hierarchy: ^3.1.2package.json并在 src/index.js 中export * from d3-hierarchy;因此安装 d3 后即可通过d3.hierarchy、d3.stratify、d3.tree等全局命名空间直接使用本模块的全部 API。2. 构建根节点d3.hierarchy(data, children)计算任何层级布局之前首先需要一棵树的根节点。如果数据已经是层级格式如嵌套 JSON可以直接传给hierarchy否则可以用 stratify 先把表格数据整理成层级。给定一棵典型的嵌套数据const data { name: Eve, children: [ {name: Cain}, {name: Seth, children: [{name: Enos}, {name: Noam}]}, {name: Abel}, {name: Awan, children: [{name: Enoch}]}, {name: Azura} ] };构建层级只需一行const root d3.hierarchy(data);children访问器函数从根data开始依次对每个数据项调用返回一个可迭代对象children 数组或空。不指定时默认的访问器为function children(d) { return d.children; }一个值得注意的细节如果data是Map它会被隐式转换为[undefined, data]形式的 entry此时 children 访问器默认变为function children(d) { return Array.isArray(d) ? d[1] : null; }这意味着可以直接把 d3-array 的 group 或 rollup 分组结果传入hierarchy无需手工展平——这是“表格 → 分组 → 树”流水线里非常实用的衔接点。返回的根节点及每个后代节点都携带以下六个属性后续所有布局算子都基于它们工作属性含义node.data传给hierarchy的关联原始数据node.depth根为 0每深一代 1node.height到任一后代叶子的最大距离叶子为 0node.parent父节点根为 nullnode.children子节点数组如有叶子为 undefinednode.value该节点及其后代之和的可选累加值由 sum/count 写入此外d3.hierarchy构造函数可用于instanceof类型判断也允许扩展节点原型。2.1 遍历与路径方法方法说明node.ancestors()返回祖先数组从本节点开始逐级向上直到根node.descendants()返回后代数组从本节点开始按拓扑序包含所有孩子node.leaves()按遍历顺序返回叶子节点数组没有 children 的节点node.find(filter)返回第一个使filter为真的节点找不到返回 undefinednode.path(target)返回本节点到target的最短路径先上升到最近公共祖先再下降到目标适合层级边捆绑hierarchical edge bundlingnode.links()返回本节点及后代的连线数组每条 link 的source是父节点、target是子节点——这是把树渲染成 SVG 线段的常用入口2.2 聚合sum与countnode.sum(value)以后序遍历见 2.3对本节点及每个后代求值指定的value函数并把返回的数值加上所有子节点之和写入node.value。value函数接收节点的 data必须返回非负数。注意value访问器会对节点和每个后代包括内部节点求值如果只想让叶子节点贡献数值就要对带有 children 的节点返回 0。例如作为node.count的替代只统计带 value 字段的节点root.sum((d) d.value ? 1 : 0);凡是依赖node.value的布局如 treemap、pack、partition都必须先调用sum或count。一个完整的 Treemap 示例// 构建 treemap 布局 const treemap d3.treemap(); treemap.size([width, height]); treemap.padding(2); // 累加并排序数据 root.sum((d) d.value); root.sort((a, b) b.height - a.height || b.value - a.value); // 计算 treemap 布局 treemap(root); // 取回所有后代节点 const nodes root.descendants();由于 API 支持方法链上例也可以写成一行链式调用d3.treemap() .size([width, height]) .padding(2) (root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value)) .descendants()node.count()则计算本节点下叶子的数量并写入node.value每个后代同理叶子本身的计数为 1返回本节点。2.3 排序sort与三种推荐顺序node.sort(compare)以前序遍历依次对本节点及每个后代的 children 排序返回本节点。比较函数接收两个节点a、b注意与sum的value函数不同——它接收的是节点而非数据a应在b前则返回小于 0 的数反之返回大于 0 的数。文档给出了三种典型排序策略// 按降序聚合值排序——圆形嵌套pack推荐 root .sum((d) d.value) .sort((a, b) b.value - a.value); // 先按降序高度、再按降序值排序——treemap 与冰柱图partition推荐 root .sum((d) d.value) .sort((a, b) b.height - a.height || b.value - a.value); // 先按降序高度、再按升序 id 排序——tree 与 dendrogramcluster推荐 root .sum((d) d.value) .sort((a, b) b.height - a.height || d3.ascending(a.id, b.id));如果想让排序影响布局必须在调用布局算子之前执行sort。2.4 访问器、迭代器与拷贝node[Symbol.iterator]()按广度优先顺序返回节点后代的迭代器可以直接for (const descendant of node) { ... }node.each(function, that)按广度优先顺序某节点只在其所有更浅层节点及同层前序节点都访问后才被访问对节点及后代调用functionnode.eachAfter(function, that)按后序遍历调用即节点在所有后代访问完之后才被访问——sum内部正是这种顺序node.eachBefore(function, that)按前序遍历调用即节点在所有祖先访问完之后才被访问——sort内部正是这种顺序以上三个回调都接收当前descendant、从零开始的遍历index和本nodethat指定回调的 this 上下文node.copy()返回以本节点为起点的子树的深拷贝拷贝共享同一份 data。返回的节点是新树的根其 parent 恒为 null、depth 恒为 0。3. 表格数据转层级d3.stratifyStratify 文档从一个关系表出发NameParentEveCainEveSethEveEnosSethNoamSethAbelEveAwanEveEnochAwanAzuraEve由于名字恰好唯一该层级可以无歧义地写成 CSVname,parent Eve, Cain,Eve Seth,Eve Enos,Seth Noam,Seth Abel,Eve Awan,Eve Enoch,Awan Azura,Eve用 d3-dsv 的csvParse解析得到{name, parent}对象数组后转换为层级const table d3.csvParse(text); const root d3.stratify() .id((d) d.name) .parentId((d) d.parent) (table);生成的层级现在可以直接传给 tree 等任意层级布局进行可视化。stratify 算子的 API 如下d3.stratify()以默认设置创建新的 stratify 算子stratify(data)从指定的表格data生成新的层级stratify.id(id)设置 id 访问器接收d和i默认d d.id。返回的字符串用于结合 parentId 确定节点关系叶子节点的 id 可以为 undefined否则必须唯一null 与空字符串等价于 undefinedstratify.parentId(parentId)设置父 id 访问器默认d d.parentId。输入数据中必须恰好有一个根节点其 parentId 为 undefinednull/空串视为 undefined且不允许环形关系stratify.path(path)设置路径访问器默认为 undefined。一旦设置了 path 访问器id 与 parentId 访问器即被忽略改为对 path 返回的斜杠分隔字符串计算类 Unix 层级必要时自动推补父节点和 id——非常适合文件系统路径。例如对一组文件路径const paths [ axes.js, channel.js, context.js, legends.js, legends/ramp.js, marks/density.js, marks/dot.js, marks/frame.js, scales/diverging.js, scales/index.js, scales/ordinal.js, stats.js, style.js, transforms/basic.js, transforms/bin.js, transforms/centroid.js, warnings.js, ]; const root d3.stratify().path((d) d)(paths);4. 节点-连线布局tree 与 cluster4.1 tree“tidy” 树Tree 文档指出tree 布局采用Reingold–Tilford “tidy” 算法经 Buchheim 等人的改进可在线性时间内运行生成的整齐树通常比 dendrogram 更紧凑。d3.tree()以默认设置创建 tree 布局tree(root)对指定 hierarchy 求布局在root及其后代上写入node.x— 节点的 x 坐标node.y— 节点的 y 坐标。x、y 是任意坐标系例如把x当角度、y当半径即可得到径向布局。建议在传入布局前调用root.sort。参数tree.size(size)设置布局尺寸为二元数组[width, height]默认为[1, 1]设为 null 表示改用 nodeSize 模式。径向布局中[360, radius]对应 360° 广度与radius深度tree.nodeSize(size)设置节点尺寸为[width, height]默认为 null表示使用 size 模式。指定 nodeSize 时根节点始终位于 ⟨0, 0⟩tree.separation(separation)设置分隔函数默认function separation(a, b) { return a.parent b.parent ? 1 : 2; }径向布局下更合适的变体会按深度比例缩小间隔function separation(a, b) { return (a.parent b.parent ? 1 : 2) / a.depth; }分隔函数接收相邻的节点a、b返回期望的分隔量。两节点通常是兄弟但如果布局决定把关系较远的节点放在相邻位置也可能是更远的亲属。4.2 cluster叶子等深的树状图Cluster 文档中的 cluster 布局生成dendrogram树状图把所有叶子节点放在同一深度的节点-连线图。树状图通常不如 tidy 树紧凑但在“所有叶子必须同级”的场景层次聚类、系统发育树中非常有用。其 API 与 tree 完全对称cluster(root)写入node.x/node.ycluster.size(size)默认[1, 1]、支持 null 切换 nodeSize 模式径向布局同样可用[360, radius]cluster.nodeSize(size)默认 null指定时根节点位于 ⟨0, 0⟩cluster.separation(separation)默认同为a.parent b.parent ? 1 : 2用于分隔相邻叶子。选型建议想让树尽量紧凑 → tree强调“叶子代表同一类比较对象”聚类结果、进化树→ cluster。5. 邻接布局partitionicicle / sunburstPartition 文档说明partition 布局生成邻接图——节点-连线树的空间填充变体。不再画父子连线而是把节点画成实心区域矩形或弧形通过相对位置揭示层级关系节点尺寸则编码了节点-连线图难以表现的定量维度。矩形形态即 icicle冰柱图弧形形态即 sunburst圆环图。d3.partition()以默认设置创建 partition 布局partition(root)对指定层级求布局写入四个坐标属性属性含义node.x0矩形左边缘node.y0矩形上边缘node.x1矩形右边缘node.y1矩形下边缘必须先调用root.sum再传入本布局通常还应先调用root.sort决定子树顺序。参数partition.size(size)设置尺寸[width, height]默认[1, 1]partition.round(round)按布尔值开关坐标取整默认 falsepartition.padding(padding)设置用于分隔同一节点相邻子节点的填充默认 0。6. 包围布局pack 与 treemap6.1 pack圆形嵌套Pack 文档指出包围图用包含嵌套表达层级叶子圆的大小编码数据的定量维度外层的包围圆表示各子树的大致累计规模但由于存在空间浪费会有一定失真——只有叶子节点之间可以精确比较。圆形嵌套的空间利用率不如 treemap但“浪费”出来的空隙反而更醒目地呈现了层级结构。d3.pack()以默认设置创建 pack 布局pack(root)对指定层级求布局写入node.x— 圆心 x 坐标node.y— 圆心 y 坐标node.r— 圆半径。同样必须先调用root.sum通常还应先root.sort按降序值排序是 pack 的推荐顺序。参数与底层工具pack.radius(radius)设置半径访问器默认 null。为 null 时叶子圆半径由叶子的node.value由sum计算推导并按比例缩放以适应 size不为 null 时半径由函数精确指定pack.size(size)设置尺寸[width, height]默认[1, 1]pack.padding(padding)设置填充数字或函数默认 0。兄弟圆相切处会分开约指定填充父圆与其子圆之间也分开约指定填充。若未显式指定半径由于需要用“两遍算法”适配布局尺寸先无填充打包 → 计算缩放因子并应用于填充 → 带填充重新打包填充值只是近似的d3.packSiblings(circles)把给定圆数组每个必须有circle.r按Wang 等人的 front-chain packing 算法紧密打包为每个圆写入circle.x、circle.y。它是 pack 布局兄弟节点排布的核心算法也可以脱离布局单独使用d3.packEnclose(circles)计算包围给定圆数组每个圆需有r、x、y的最小圆采用 Matoušek-Sharir-Welzl 算法。这是包围圆几何的基础原语pack 用它为每个内部节点求外圈。6.2 treemap矩形递归细分Treemap 文档说明Treemap 由 Ben Shneiderman 于 1991 年提出按节点关联值把面积递归细分为矩形。d3 的实现支持可扩展的平铺方法默认 squarify 方法力求生成接近黄金比例的矩形可读性与尺寸估计优于简单的 slice-and-dice仅按深度交替水平/垂直切分。d3.treemap()以默认设置创建 treemap 布局treemap(root)对指定层级求布局写入node.x0、node.y0、node.x1、node.y1矩形的左右上下四边。同样要求先root.sum建议先root.sort按降序高度、再降序值是 treemap 的推荐顺序。参数treemap.tile(tile)设置平铺方法默认为黄金比例下的 treemapSquarifytreemap.size(size)设置尺寸[width, height]默认[1, 1]treemap.round(round)开关坐标取整默认 falsetreemap.padding(padding)同时设置内、外填充数字或函数treemap.paddingInner(padding)内填充默认 0。若为函数对每个带 children 的节点调用。用于分隔同一节点的相邻子节点treemap.paddingOuter(padding)外填充等价于同时设置四个方向的 paddingtreemap.paddingTop / paddingRight / paddingBottom / paddingLeft(padding)分别设置四边外填充默认均为 0。若为函数对每个带 children 的节点调用用于分隔节点某条边与其子节点。内置平铺方法均可传给treemap.tile方法行为treemapBinary(node, x0, y0, x1, y1)递归地把节点集划分为近似平衡的二叉树宽矩形做水平划分高矩形做垂直划分treemapDice(node, x0, y0, x1, y1)按各 child 的值水平切分矩形子节点从左边缘 (x0) 起依次排列若子节点值之和小于节点自身值即节点有非零内部值剩余空白落在右边缘 (x1)treemapSlice(node, x0, y0, x1, y1)按各 child 的值垂直切分矩形子节点从上边缘 (y0) 起依次排列剩余空白落在下边缘 (y1)treemapSliceDice(node, x0, y0, x1, y1)节点深度为奇数时委托 slice否则委托 dicetreemapSquarify(node, x0, y0, x1, y1)实现 Bruls 等人的 squarified treemap 算法力求产出指定宽高比的矩形treemapResquarify(node, x0, y0, x1, y1)类似 squarify但保持上一次布局的拓扑节点相邻关系。适合动画更新 treemap只改变节点尺寸而不改变相对位置避免令人分心的重排与遮挡代价是后续更新的布局不再最优——只有第一次布局使用 Bruls 算法平铺比例参数squarify.ratio(ratio)指定期望的矩形宽高比必须 ≥ 1。注意该比例不蕴含方向——ratio 为 2 时会同时产出 2:1 与 1:2 的矩形混合。且ratio只是给平铺算法的提示不保证所有矩形都达到该比例。默认值为黄金比例 φ (1 √5) / 2依据 Kong 等人的感知研究。7. 实战工作流小结从数据到布局的固定顺序综合各子文档的要求一条可靠的 d3-hierarchy 使用流水线是建树嵌套 JSON 直接d3.hierarchy(data)表格/CSV 用d3.stratify().id().parentId()(rows)路径列表用d3.stratify().path(d d)(paths)聚合面积类布局partition、pack、treemap前必须root.sum(d d.value)或root.count()节点-连线类布局tree、cluster可省略但仍建议为排序提供 value排序布局前pack 按b.value - a.valuetreemap/partition 按b.height - a.height || b.value - a.valuetree/cluster 按b.height - a.height || d3.ascending(a.id, b.id)求布局d3.tree()/d3.cluster()输出x, yd3.partition()/d3.treemap()输出x0, y0, x1, y1d3.pack()输出x, y, r渲染与交互用root.links()取连线端点画线段tree/cluster用坐标直接画矩形partition/treemap或圆pack缩进树可借助node.depth做 x 向缩进适合可折叠的交互式浏览。所有坐标都在布局自己的坐标系中默认归一化到[1, 1]通过size映射到目标像素因此上述任一布局都能通过调整 size/坐标系约定如极坐标无缝切换成径向形态。8. 延伸阅读与仓库入口模块总览docs/d3-hierarchy.md七份 API 文档hierarchy、stratify、tree、cluster、partition、pack、treemap全量包入口与导出src/index.jsexport * from d3-hierarchy;版本与依赖声明package.jsond3-hierarchy ^3.1.2d3 7.9.0仓库内置的 pack 布局对比示例图img/pack-v3.png、img/pack-v4.pngstratify 家族树示意img/stratify.png掌握以上七份文档对应的 API 后d3-hierarchy 能覆盖绝大多数层级可视化需求节点-连线图tree/cluster表达拓扑邻接图partition兼顾拓扑与定量面积包围图pack/treemap则用包含关系让“部分如何构成整体”一目了然。【免费下载链接】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),仅供参考
返回列表