
Graphviz(Graph Visualization) 是1990年代初诞生 于 AT&T Bell 实验室的一个开源的(EPL)、跨平台的脚本自动化绘图软件工具。使用 dot 脚本 语言进行绘图,这是一种 DSL (Domain Special Language) 领域特定脚本语言。编写脚本文件, 然后使用 Layout 布局引擎解析这个 Script File 脚本文件完成自动化布局渲染。Graphviz 提供了 C/CPP、Java、Python、PHP 等语言的 API。VSCode 中可以通过 Markdown 或者 AsciiDoc 文档嵌入 PlantUML 脚本。也可以使用 PlangUML 命令行调用 dot 程序,或者通过 -graphvizdot 参数指定的路径来调用 dot 程序。以下是 Graphviz 图表基本结构示意图


Markdown 和 AsciiDoc 文档中都可以使用 plantuml 代码块来插入绘图脚本,像以下这样。 Markdown 还可以使用 uml 代码块,AsciiDoc 还可以使用 graphviz 代码块。VSCode 中可以使用 PlantUML 或者 AsciiDoc 插件来预览图像。具体使用什么代码块标记取决于脚本 内容的编写,同时 PlantUML 可以调用 Graphviz 命令处理 dot 脚本。AsciiDoc 文档中的 代码块标记会用来构造 Kroki 服务器的请求地址,比如:
https://kroki.io/graphviz/svg 对应的就是 Graphviz 脚本转图服务 (SVG);
https://kroki.io/plantuml/svg 对应的就是 PlantUML 脚本转图服务 (SVG);
Graphviz 脚本语法结构主要由代码块、语句、标识符、注释等几部分组成,具体定义可以参考官方 定义的 DOT Language 文法规则:
代码块: 位于花括号 {} 内的语句即构成代码块;
语句:以分号 ; 结尾,主要分为代码块,节点,连线,属性四种语句类型;
实体对象标识符:除了特殊字符外的所有字符都可以用于标识符,如数字,中英文字符串等;
C 风格注释: // 表示单行注释,/…/ 表示多行注释。
Graphviz 图实体主要分两类,有向图和无向图,另外可以使用子图、图集两种形式组织:
graph: digraph {…} 定义有向图;graph {…} 定义无向图;→ 或者 -- 连线;
subgraph:subgraph {…} 定义子图;
cluster: subgraph cluster_xxx {…} 代码块的 ID 使用 cluster 前缀表示图集,子图的一种组织形式,会被线框包裹。
默认布局(layout=dot)中有几个重要的属性,性决定了图的尺寸和节点朝向:
nodesep 同一个 rank 中的相邻节点的最小距离,单位为英寸(=2.54 cm)。
ranksep 相邻 rank 之间的距离,即与 rankdir 方向垂直的方向排列的节点组间距;
rankdir 可设置 LR (left to right) or RL,或者 TB (top to bottom) or BT;
rank 设置一组节点的 ranking (秩序):"same", "min", "source", "max", "sink";
group 设置相同分组的节点间连线后,可以避免被其它线条跨过。
ratio 设置绘图对象的宽高比,与 rankdir 方向无关,情况有多种:
Case 1. ratio 使用默认值,由布局引擎自动处理图形的大小。否则等比例伸缩以匹配画布尺寸。
Case 2a. 浮点值 ratio=x 表示绘图 x 倍的高度比宽度(height/width)。
Case 2b. 填充模式 ratio=fill 并且 size="x, y",那么将伸缩图像以满足 y/x 高宽比例。
Case 2c. 紧密模式 ratio=compress 并且 size="x, y",初始布局收缩以适应边界盒。
Case 2d. 自动模式 ratio=auto 并且设置了 page 属性,内容超出纸张时忽略 size 属性。
通过设置 size 属性,可以约束边界盒(bounding box)尺寸,这是图像结果输出的最大尺寸。
Graphviz 布局会自动调整图形大小以适用文本内容尺寸,如果不希望这种默认行为,比如需要 严格的图形对齐,就可以使用 fixedsize=true 属性来禁止自动调整,让图形尺寸严格按照 width 和 height 属性指定的数值呈现。 Graphviz library manual 给出了 根据不同算法实现的各种布局引擎的说明,这些布局类型对应了同名的命令行工具: dot、 neato、 fdp、 sfdp、 twopi、 circo、 osage。 Graphviz 默认使用 layout = dot 布局,此布局可以胜任实现中各种常见的流程图绘制。此布局 使用轴向 rankdir 来排列那些已经使用 edge 连接的节点,对于没有连接的节点将默认在最小的 ranking 等级,也就是最先绘制到画布上。Ranking 就是排序等级,就是节点绘制的优先级别。这是 一个全局属性,只能在 graph 对象上使用,子图或图集不能设置 rankdir 等相关属性。
所谓 rank="same" 就是和 rankdir 轴线垂直的排列方向,比如 LR 轴线朝向的就是竖直排列。 rankdir 属性的意思是按指定朝向排列子图,因此相同 rank 的节点就与 rankdir 轴向垂直排列。 比如下图中设置的 {rank="max"; C; D;} 和 {rank="same" A B H},保持两组节点在竖直 方向上对齐。差别在于,min 或者 max 在表示一组节点具有相同的 ranking 等级的同时,还将这些节点 安排在最优先绘制等级、最后绘制等级。因为默认的 rankdir 方向就是 TD (To to Down),因此, 最小 ranking 等级(最优先)表示在最上层位置绘制。而最大 ranking 等级(最后绘制)的节点将绘制 在最下方。注意,那些没有连接的节点,相当于独立的图(island),它们会默认按小 ranking 等级处理。 下图中,由于 A B 相连,默认是归属相邻 rank 排列,但是由于显式设置它们为相同 rank,因此原本应该 显示在 A 下方的 B 节点就提升一个秩序进行渲染。而原本 H 的等级更大,也因为 rank="same" 改变 了其原来的排列。从而导致整个 graph 的排版都产生非常大的变化。使用 rank = "sink" 可以像它的 名称所暗示,将会下沉到 max 之后。有点费解的是 rank="source",这种方式将节点都放置在最小秩上。

由于 ranking 排序方式不能完全保证图表对象的排列总是符合需求,所以在出现意外情况下,可以使用一些 隐藏节点、边来辅助布局,例如以下是一个二叉树的绘制,通过 style=invis 让辅助排版的节点或边隐藏。



VS Code 中安装 AsciiDoc 扩展,使用 https://kroki.io/[Kroki] 服务器渲染
Graphviz 脚本。在处理 <<sfdp>> 布局时可能会遇到报错,输出带有提示信息的 SVG 图像, dot 脚本生成的图像可能是有效的,但是由于错误信息的存在出不能按正常 SVG 图像处理。错误 可能是因为使用了服务器中没有集成的 triangulation library 模块引起的。下载 Kroki 最新版本在本地运行,就没有这样了问题。 Error 400: remove_overlap: Graphviz not built with triangulation library 还好 AsciiDoc 考虑到 Web 服务器配置,用户可以在文档开头配置一个文档属性来指定 Graphviz 转图的 Web 服务器,比如这样 `:kroki-server-url: http://localhost:8000/`。当启动 文档预览功能时,AsciiDoc Kroki 模块就会激活并读取文档头中定义的属性,以启用用户配置。 以下使用 PlantUML 脚本的 `testdot` 查询其软件的版本信息。 [source,bash] ---- http://localhost:8000/plantuml/svg/eNorSS0uSckvAQAMHgMI PlantUML version 1.2025.0 (Wed Jan 08 01:35:36 CST 2025) Dot version: dot - graphviz version 2.44.1 (20200629.0846) Installation seems OK. File generation OK https://kroki.io/plantuml/svg/eNorSS0uSckvAQAMHgMI PlantUML version 1.2024.1 (Thu Feb 01 18:18:54 UTC 2024) Dot version: dot - graphviz version 9.0.0 (20230911.1827) Installation seems OK. File generation OK ----
VS Code 中安装 AsciiDoc 扩展,使用 Kroki 服务器渲染 Graphviz 脚本。在处理 sfdp 布局时可能会遇到报错,输出带有提示信息的 SVG 图像, dot 脚本生成的图像可能是有效的,但是由于错误信息的存在出不能按正常 SVG 图像处理。错误 可能是因为使用了服务器中没有集成的 triangulation library 模块引起的。下载 Kroki 最新版本在本地运行,就没有这样了问题。
Error 400: remove_overlap: Graphviz not built with triangulation library 还好 AsciiDoc 考虑到 Web 服务器配置,用户可以在文档开头配置一个文档属性来指定 Graphviz 转图的 Web 服务器,比如这样 :kroki-server-url: http://localhost:8000/。当启动 文档预览功能时,AsciiDoc Kroki 模块就会激活并读取文档头中定义的属性,以启用用户配置。 以下使用 PlantUML 脚本的 testdot 查询其软件的版本信息。 http://localhost:8000/plantuml/svg/eNorSS0uSckvAQAMHgMI PlantUML version 1.2025.0 (Wed Jan 08 01:35:36 CST 2025) Dot version: dot - graphviz version 2.44.1 (20200629.0846) Installation seems OK. File generation OK https://kroki.io/plantuml/svg/eNorSS0uSckvAQAMHgMI PlantUML version 1.2024.1 (Thu Feb 01 18:18:54 UTC 2024) Dot version: dot - graphviz version 9.0.0 (20230911.1827) Installation seems OK. File generation OK 默认布局算法是 dot algorithm,基于秩序的布局(ranked layout),也就是以 dege 连接方向 排列的布局,这个方向称为轴心方向(rankdir)。通过设置节点的连接状态以及节点的 rank 类型,就可以控制不同的节点在同一个切面上排列、排列到轴心的起始或结尾。特别适用于网格、树状层级 等常见排版需求。相关算法贡献者:Sugiyama et al。 第二个是基于弹力的 neato 布局,算法来自 Kamada、Kawai。首次由 Kruskal、Seely 以不同的形式 引入。此模型假设了一个弹簧模型,弹簧两端点拥有理想不受限的长度,两点的连接表示为一个函数,就是图表的边。 此系统尝试保持最小化的能量状态。即密集的节点会彼此推开,稀疏的节点会彼此牵引靠近。此模型有一个最大的 不同之处,就是提供了 pos 属性用于指定节点、边的坐标,还可以为 3D 工具导出到 VRML 信息文件。 需要设置为三维坐标模式 dimen = 3。 第三个布局 fdp 与 neato 相似,同样假设了一个目虚拟的物理模型(virtual physical model), 算法由 Fruchterman、Reingold 提出。使用有电磁排斥势(electrical repulsive force)的弹性边 连接节点。系统目标是最小化弹性力,而不像 neato 那样最小化能量,且支持图集 (cluster subgraphs)。 算法的能量梯度(energy gradients)比较过程可以通过 epsilon 属性进行微调,最小值 0.001, 当能量等级小到此值时就完成布局算法。其值作用相当于弹力强度,值越大得到的布局越紧密。 第四个布局引擎是 sfdp,还是力定向布局,Scalable Force-Directed Placement, 增加前缀词“可伸缩”表示能更好地处理大规模的图表,相对 fdp 布局增加了多级细调方法。 sfdp 也和 neato 一样不支持(cluster subgraphs),也不能像 fdp 那样 支持模型的 edge lengths,也不像 twopi 支持 weight。但是 作为才力定向布局,都可以为图表设置弹力系数常量 K - Spring constant。 第五个是放射线布局引擎 twopi,模型概念最简单,按照 Wills 的算法,取一个 center 节点作为放射布局的中心,由此向四周生成树(spanning tree),按照节点连接关系递归下去。此算法 归纳生成树的每个分支所占用的角度切片,以确保树在每个环上有足够的空间。目前不支持 clusters。 最后是环形布局 circo, 算法基于 J. M. Six and I. G. Tollis, “A Framework for Circular Drawings of Networks,”。节点围绕一个圆心进行双向连接。 算法会尝试最小化边的交叉情况,并且派生树将以 twopi 样式排列。

Graphviz 还可以使用 HTML 代码风格的标签,这里说 HTML-like labels 是因为这些标签只是在形式 上模拟 HTML 标签,但它们不是,只提供有限属性支持,主要是使用 TABLE (注意大小写要一致) 表格布局服务 Graphviz 绘图之需要。比如不能随意使用 HTML 中常用的 style 属性来设置 CSS 样式, 只支持有限的样式值。比如,ROUNDED 将填充色圆角化处理,RADIAL 为辐射式底色填充,一般不搭配 边框线(BORDER)使用。辐射填充色配合 bgcolor 以及 gradientangle 属性使用。 标签属性及使用参考 HTML-Like Labels 以及 HTML-Like Label Examples。
除了 HTML 风格的标签之外,还提供了一种节点名为 Record-based Nodes,也就是 使用花括号包裹的一组标签内容,与 HTML 风格的标签使用尖括号对应,只是花括号表达形式上更精简。
节点端口(Node Ports)是为了实现节点、标签特定区域与其它节点的连接而引入的功能。有两种端口设置, 用户设置的端口使用 HTML-like 标签中的 port 属性定义,或者在花括号表达形式内使用尖括号 定义,并且需要在单元格内容开始位置定义。另一种是 Graphviz 内置的 8 个方位点(compass points), 分别使用 north, south, east, west 首字母组合表达:n, ne, e, se, s, sw, w, nw。
使用这些端口的形式也有两种,比如 a → b [tailport=se] 或者 a → b:se。这两种形式一是 通过 edge 属性:headport 和 tailport 指定。另一种是使用端口后缀表达形式: node:port 或者 node:compass:port。默认的方位点是与连接线所在侧的一点。
标签记录产生的图形在连接时,并不使用直线,即使设置 splines = false 或使用就近端口。

给连线设置 dir="none" 属性可以隐藏连接两端的箭头,给连线设置 style="invis" 可以隐藏线条。 Graphviz 提供了丰富的箭头类型,可以通过 arrowType 类型的属性设置箭头类型,也就是设置 Edge 对象的 arrowhead 以及 arrowtail 属性。 注意,对于 A → B 这样的连接关系中,B 所在的箭头对应 arrowhead,连线的起点才是末端。 箭头具体图形参考 arrow shapes。

所谓图集(cluster)就是一组包裹在子图中的节点构成的图,它使用 cluster 作为子图 ID 名称前缀, 一般使用下划线与后缀名分隔,这样分隔可以使名称更直观。图集会被线框包裹以表示一簇子图节点。另外一种 图集的表示方式是直接使用花括号包裹一组节点。图集可以方便对多个节点进行连接,连到到图集的线会自动 与图集的子节点相连,而不必手动逐个设置。图集还可以像 graph 一样可以使用 label 属性设置 标题字符串。

阅读 Graphviz 问答列表得到关于图层和 3D 图形处理的信息:
How can I get 3D output?
Blender and Web3D X3D/VRML2
Graphviz 支持 VRML 格式导出,这是一种使用文本描述 3D 模型的文件格式。Blender 中可以安装一个 VRML 格式插件来导入并按照 Graphviz 输出的信息生成 3D 模型。默认输出的 VRML 扩展名(.vmrl)与 Blender 中插件使用的扩展名(.wrl)不同,使用命令导出文件时可以指定文件名。 插件稳定性不是很好,如果删除导入的内容再重新导入时会报错。 导出 VRML 文件时,Graphviz 布局引擎会将渲染的节点平面图像作为纹理 ImageTexture 文件 输出到相同目录下。Blender 插件在导入时,会自动读取纹理文件(PNG)并且提取其 Alpha 通道 中的透明信息设置到生成模型的材质中。图表中的 shape=point 会被当作 3D sphere 模型处理。 线段使用 cylinders 表示,此外,其它图表在转换 3D 模型时都是使用平面表示。 $ dot -Kfdp -Tvrml -o tree.wrl tree.dot $ neato -Tvrml -o tree.wrl tree.dot 尽管可以使用 dim (layout)和 dimen(rendering)属性启用布局引擎的三维坐标, 以及配合 pos 属性设置三维坐标(z 属性过时)。但是导出 VRML 的三维信息也较局限。 没有模型厚度信息。 设置三维后(dim=3),neato 会使用节点的 z 属性值作为 pos 初始值的 z 分量。 而且必需在节点中定义 z 属性,否则在命令行中不能通过 -G 参数形式设置。缺少 z 属性 会导致 neato -Tvrml 输出的模型投影在 xy 平面。Blender 中是 xz 平面。至少应该有一个 节点设置 z 值,或者使用新的 pos 属性设置三维坐标。 pos 是一个多用途属性,它既可以用作设置节点的坐标(point),又可以作为样条控制 点列表(splineType)。如果是作为节点坐标,格式是 "%f,%f('!')?",或者三维坐标 "%f,%f,%f('!')?",分别对应 (x,y) 和 (x,y,z)。后缀的感叹号 '!' 表示只作为 输入 (input-only),布局引擎不能修改它。要在 Blender 中使用导出的具有三维信息的图表, 就要 dimen=3,这是设置渲染输出三维信息,也就是导出时的坐标维度。另一个 dim=3 设置的布局坐标维度与导出无关。Graphviz 作为一个 2D 绘图工具, 它不能处理 3D 图形的渲染。以下代码片段用于测试,可以看到设置了三维坐标的节点只有在 neato 以及 fdp 引擎布局下才能正确处理。默认的 dot 布局不能处理 2D 以外的坐标。 neato 内部的布局支持超过三维的坐标,dim 和 dimen 属性 都可以设置超过 3 的值,比如在命令行中传入全局设置 neato -Gdim=7。Graphviz 处理 2D 和 3D,但除非在调用 neato 布局引擎并检查 ND_pos(n)[i] 来获取 n 节点的 i 维数据,否则 无法获得更高维的输出。这个方法在开发自己的布局引擎时会用到。命令行中可以使用 -n 来禁止脚本中 设置的 layout 模式。 Graphviz pos 属性有两种用途,对应两种数据类型,前面解释了它作为节点中心坐标的使用形式, 如果 pos 属性作为 splineType 类型使用,用于控制 edge 对象的样条线,格式如下: pos = spline ( ';' spline )* spline = (endp)? (startp)? point (triple)+ triple = point point point endp = "e,%f,%f" startp = "s,%f,%f"
以下是 Graphviz Shapes, Edges and Styles Cheatsheet,建议人手一份。

Graphviz 样式属性可以组合在一起使用,但是每个样式有它生效的前置条件,在条件不满足时就会失效。 比如,样式 style=striped 属性只能和矩形类图形配合使用,比如 shape=box 或者在 cluster 图集上使用,它本身可以看作是一个矩形。style="wedged" 只能用在圆形上, 包括 ellipse、oval、circle、Mcircle、doublecircle,但是 egg、point 不算。 控制边线的样式,group exclude 标记的样式是互斥项,最后设置的项生效。其中 invis 可以看作是其它所有样式的互斥项,因为设置了 invisible 就看不见图形了,所以常用它来隐藏 一些用于辅助布局的图形、边线。对于边的 bold 样式,可以等效看作 penwidth=2 属性 加粗其笔触。控制边线尖锐化的 "tapered" 属性是一个比较复杂的设置,它需要结合多个 属性工作。其工作流程可以描述为:设置边线属性 style=tapered 启用尖锐化,将按照 penwidth 属性指定的像素大小沿着 dir 属性 指定的方向锐减到 1 个像素。边线的箭头大小也受到 penwidth 属性影响,可以通过 arrowhead 以及 arrowtail 属性来控制是否显示目标点、起点箭头。






开放文档持续更新,未来上传 Github:
https://vscode.dev/github/Jeangowhy/opendocs/blob/main/Graphviz_dot.adoc