多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

如何用 d3.treemap 把层级数据渲染为矩形布局?

如何用 d3.treemap 把层级数据渲染为矩形布局? 如何用 d3.treemap 把层级数据渲染为矩形布局【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3如果你手里有一份带数值的层级数据比如按目录统计的文件大小、按部门拆分的营收想把它画成一块被递归细分的矩形区域——即 treemap——那么 d3-hierarchy 的d3.treemap()就是布局计算工具。本文的完整目标是在浏览器里加载 D3从一份层级 JSON 出发调用d3.treemap()为每个节点算出x0, y0, x1, y1四个矩形坐标再把它们渲染成 SVG 矩形并验证结果。依据是仓库中的 d3-hierarchy/treemap 与 d3-hierarchy/hierarchy 文档。D3 在任何 JavaScript 环境都能运行。按 getting started 的说明官方推荐使用 CDN 托管的 ES module 包如果你需要不依赖模块加载的环境还有 UMD 包和 npm 安装两种替代方式。准备加载 D3 并确认数据形态treemap 文档要求布局的输入是一个 hierarchy 根节点因此数据必须满足两点数据本身是层级结构。文档给出的标准形态是一个根对象其children数组逐层嵌套子节点const data { name: Eve, children: [ {name: Cain}, {name: Seth, children: [{name: Enos}, {name: Noam}]}, {name: Abel}, {name: Awan, children: [{name: Enoch}]}, {name: Azura} ] };如果你的数据还是 CSV 等表格形式hierarchy 文档指出需要先用 stratify 整理成层级后再传给d3.hierarchy。每个参与面积计算的节点要有非负数值。node.sum的文档明确要求 value 访问函数返回非负数。下面把上面的示例数据补充value字段叶子节点取数值内部节点不取值后面代码即按这份数据编写const data { name: Eve, children: [ {name: Cain, value: 45}, {name: Seth, children: [{name: Enos, value: 10}, {name: Noam, value: 20}]}, {name: Abel, value: 30}, {name: Awan, children: [{name: Enoch, value: 15}]}, {name: Azura, value: 40} ] };加载 D3 有三种方式任选其一后文代码对三者通用CDN ES module文档推荐!DOCTYPE html div idcontainer/div script typemodule import * as d3 from https://cdn.jsdelivr.net/npm/d37/esm; // ……后文的完整代码…… container.append(svg.node()); /scriptCDN UMD用script srchttps://cdn.jsdelivr.net/npm/d37/script加载它导出全局d3后续代码不变。npm执行npm install d3文档也列出 yarn / pnpm 对应命令然后在模块里import * as d3 from d3;。此外 getting started 还提到 Observable 笔记本内置 D3、返回单元格生成的 DOM 元素即可展示图表可作为在线调试环境。第一步构建层级并计算节点数值用d3.hierarchy(data)从上面的data构造根节点const root d3.hierarchy(data);不指定 children 访问函数时默认取d.children正好匹配上面的数据结构。构造完成后每个节点带有node.data、node.depth、node.height、node.parent、node.children等属性其中node.value是需要你显式计算汇总的可选属性。treemap 文档写明必须在把层级交给 treemap 布局之前调用root.sum或root.count否则节点没有value可用。sum按后序遍历把每个节点的值设为自身访问函数返回值 所有子节点值之和访问函数收到的是节点的 dataroot.sum((d) d.value ?? 0);?? 0只用于本示例内部节点没有value字段返回 0 表示数值全部来自叶子。这正是文档给出的做法——如果你只想让叶子节点贡献数值对任何有 children 的节点返回 0例如root.sum((d) d.value ? 1 : 0)。若数据每个节点都自带汇总值则写成root.sum((d) d.value)。如果只想按叶子数量布局可以直接用root.count()它把每个节点值设为其下属叶子数。第二步配置并调用 treemap 布局treemap 文档 的默认值size默认为[1, 1]padding默认为 0round默认false平铺方法默认为黄金比例的treemapSquarify。因此要得到实际尺寸的布局必须先设置size。hierarchy文档给出的官方示例写法是// Construct the treemap layout. const treemap d3.treemap(); treemap.size([width, height]); treemap.padding(2);其中width和height是画布尺寸取你自己的值padding(2)同时设置内边距和外边距本文取 2 像素数值可自定。文档同时建议在计算布局前调用root.sort对层级排序treemap 推荐的比较方式是高度降序、值降序root.sort((a, b) b.height - a.height || b.value - a.value);注意 sort 的比较函数收到的是两个节点而不是两个 data这与Array.prototype.sort比较 data 的习惯不同。排序必须在调用布局前完成否则不影响本次布局结果。最后调用布局计算treemap(root);调用后root及其每个后代节点都被写入了四个属性这就是渲染的全部输入node.x0—— 矩形左边缘node.y0—— 矩形上边缘node.x1—— 矩形右边缘node.y1—— 矩形下边缘第三步把节点渲染成 SVG 矩形布局只负责坐标画矩形需要 d3-selection 的数据绑定。按 joining 文档 的selectAll().join()模式先创建 SVG 容器getting-started 的空白图表示例即d3.create(svg)再为每个后代节点生成一个rect// 创建 SVG 容器尺寸与上一步 size 一致 const svg d3.create(svg) .attr(width, width) .attr(height, height); // 为每个节点生成一个矩形 svg.append(g) .selectAll(rect) .data(root.descendants()) .join(rect) .attr(x, (d) d.x0) .attr(y, (d) d.y0) .attr(width, (d) d.x1 - d.x0) .attr(height, (d) d.y1 - d.y0); // 展示在独立 HTML 页面里把 SVG 挂到容器上 container.append(svg.node());root.descendants()按拓扑序返回根节点 所有后代每个节点恰好对应一个rectjoin(rect)完成数据到元素的绑定。在 Observable 里则不需要container.append直接把svg.node()从单元格返回即可。验证布局结果文档没有给出成功日志但给出了可判断的坐标契约每个节点必须有x0, y0, x1, y1矩形宽高即x1 - x0与y1 - y0。运行后在浏览器控制台检查console.log(root.x0, root.y0, root.x1, root.y1); // 根节点四个坐标 const leaf root.leaves()[0]; console.log(leaf.data.name, leaf.x0, leaf.y0, leaf.x1, leaf.y1);可核对的点根节点与每个叶子的x1 x0且y1 y0宽高为正画出的矩形不翻转、不为零宽leaf.data.name应等于你在data里写的名字如示例中的 Cain说明node.data关联正确视觉上所有矩形铺满width × height区域相邻子块之间有 2 像素缝隙对应padding(2)没有任何矩形越出 SVG 边界。若某个区域缺失或错位先检查两处最常见的文档明确要求是否漏了root.sum(...)treemap 文档将其列为布局前的强制步骤以及size([width, height])是否设置成了你实际的画布尺寸而不是默认[1, 1]。调整平铺方式与可选配置treemap.tile 用于更换递归细分算法。默认的treemapSquarify实现 Bruls 等人的 squarified 算法目标矩形宽高比默认为黄金比例 φ (1 √5) / 2。文档说明该比例只是给算法的提示不保证每个矩形恰好等于该比例squarify.ratio(ratio)可以改目标比例要求 ≥ 1。其余内置方法的行为方法文档描述的行为treemapDice按子节点数值水平划分从左边缘x0起依次排布treemapSlice按子节点数值垂直划分从上边缘y0起依次排布treemapSliceDice奇数深度委托给 slice否则委托给 dice即按深度交替横竖切分treemapBinary递归划分成近似平衡的二叉树宽矩形用水平划分、高矩形用垂直划分treemapResquarify与 squarify 类似但保留上一次treemapResquarify布局中节点相邻关系适合做 treemap 的形变动画只改尺寸不改相对位置代价是后续布局不再是严格 squarify 结果切换方式const treemap d3.treemap() .tile(d3.treemapSliceDice) .size([width, height]) .padding(2);其余可选配置均为 treemap 文档给出的默认值round(true)开启坐标取整默认falsepaddingInner(n)/paddingOuter(n)分别单独控制内边距分隔相邻子节点与外边距分隔节点边缘与其子节点都默认 0也可传按节点取值的函数。完整示例与文档限制把上面各步拼起来是一个可整页运行的独立 HTMLESM CDN 加载其余按你的数据替换!DOCTYPE html div idcontainer/div script typemodule import * as d3 from https://cdn.jsdelivr.net/npm/d37/esm; const width 640; const height 400; const data { name: Eve, children: [ {name: Cain, value: 45}, {name: Seth, children: [{name: Enos, value: 10}, {name: Noam, value: 20}]}, {name: Abel, value: 30}, {name: Awan, children: [{name: Enoch, value: 15}]}, {name: Azura, value: 40} ] }; const root d3.hierarchy(data); const treemap d3.treemap() .size([width, height]) .padding(2); root.sum((d) d.value ?? 0); root.sort((a, b) b.height - a.height || b.value - a.value); treemap(root); const svg d3.create(svg) .attr(width, width) .attr(height, height); svg.append(g) .selectAll(rect) .data(root.descendants()) .join(rect) .attr(x, (d) d.x0) .attr(y, (d) d.y0) .attr(width, (d) d.x1 - d.x0) .attr(height, (d) d.y1 - d.y0); container.append(svg.node()); /script运行后按上一节的验证清单检查。文档明确给出的边界只有两条布局前必须sum或count以及sum的访问函数必须返回非负数。若你的数据是表格而非层级先走 stratify若想让节点间留白更精细再阅读 treemap 文档 中paddingTop/Right/Bottom/Left的单独控制接口。【免费下载链接】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),仅供参考
返回列表