飞书画板矩形树图(Treemap)生成指南:基于 Slice-and-Dice 脚本化坐标计算与 whiteboard-cli 渲染实战
【免费下载链接】cliThe official Lark/飞书 CLI tool, maintained by the larksuite team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, and more, with 200+ commands and 20+ AI Agent Skills.项目地址: https://gitcode.com/gh_mirrors/cli414/cli
导读
本文讲解如何在飞书(Lark)官方 CLI 项目的 lark-whiteboard 技能体系中,用脚本化方式在飞书画板中生成矩形树图(Treemap):通过.cjs脚本按 Slice-and-Dice 交替切分法递归计算每个矩形的精确坐标与面积,输出 DSL JSON 后使用@larksuite/whiteboard-cli渲染,再经由lark-cli whiteboard +update写入画板。读完本文你将掌握 treemap 的场景约束、面积比例计算规则、父标签预留空间的正确做法、完整可运行的 JSON 骨架,以及渲染与写入画板的完整命令链路。
本文依据仓库中 skills/lark-whiteboard/scenes/treemap.md 编写,并补充 skills/lark-whiteboard/routes/dsl.md、skills/lark-whiteboard/references/lark-whiteboard-workflow.md 等仓库文档与相关源码佐证。
一、Treemap 场景定位与适用前提
矩形树图适合表达层级占比信息:用嵌套矩形面积直观呈现"总量 → 分类 → 子项"的数值比例关系。在 lark-whiteboard 技能的场景指南体系中,treemap 与架构图、组织架构图、柱状图、漏斗图、金字塔图等并列,归属于 DSL 脚本生成类场景(见 skills/lark-whiteboard/routes/dsl.md)。
从 skills/lark-whiteboard/SKILL.md 可以确认,本场景的完整使用前置条件包括:
- 本机已安装
lark-cli(运行lark-cli --version确认可用); - 可运行
npx -y @larksuite/whiteboard-cli@^0.2.13 -v(本技能约定的 whiteboard-cli 版本); - 开始前需先阅读 skills/lark-shared/SKILL.md 获取认证与权限处理规则。
为什么 treemap 必须走脚本生成
treemap 的每一个矩形面积都与数值严格成正比,多层嵌套下坐标相互依赖,任何一层的心算偏差都会导致面积失真。因此 treemap.md 明确给出了 Layout 选型结论:
- 脚本生成坐标(推荐):用
.cjs脚本递归切分矩形,脚本输出 JSON 文件后调用npx -y @larksuite/whiteboard-cli@^0.2.13渲染; - 不适合手动心算坐标。
这与 skills/lark-whiteboard/routes/dsl.md 中"构建方式是强约束:当 scene 指南要求『脚本生成』时,必须先写脚本(.cjs,CommonJS)并用node执行来产出 JSON 文件"的规则完全一致。
二、Content 约束:信息量与标签规范
在开始写脚本前,先用 skills/lark-whiteboard/elements/content.md 的思路规划信息量。treemap 场景对内容有硬性约束(见 treemap.md):
| 约束项 | 要求 |
|---|---|
| 顶层分类数量 | 3–5 个 |
| 每个分类下子项数量 | 2–4 个 |
| 面积比例 | 必须预先计算:每个矩形面积 = 父矩形面积 ×(本项数值 / 同级总数值) |
| 叶子节点标签 | 必须包含数值,格式如{{LABEL}} ({{VALUE}}) |
这意味着:分类数、子项数都有上下限,避免画面过密或信息过载;叶子节点的文字必须携带数值(如"CPU 服务器 (24)"),让读者无需对照数据表即可读取具体占比;总面积的比例关系由数值驱动,而不是由视觉喜好决定。
三、Layout 规则:Slice-and-Dice 交替切分法
矩形树图的核心算法是交替切分法(Slice-and-Dice),规则定义在 treemap.md:
- 交替切分方向:奇数层水平切分
width,偶数层垂直切分height; - 父标签预留空间:父矩形内必须为标题预留 30–40px 顶部空间,子矩形从
y + 35开始放置; - 边界约束:子节点必须完全落在父矩形范围内;
- 水平切分公式:子
width = 父 width × (子数值 / 父总数值),子x依次向右累加; - 垂直切分公式:子
height = (父 height - 35) × (子数值 / 父总数值),子y依次向下累加(注意扣除父标签预留的 35px)。
面积比例计算规则详解
treemap.md 给出了四步递归算法:
- 面积与数值严格成正比:任何层级的节点,其矩形面积
width × height必须与数值成比例; - 奇数层水平切分(如第一层分类):
- 父矩形的
height和y坐标传给所有子节点(扣除标签预留空间后); - 按子节点数值占父节点的比例切分父矩形的
width:子width = 父width × (子数值 / 父总数值); - 子节点的
x坐标依次向右累加;
- 父矩形的
- 偶数层垂直切分(如第二层子项):
- 父矩形的
width和x坐标传给所有子节点; - 按子节点数值占父节点的比例切分父矩形的
height:子height = 父height × (子数值 / 父总数值); - 子节点的
y坐标依次向下累加;
- 父矩形的
- 层层递归:不断交替水平和垂直切分方向,直到所有叶子节点都被分配了精确的坐标和宽高。
父标签预留空间
每个非叶子节点的矩形,顶部必须预留 30–40px 放置分类标签。子矩形从父矩形的y + 35开始放置,可用高度为父height - 35。
treemap.md 给出的示例:父矩形{ x: 40, y: 40, height: 700 },则:
- 父标签放在
y: 46(留 6px 上边距); - 子矩形从
y: 75开始放置(40 + 35); - 子矩形可用高度为
700 - 35 = 665。
为什么要扣 35px?分类标签是一个独立的
text节点,必须落在父矩形内部且不被子矩形遮挡。若子矩形从父矩形顶部直接开始,标签会被盖住;预留空间不足(<30px)则标签与子矩形拥挤粘连。
四、骨架示例:2 层 Treemap 完整 JSON
treemap.md 给出了一个可直接运行的 2 层 treemap 骨架:3 个分类(硬件 40、软件 35、服务 25),各含 2 个子项;根矩形 1100×700,第一层水平切分 width,第二层垂直切分 height。
{ "version": 2, "nodes": [ { "type": "rect", "id": "root", "x": 40, "y": 40, "width": 1100, "height": 700, "borderWidth": 2, "borderRadius": 6 }, { "type": "text", "x": 48, "y": 46, "width": 1084, "height": 24, "text": "{{ROOT_TITLE}}", "fontSize": 14 }, { "type": "rect", "id": "cat-A", "x": 40, "y": 75, "width": 440, "height": 665, "borderWidth": 2, "borderRadius": 6 }, { "type": "text", "x": 48, "y": 81, "width": 424, "height": 24, "text": "{{CAT_A}}", "fontSize": 14 }, { "type": "rect", "id": "cat-A-item-1", "x": 40, "y": 110, "width": 440, "height": 380, "borderRadius": 4 }, { "type": "text", "x": 48, "y": 116, "width": 424, "height": 24, "text": "{{ITEM_A1}} (24)", "fontSize": 14 }, { "type": "rect", "id": "cat-A-item-2", "x": 40, "y": 490, "width": 440, "height": 250, "borderRadius": 4 }, { "type": "text", "x": 48, "y": 496, "width": 424, "height": 24, "text": "{{ITEM_A2}} (16)", "fontSize": 14 }, { "type": "rect", "id": "cat-B", "x": 480, "y": 75, "width": 385, "height": 665, "borderWidth": 2, "borderRadius": 6 }, { "type": "text", "x": 488, "y": 81, "width": 369, "height": 24, "text": "{{CAT_B}}", "fontSize": 14 }, { "type": "rect", "id": "cat-B-item-1", "x": 480, "y": 110, "width": 385, "height": 380, "borderRadius": 4 }, { "type": "text", "x": 488, "y": 116, "width": 369, "height": 24, "text": "{{ITEM_B1}} (20)", "fontSize": 14 }, { "type": "rect", "id": "cat-B-item-2", "x": 480, "y": 490, "width": 385, "height": 285, "borderRadius": 4 }, { "type": "text", "x": 488, "y": 496, "width": 369, "height": 24, "text": "{{ITEM_B2}} (15)", "fontSize": 14 }, { "type": "rect", "id": "cat-C", "x": 865, "y": 75, "width": 275, "height": 665, "borderWidth": 2, "borderRadius": 6 }, { "type": "text", "x": 873, "y": 81, "width": 259, "height": 24, "text": "{{CAT_C}}", "fontSize": 14 }, { "type": "rect", "id": "cat-C-item-1", "x": 865, "y": 110, "width": 275, "height": 399, "borderRadius": 4 }, { "type": "text", "x": 873, "y": 116, "width": 259, "height": 24, "text": "{{ITEM_C1}} (15)", "fontSize": 14 }, { "type": "rect", "id": "cat-C-item-2", "x": 865, "y": 509, "width": 275, "height": 231, "borderRadius": 4 }, { "type": "text", "x": 873, "y": 515, "width": 259, "height": 24, "text": "{{ITEM_C2}} (10)", "fontSize": 14 } ] }面积比例验证(第一层水平切分 width)
- 硬件 40/100 × 1100 =440,软件 35/100 × 1100 =385,服务 25/100 × 1100 =275(宽度之和 440 + 385 + 275 = 1100,正好铺满根矩形宽度);
- 三个分类矩形的
x依次为 40、480(40 + 440)、865(480 + 385); - 子矩形从
y=75开始,可用高度 665; - 第二层垂直切分时(如 cat-A 内:item-1 数值 24、item-2 数值 16,同级总值 40):item-1 高度 = (665) × 24/40 =399,item-2 高度 = 665 × 16/40 =266,近似于示例中的 380 与 250(骨架保留呼吸空间后的近似值)。
需要说明的是:骨架示例为便于人工阅读对部分数值做了近似与留白处理;实际生产中由
.cjs脚本严格按比例递归计算,不依赖人工近似。
五、脚本生成:从 data 树到 diagram.json
treemap.md 明确要求:"此场景必须用 .cjs 脚本生成。Agent 使用时只需修改data树,其余坐标与矩形面积自动递归计算"。
结合 skills/lark-whiteboard/routes/dsl.md 的脚本构建流程,完整操作步骤如下:
- 创建产物目录:
./diagrams/YYYY-MM-DDTHHMMSS/(本地时间,不含冒号和时区后缀;用户指定路径时以用户为准); - 编写坐标计算脚本:保存为
diagram.gen.cjs(必须.cjs后缀——脚本用require()写,.js在 ESM 项目下会崩),脚本内部只暴露一份data树(分类与数值),用递归函数实现 Slice-and-Dice 切分; - 执行脚本产出 JSON:
node diagram.gen.cjs生成diagram.json; - 渲染预览:
npx -y @larksuite/whiteboard-cli@^0.2.13 -i diagram.json -o diagram.png(PNG 仅用于预览验证,不是最终产物); - 检查并交付:确认信息完整、布局合理、配色协调、文字无截断后,进入写入画板环节。
脚本核心逻辑的伪代码(依据 treemap.md 的切分规则)如下:
const { writeFileSync } = require('fs'); // Agent 只需修改这里:data 树 const data = { label: '{{ROOT_TITLE}}', value: 100, children: [ { label: '{{CAT_A}}', value: 40, children: [ { label: '{{ITEM_A1}}', value: 24 }, { label: '{{ITEM_A2}}', value: 16 } ]}, { label: '{{CAT_B}}', value: 35, children: [ /* ... */ ] }, { label: '{{CAT_C}}', value: 25, children: [ /* ... */ ] } ] }; // 递归切分:depth 为奇数层水平切分 width,偶数层垂直切分 height function slice(node, x, y, width, height, depth) { // 叶子节点:输出 rect + 带数值的 text // 非叶子节点:先输出分类标签 text,再按比例切分 // 水平切分: childWidth = width * (childValue / totalValue), x 依次累加 // 垂直切分: childHeight = (height - 35) * (childValue / totalValue), y 依次累加 }脚本输出结构对应 skills/lark-whiteboard/elements/schema.md 中的WBDocument顶层协议:{ "version": 2, "nodes": [...] },节点类型使用rect(矩形)与text(文本标签)。注意rect与text都是基础节点,x/y/width/height全部为固定像素数值——这正是 treemap 这类"极度依赖几何坐标"的图必须走脚本构建的原因(见 skills/lark-whiteboard/elements/layout.md)。
六、配色:顶层分类用色板区分,子节点继承色系
treemap 的层级结构天然需要颜色分组。根据 skills/lark-whiteboard/elements/style.md 的上色原则与 treemap.md 的要求:
- 不同顶层分类必须用不同背景色(从色板选取),所有子节点继承对应色系;
- 外层浅色填充 + 内层白色节点 + 分组色边框:分类矩形用浅色
fillColor,叶子节点用#FFFFFF填充 + 所属分组的深色borderColor; - 分类标签文字统一用深色
#1F2329,颜色区分靠容器背景与边框,不靠标签文字变色。
以经典色板为例(见 style.md):
| 分组 | 层容器 fillColor | 层容器 borderColor | 内部节点 borderColor |
|---|---|---|---|
| 第 1 组(硬件) | #F0F4FC(浅蓝) | #5178C6 | #5178C6 |
| 第 2 组(软件) | #EAE2FE(浅紫) | #8569CB | #8569CB |
| 第 3 组(服务) | #DFF5E5(浅绿) | #509863 | #509863 |
用户未指定配色时必须从色板选取(#E8F3FF、#1664FF等自创色值不在色板中,禁止使用);用户指定了色值/风格时以用户为准。
七、渲染与写入画板:完整命令链路
第一步:获取 board_token
根据 skills/lark-whiteboard/references/lark-whiteboard-workflow.md:
- 用户直接给了 whiteboard token(
wbcnXXX):直接使用; - 文档 URL 或 doc_id,文档中已有画板:
lark-cli docs +fetch --doc <URL> --as user,从返回的<whiteboard token="xxx"/>提取; - 需要新建画板:
lark-cli docs +update --doc <doc_id> --command append --content '<whiteboard type="blank"></whiteboard>' --as user,从响应data.new_blocks[0].block_token取得。
第二步:渲染与写入
脚本产出diagram.json后,先用 whiteboard-cli 渲染 PNG 预览自查:
npx -y @larksuite/whiteboard-cli@^0.2.13 -i diagram.json -o diagram.png确认无误后,将 DSL JSON 转换为 OpenAPI 原生节点格式并 pipe 给lark-cli whiteboard +update(链路详见 dsl.md 与 lark-whiteboard-update.md):
npx -y @larksuite/whiteboard-cli@^0.2.13 -i diagram.json --to openapi --format json \ | lark-cli whiteboard +update --whiteboard-token <board_token> \ --source - --input_format raw --idempotent-token <时间戳+标识> --as user+update关键参数(详见 skills/lark-whiteboard/references/lark-whiteboard-update.md):
| 参数 | 必填 | 说明 |
|---|---|---|
--whiteboard-token | 是 | 画板 token,需要拥有画板的编辑权限 |
--idempotent-token | 否 | 幂等 token,最少 10 个字符,建议用时间戳 + 场景标识拼接(如1744800000-board-1)。同一次逻辑更新只生成一次,重试时须原样复用,切勿在每次重试时重新生成,否则会重复写入 |
--overwrite | 否 | 带上则覆盖更新(先删除画板所有现有内容再写入);省略则为增量追加。默认 false |
--source | 是 | 输入画板内容,支持@path从文件读取,或-从 stdin 读取 |
--input_format | 否 | 输入格式:raw、plantuml、mermaid、svg,默认为raw |
身份默认使用--as user;仅当需要以应用身份上传时使用--as bot。若写入的是非空已有画板并需要 overwrite,先确认会整板重建。
渲染前自查清单
结合 dsl.md 与 treemap 场景特征,提交前逐项检查:
- 不同顶层分类用了不同颜色?同分类下的叶子节点样式完全一致?
- 外层分类矩形浅色背景、内层叶子节点白色填充 + 分组色边框?
- 所有分类矩形有边框(
borderWidth: 2)?文字在背景上清晰可读? - 分类标签文字未被叶子矩形遮挡(子矩形从
y + 35开始)? - 叶子节点标签都包含数值(
{{LABEL}} ({{VALUE}}))? - 面积比例是否由脚本按数值严格计算,而非手工近似?
八、常见陷阱与规避
treemap.md 列出了四类高发问题,按严重程度排序:
- 父标签被子矩形遮挡(最严重):子矩形必须从
y + 35(相对父矩形顶部)开始放置,为父分类标签留出空间; - 分类标签不可见:分类标签 text 节点必须在其子矩形 rect 节点之前添加。这与 skills/lark-whiteboard/elements/layout.md 中的图层规则一致——数组中越靠后的节点层级越高,若 text 写在 rect 之后会被矩形盖住;
- 面积比例不正确:必须用脚本预先计算比例,不要心算——多层嵌套下心算必然产生累积误差;
- 缺少配色区分:不同顶层分类必须用不同背景色(从色板选取),所有子节点继承对应色系,否则读者无法快速识别分组边界。
九、与其他场景的对照与适用边界
treemap 属于 DSL 脚本构建类场景,与其并列的还有柱状图(bar-chart)、折线图(line-chart)等需要几何坐标计算的图表(见 dsl.md)。它们共同遵循"脚本生成 JSON → whiteboard-cli 渲染 →+update --input_format raw写入"的链路。
需要区分的是:思维导图、时序图、类图、饼图、甘特图走 skills/lark-whiteboard/routes/mermaid.md 路径;含 @用户提及或图片的内容走 skills/lark-whiteboard/routes/dsl.md 的 mention / photo-showcase 指南;treemap 则始终属于"其他图表"中的 DSL 脚本生成类别。
十、总结
矩形树图的本质是"数值驱动面积"的可视化:数据决定了比例,比例决定了坐标。正确实践是:将数据组织为data树 → 用.cjs脚本按 Slice-and-Dice 规则递归计算坐标(奇数层切 width、偶数层切 height、每层扣除 35px 标签空间)→ 输出diagram.json→ 用npx -y @larksuite/whiteboard-cli@^0.2.13渲染预览 → 经--to openapi转换后 pipe 给lark-cli whiteboard +update --input_format raw写入画板。Agent 或开发者只需维护data树,其余坐标与矩形面积全部自动递归计算,即可在飞书画板中得到面积准确、层级清晰、配色分组的专业矩形树图。
进一步阅读:场景约束与脚本模板见 skills/lark-whiteboard/scenes/treemap.md;DSL 节点类型与字段见 skills/lark-whiteboard/elements/schema.md;布局原则见 skills/lark-whiteboard/elements/layout.md;配色体系见 skills/lark-whiteboard/elements/style.md;完整创作/编辑工作流见 skills/lark-whiteboard/references/lark-whiteboard-workflow.md。
【免费下载链接】cliThe official Lark/飞书 CLI tool, maintained by the larksuite team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, and more, with 200+ commands and 20+ AI Agent Skills.项目地址: https://gitcode.com/gh_mirrors/cli414/cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考