要让 Mac 上的 Graphviz 层级图像缩进大纲一样易读,请为每个节点指定行和缩进坐标,然后使用 neato -n2 渲染。这样既能保留父子边,也能让你控制阅读顺序。
本指南将创建一个包含六个节点的示例,并导出为 PDF。目标很明确:每行一个节点,后代紧接在其父节点下方,且同一深度的节点缩进相同。Graphviz 在其 -n 命令行选项中介绍了固定位置渲染。
为什么普通的 Graphviz 树看起来不像大纲?
层级描述的是关系;缩进大纲还规定了阅读顺序。在本例中,根节点包含两个分支,第一个分支的后代必须排在第二个分支之前。期望的顺序如下:
Node R
Node A
Node C
Node D
Node B
Node E
图中的关系是根节点到 A、根节点到 B、A 到 C、A 到 D,以及 B 到 E。D 和 B 之间没有关系,只因为 B 出现在下一行。DOT 使用 -> 运算符表示有向边;节点声明与节点的显示属性是语言中彼此独立的部分。请参阅官方的 DOT 语言参考。
Graphviz 的 dot 引擎会生成分层或分层式布局。它会尝试减少交叉和边长,同时让边朝着共同方向延伸。这一目标适合绘制分支图,但并未规定上方示例所示的每个节点占一行的大纲布局。请参阅 dot 布局文档。
一些常见属性只能解决部分问题:
| 设置 | 文档所述用途 | 未解决的问题 |
|---|---|---|
rankdir=LR |
将布局方向改为从左到右 | 未指定纵向大纲顺序 |
rank=same |
将子图中的节点放在同一层级 | 未为每个节点指定独立的大纲行 |
ordering=out |
约束出边按照输入顺序从左到右排列 | 未定义基于深度的缩进和行坐标 |
这些边界与 rankdir、rank 和 ordering 的官方参考文档一致。对于本文要求的精确行布局,显式坐标比组合这些属性更直接。
如何在 Mac 上准备 Graphviz?
打开 Terminal。如果你已经在使用 Homebrew,请运行以下命令安装 Graphviz。官方 Graphviz 下载页面列出了这种 Mac 安装方式:
brew install graphviz
如果你使用的是 MacPorts,同一下载页面列出了 sudo port install graphviz。选择你已经在用的包管理器。如果两者都尚未安装,请先通过该页面中的相应链接完成安装,再继续本指南。
检查渲染器是否可用:
neato -V
-V 选项会输出 Graphviz 版本信息。比较不同电脑上的结果时,请记录输出的版本;本文不要求显示某个特定版本号。Graphviz 在其命令行参考中介绍了此选项。
如何创建缩进层级图?
以下完整的 shell 代码块会创建一个新的工作文件夹,并在其中写入 DOT 文件。在 Terminal 中运行;完成后会打印文件夹路径。带引号的 heredoc 分隔符会按原样保留 DOT 文本。
outline_dir=$(mktemp -d "$HOME/graphviz-outline.XXXXXX")
if [ -n "$outline_dir" ] && cd "$outline_dir"; then
cat > hierarchy.dot <<'DOT'
digraph Hierarchy {
graph [splines=ortho, bgcolor=white];
node [
shape=box,
width=1.2,
height=0.35,
fixedsize=true,
fontname="Courier",
fontsize=12
];
r [label="Node R", pos="100,300"];
a [label="Node A", pos="136,252"];
c [label="Node C", pos="172,204"];
d [label="Node D", pos="172,156"];
b [label="Node B", pos="136,108"];
e [label="Node E", pos="172,60"];
r -> a;
r -> b;
a -> c;
a -> d;
b -> e;
}
DOT
pwd
fi
节点位置定义了大纲布局。每往下一行,纵向移动 48 个点;每增加一层深度,横向移动 36 个点。这些数字只是示例间距,并非 Graphviz 默认值。pos 文档说明节点位置指的是节点中心;与 -n 一起使用时,neato 会将位置解释为点。
| 节点 | 深度 | 行,从零开始 | 中心位置 |
|---|---|---|---|
| Node R | 0 | 0 | 100,300 |
| Node A | 1 | 1 | 136,252 |
| Node C | 2 | 2 | 172,204 |
| Node D | 2 | 3 | 172,156 |
| Node B | 1 | 4 | 136,108 |
| Node E | 2 | 5 | 172,60 |
在此布局中,坐标规则是 x = 100 + 36 × depth 和 y = 300 − 48 × row。注意从 Node D 回到 Node B:行号增加了,但深度降低了。正是这一次变化,让图看起来像大纲,而不是一条链。
所有方框尺寸相同,因此中心坐标相同的节点,其左边缘也会对齐。长度相同的示例标签便于比较文本位置。使用 fixedsize=true 时,Graphviz 会采用指定的节点尺寸,而不会为了容纳内容而扩大方框;标签过大会产生警告。请参阅 fixedsize 参考文档。
如何渲染并检查结果?
在同一个 Terminal 会话和工作文件夹中运行:
neato -n2 -Tpdf hierarchy.dot -o hierarchy.pdf
这里,-n2 会保留提供的节点布局,不进行避免重叠的调整,并为未指定自身位置的边计算路径。因此,每个节点都需要有一个 pos 值。-Tpdf 和 -o 选项分别用于选择 PDF 输出格式和输出位置。这些行为见于命令行参考。
在 Finder 中打开工作文件夹,再打开 hierarchy.pdf。根据 Graphviz 的 PDF 输出页面,PDF 输出要求 Graphviz 构建版本包含 Cairo 渲染器。本例已根据文档进行核对,但由于研究环境中没有 Graphviz,因此未在此处实际渲染测试。
请分别检查以下三点:
- 从上往下阅读:R、A、C、D、B、E 应依次占据各行。
- 比较缩进:A 和 B 应对齐;C、D 和 E 应再向右缩进一级并彼此对齐。
- 沿边检查关系:R 连接到 A 和 B;A 连接到 C 和 D;B 连接到 E。
前两项检查坐标布局,第三项检查图中的实际关系。将这些检查分开,有助于发现看似整齐、实际却包含错误父子边的图。
连接线会和文件浏览器中的树完全一样吗?
节点行和缩进是明确指定的;连接线的路径仍由系统计算。本例指定了 splines=ortho,会让边沿轴对齐线段布线。Graphviz 文档还指出,这种布线方式不支持端口,因此添加罗盘端口并不是强制特定折线样式的有据可依方法。请参阅 splines 参考文档。
因此,这种构造能生成带自动直角连接线的缩进层级图,但不会规定一条共用的垂直主干,并精确放置各分支的连接点。如果连接线几何形状至关重要,就需要额外构造并显式指定边路径;仅靠 splines=ortho 无法表达这一要求。
如果想要不显示连接线的整洁大纲,请将图设置改为 splines=none,然后重新渲染。DOT 文件仍然包含父子边,但渲染器会省略这些边的线条。这是同一 splines 参考文档中介绍的渲染选项。
如何在不改变含义的情况下调整示例?
先独立于边列表写下预期的阅读顺序。先访问父节点,再访问其第一个子节点及其后代,然后才转到下一个子节点。为序列中的每个节点分配一行,再根据深度分配缩进。
例如,要在 Node D 后面为 Node A 添加另一个子节点,请在阅读顺序中将它插入 Node B 之前。为它指定深度 2,将后续各行下移,并添加一条从 A 指向新节点的边。现有父子关系不会仅仅因为行号改变而发生变化。
编辑后的版本看起来不对时,请按以下顺序排查:
- **图形扩散成了另一种布局:**确认命令仍然使用
neato -n2。本例依赖提供的位置,而非普通的自动布局。 - **渲染提示缺少位置:**检查每个节点 ID,包括只出现在边语句中的 ID。在这种工作流程中,每个节点都需要有带位置的声明。
- **标签超出方框:**增大所有方框的尺寸,或缩短标签。固定尺寸的方框不会自动扩展。
- **增大方框后各行发生重叠:**增大行间距。所选渲染模式会保留位置,而不会修正重叠。
- **分支连接到了错误的节点:**修正边语句。移动方框无法修复错误的关系。
缺少位置和发生重叠的情况符合 Graphviz 文档中关于 -n2 行为的说明;标签问题符合其 fixedsize 行为。对于这个手动定位的示例,增大间距是相应的设计调整。
将层级的边列表作为关系的依据,将坐标表作为显示布局的依据。对于 Mac 桌面上的小型示意图,这种分工便于维护:层级关系改变时调整边;阅读顺序改变时调整行;标签需要更多空间时调整间距。