D2 v0.1.4 交互式图表:tooltip 与 link 关键字、附录渲染与布局引擎选项详解
【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2
D2 是一门把文本描述编译成图表的现代图表脚本语言。v0.1.4 是 D2 发展历程中里程碑式的一个版本:它首次引入交互式图表能力——图形可以挂载tooltip(悬停提示)与link(点击跳转)属性;同时把此前仅限图片的width/height关键字开放给所有非容器图形,并全面暴露各布局引擎的可配置选项。本文以该版本的官方变更日志(ci/release/changelogs/v0.1.4.md)为主线,结合当前仓库源码,逐一拆解这些能力的用法、底层实现与升级注意事项,读完即可在自己的 D2 脚本和库调用中直接落地这些新特性。
一、交互式图表:让静态图形“动”起来
v0.1.4 的核心卖点是交互式图表:D2 图形现在可以设置tooltip与link。其中:
tooltip允许读者把鼠标悬停在图形上查看更多信息;link允许读者点击图形跳转到外部链接。
这一个小改动打开了巨大的想象空间——例如把企业内部 Wiki 通过图表互相串联,形成可点击导航的知识图谱。当某个图形带有可悬停的 tooltip 或可点击的链接时,D2 会在图形上渲染一个图标作为提示。
1.1 关键字在语法层面的定位
在 D2 语法树中,tooltip与link被定义为一等保留关键字。查看 d2ast/keywords.go 可以看到它们同时出现在两个集合中:
SimpleReservedKeywords("tooltip"、"link"等):它们可以直接以key: value的形式附着在图形上;CompositeReservedKeywords中的"tooltip":意味着 tooltip 还可以承载复合对象(例如多行文本内容)。
server: { tooltip: "生产环境主节点,承载订单服务" link: https://wiki.example.com/services/order } database: { label: "订单数据库" tooltip: | 只读副本 3 个 主从延迟 < 500ms link: https://grafana.example.com/d/orders }1.2 从源码看 tooltip/link 的数据结构
交互属性最终会被编译进目标图(target diagram)的 Shape 结构中。在 d2target/d2target.go 中可以看到 Shape 上挂着三个相关字段:
Tooltip string `json:"tooltip"` Link string `json:"link"` PrettyLink string `json:"prettyLink,omitempty"`Tooltip是悬停时展示的原始文本(支持 Markdown);Link是点击跳转的完整 URL;PrettyLink是链接的展示文本,用于在非交互场景下“尽量美观地”呈现这个链接,因为静态导出格式无法点击。
而 tooltip 的展示位置由TooltipPosition字段控制(d2target/d2target.go)。合法取值在 d2ast/keywords.go 的TooltipPositionsArray中定义:
var TooltipPositionsArray = []string{ "top-left", "top-center", "top-right", "center-left", "center-right", "bottom-left", "bottom-center", "bottom-right", }在 SVG 等交互格式中,D2 会为带位置的 tooltip 生成一个可见的气泡框:在 d2renderers/d2scenebuild/tooltip.go 中,buildPositionedTooltip会先按 Markdown 排版 tooltip 内容,再根据TooltipPosition计算气泡的坐标(调用d2target.CalculateTooltipPosition),最后绘制圆角背景框、指向图形的尾巴(tail)以及 Markdown 文本内容,并把这些节点追加到所有图形之后以保证它们始终处于最上层。
二、静态导出格式的附录(Appendix)机制
交互特性在 PNG 这类静态导出格式上显然无法工作。因此 v0.1.4 规定:当导出到静态格式时,tooltip 与 link 会被自动收录进一个“附录”(appendix)中——每个交互属性对应一个带编号的图标,正文下方多出一块编号清单,读者虽然无法悬停点击,但依然能看到每条元数据。
2.1 附录的源码实现
附录逻辑集中在 d2renderers/d2scenebuild/appendix.go 中,核心流程分三步:
- 收集(addAppendixItem):
addAppendixItem记录每个无法交互呈现的元数据(连接线的 tooltip、Markdown 链接标题等),并为附录项数量与字符串总字节数设置预算上限(受LinkBudget约束); - 测量(measureAppendix):用文本测量工具计算每一行的宽高,逐步累加出附录整体占用的高度,并通过
expandViewBox在导出时把视口(viewBox)向下扩展; - 绘制(buildAppendix):在正文下方先画一条分隔线,然后为每一条附录渲染“编号圆形图标 + 文本”的行。
对应地,d2renderers/d2svg/appendix/appendix.go 负责在 SVG 序列化阶段把附录真正写进输出文档。
2.2 附录图标的位置计算
对于正文中的图形,D2 会在图形右上角绘制白色圆形编号徽标(tooltip 与 link 同时存在时是两个并排徽标)。appendixIconCenters(appendix.go)会根据图形几何类型做特判:例如圆形、椭圆、菱形、人形、云、圆柱等图形在同时有两个徽标时会把链接徽标挪到图形内部;step、hexagon、queue、page 等形状则把徽标对齐到顶部。
例如一个同时带 tooltip 和 link 的矩形,导出 PNG 后会形如:
service: "订单服务" { tooltip: "点击查看监控面板" link: https://grafana.example.com/d/orders }导出 PNG 后,图形右上角会出现① ②两个徽标,图下方附录区则列出:
① 订单服务 tooltip: 点击查看监控面板 ② 订单服务 link: https://grafana.example.com/d/orders三、width 与 height:不再只是图片的专属
在 v0.1.4 之前,width和height是 D2 关键字,但只对图片(icon)生效。本次更新把这两个关键字开放给了所有非容器图形:
box: { width: 200 height: 100 label: "固定尺寸的矩形" } person: "固定尺寸的人形" { width: 120 height: 120 }也就是说,任意非容器形状(矩形、椭圆、人形、圆柱等)现在都可以通过width/height显式控制自身尺寸,这让精确排版成为可能。注意容器(container,即内部还有子图形的形状)仍然不适用这两个关键字,因为它们的大小由内部内容决定。
这一改动与同版本的另一项修复互为表里——图形边框宽度(stroke width)被纳入图表整体包围盒(bounding box)的计算,详见下文 Bugfix 部分。
四、布局引擎选项全面开放
v0.1.4 还给了用户“更多控制布局的权力”:所有布局引擎的配置项都被暴露出来。D2 在无任何输入的情况下会为每个布局引擎设置合理的默认值,因此这些配置属于面向高级用户的特性——需要额外控制时才使用。
4.1 dagre 布局引擎的可配置选项
dagre 是 D2 默认的布局引擎,其配置结构定义在 d2layouts/d2dagrelayout/layout.go:
type ConfigurableOpts struct { NodeSep int `json:"nodesep"` EdgeSep int `json:"edgesep"` } var DefaultOpts = ConfigurableOpts{ NodeSep: 60, EdgeSep: 20, }| 选项 | 默认值 | 含义 |
|---|---|---|
NodeSep | 60 | 同一层级内相邻节点之间的间距(水平方向) |
EdgeSep | 20 | 边与边之间的间距(水平方向) |
4.2 ELK 布局引擎的可配置选项
ELK 布局引擎的配置结构定义在 d2layouts/d2elklayout/layout.go:
type ConfigurableOpts struct { Algorithm string `json:"elk.algorithm,omitempty"` NodeSpacing int `json:"spacing.nodeNodeBetweenLayers,omitempty"` Padding string `json:"elk.padding,omitempty"` EdgeNodeSpacing int `json:"spacing.edgeNodeBetweenLayers,omitempty"` SelfLoopSpacing int `json:"elk.spacing.nodeSelfLoop"` } var DefaultOpts = ConfigurableOpts{ Algorithm: "layered", NodeSpacing: 70.0, Padding: "[top=50,left=50,bottom=50,right=50]", EdgeNodeSpacing: 40.0, SelfLoopSpacing: 50.0, }| 选项 | 默认值 | 含义 |
|---|---|---|
Algorithm | "layered" | ELK 布局算法,默认分层布局 |
NodeSpacing | 70 | 层内节点间距 |
Padding | "[top=50,left=50,bottom=50,right=50]" | 容器内边距 |
EdgeNodeSpacing | 40 | 边与节点之间的间距 |
SelfLoopSpacing | 50 | 自环(self-loop)边的间距 |
从源码看,这些用户选项会与引擎内部固定参数合并:newRootLayoutOptions(layout.go)在根容器上固定Thoroughness: 8、EdgeEdgeBetweenLayersSpacing: 50、HierarchyHandling: "INCLUDE_CHILDREN"、CycleBreakingStrategy: "GREEDY_MODEL_ORDER"等 ELK 原生参数;而newContainerLayoutOptions(layout.go)在子容器上会关闭 model-order 处理——源码注释明确说明这是为了避免 ELK 0.12 在复合子图上崩溃(这也与 v0.1.4 修复的 ELK panic 类问题一脉相承)。此外,若用户未显式设置SelfLoopSpacing,布局器会根据图中自环标签的最大尺寸动态放大该间距,防止自环标签互相重叠(layout.go)。
4.3 库调用方式的 Breaking Change
把布局选项暴露出来的代价是一个破坏性变更:当你把 D2 当作 Go 库使用时,d2dagrelayout.Layout与d2elklayout.Layout现在都接受第三个参数(options)。如果希望保持默认行为,请改用各自包下的DefaultLayout:
// 旧写法(v0.1.4 之前) // import "github.com/d2lang/d2/d2layouts/d2dagrelayout" // err := d2dagrelayout.Layout(ctx, graph) // 新写法(v0.1.4 起) import "github.com/d2lang/d2/d2layouts/d2dagrelayout" // 方式一:使用默认选项(推荐) err := d2dagrelayout.DefaultLayout(ctx, graph) // 方式二:自定义选项 opts := d2dagrelayout.ConfigurableOpts{ NodeSep: 80, EdgeSep: 30, } err := d2dagrelayout.Layout(ctx, graph, &opts)ELK 同理:
import "github.com/d2lang/d2/d2layouts/d2elklayout" // 默认 err := d2elklayout.DefaultLayout(ctx, graph) // 自定义 opts := d2elklayout.ConfigurableOpts{ Algorithm: "layered", NodeSpacing: 90, Padding: "[top=60,left=60,bottom=60,right=60]", EdgeNodeSpacing: 50, SelfLoopSpacing: 60, } err := d2elklayout.Layout(ctx, graph, &opts)从实现看,DefaultLayout内部就是Layout(ctx, g, nil)——传入 nil 时布局器会回退到包级DefaultOpts(d2dagrelayout/layout.go、d2elklayout/layout.go),因此两种写法语义完全一致。
五、改进与 Bugfix 一览
5.1 改进:Watch 模式自适应缩放
v0.1.4 起,watch 模式(d2 --watch)下渲染内容会自动适配屏幕,不再出现图形超出可视区域需要手动滚动/缩放的尴尬。这对使用 watch 模式做交互式编辑的用户是实打实的体验提升。
5.2 本次修复的 Bug
| 问题 | 说明 |
|---|---|
class与table空表头渲染错误 | 修复了类图与表格在没有表头时的渲染问题 |
sql_table无列时渲染错误 | 修复了 SQL 表不包含任何列时的渲染问题 |
| 包围盒未计入描边宽度 | 图表整体 bounding box 现在会为 stroke width 预留空间,避免描边被裁切 |
near关键字位置不受控 | near: top-center这类常量此前在容器上会导致布局错乱或直接报错;现在限制near常量只能在合法位置使用,并给出友好错误信息而非随机报错。相关合法取值定义在 d2ast/keywords.go 的NearConstantsArray中(top-left至bottom-right共 8 个) |
| ELK 渲染空标签图片时 panic | 修复了带空标签的图片走 ELK 布局时程序崩溃的问题 |
需要说明的是:由于near关键字值必须是合法的 D2 关键字(其常量集合见 d2ast/keywords.go),在容器上使用这些常量会破坏容器内子图形的约束,因此 v0.1.4 在编译期就拦截这类写法并输出清晰的错误提示。
六、升级到 v0.1.4 的注意事项
综合以上内容,从旧版本升级到 v0.1.4 时需要注意:
- 库调用签名变化(必须处理):如果你的项目直接以 Go 库方式调用
d2dagrelayout.Layout或d2elklayout.Layout,需要为调用补上第三个参数,或直接改用DefaultLayout保持默认行为(见 4.3 节示例)。 near使用受限:如果你之前曾在容器上使用near: top-center之类的写法,请改为在合法对象上使用,否则会得到编译错误(这是刻意为之,为的是提供确定性行为与友好报错)。- 静态导出会附带附录:导出 PNG/GIF/PDF/PPTX 等静态格式时,所有 tooltip 与 link 都会自动生成附录行与编号徽标,这是预期行为而非缺陷;若完全不需要这些元数据,就不要在脚本中写
tooltip/link。
结语
v0.1.4 通过tooltip/link把 D2 从“只能看”的静态图表推进到“可以悬停、可以点击”的交互式图表,同时借助附录机制保证了静态导出场景下信息不丢失;width/height的开放与布局引擎选项的暴露,则让高级用户获得了精确排版与深度控制布局的能力。这一版本确立的架构——关键字在 d2ast/keywords.go 中定义、交互数据承载于 d2target/d2target.go 的 Shape 结构、静态附录渲染在 d2renderers/d2scenebuild/appendix.go 与 d2renderers/d2svg/appendix/appendix.go 中实现——至今仍是 D2 交互与静态导出两大能力的基础。如果你正在做企业内部文档体系或需要精确控制版式的图表,这个版本引入的能力值得直接上手。
【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考