使用 Graphviz 绘图
1. 简介
Graphviz 是一款开源的绘图软件,主要用来绘制关系、流程图。与多数“所见即所得”的绘图软件不同,Graphviz 使用 DOT 语言来绘图(可理解为“所思即所得”),这样我们可专注于图片要表达的内容本身,而不去考虑如何画一根线、如何处理元素之间的布局等等,这些工作都由 Graphviz 去做;由于通过编程方式绘图,源文件也是纯文本形式的,便于我们用任意编辑器编辑,同时便于使用版本控制软件管理。
DOT 语言用于描述节点、线和图(Graph),其中图又分为有向图和无向图。
2. 无向图
无向图即节点之间的连线没有箭头指向的方向。定义语法为:
graph 图名称 { 节点1 -- 节点2; }
节点之间的连线用“–”。另外,DOT 语言的注释如下:
// 行注释 /* 块注释 */
例,定义一棵二叉树:
// example.dot graph example { a -- b; a -- c; b -- d; b -- e; c -- f; c -- g; }
然后用 dot 命令生成图片(命令使用方法请见“附录”)。
结果如下图:
3. 有向图
有向图即节点之间通过箭头表示方向。定义语法如下:
digraph 图名称 { 节点1 -> 节点2; // 节点之间关系用”->“表达 }
例:
digraph example{ a -> b; a -> c; }
输出如下:
a -> b; a -> c;
还可用等价的语法表示:
a -> {b, c};
4. 属性设置
在 DOT 语言中我们还可以对图、节点和连线进行样式的属性设置。
以下几节列举一些常用的,完整的属性请见官方文档:http://www.graphviz.org/content/attrs。
4.1. 通用属性
4.1.1. label
label 可为图、线和节点设置文字标签。
例,为节点设置标签:
digraph example { 0 [label="顶点0"]; 0 -> 1 [label="从0到1"]; }
效果如下:
例,为图设置标题:
digraph example { graph [label="图的标题"]; a -> b; }
效果如下:
4.1.2. 设置字体样式
4.1.2.1. 字体大小设置
digraph example { a [fontsize=18, label="18号字体"] b [fontsize=12, label="12号字体"]; a -> b; }
效果如下:
4.1.2.2. 字形设置
要设置字形,需要使用的 HTML 标签。注意 label 里使用“<…>”包围字符串。
digraph example { b [label=<<b>粗图</b>、<i>斜体</i>、<u>下划线</u>>]; a -> b; }
4.2. 线属性
4.2.1. 箭头样式设置
digraph example { a -> b [label="默认是实线"]; b -> c [style=dotted, label="虚线"]; c -> d [style=bold, label="粗线"]; d -> e [dir=none, label="无箭头"]; }
效果如图:
4.2.2. 反向箭头
digraph example{ a -> b; b -> c [dir=back] }
效果如图:
4.2.3. 双向箭头
digraph example { a -> b [dir=both]; }
效果如图:
4.2.4. 横向箭头
digraph example { rankdir = LR; // LR 表示箭头方向是从左到右,要想从右到左,改成RL即可 a -> b -> c -> d; }
效果如图:
4.3. 节点属性
4.3.1. 改变节点的形状
下面代码列举了一些常用形状:
digraph example { a [shape=box, label="矩形"]; b [shape=circle, label="圆"]; c [shape=polygon, label="平行四边形"]; d [shape=ellipse, label="椭圆"]; a -> b -> c -> d; }
效果如下图:
4.3.2. 节点边框设置
4.3.2.1. 无边框
代码如下,取消节点“b”的边框样式:
digraph example { b [shape=none]; a -> b; }
效果如下图:
还可以把节点变成文本样式:
digraph example { rankdir = LR; // 节点的方向是从左到右 node [shape=plaintext]; a -> b -> c -> d; }
效果如下图:
4.3.2.2. 改变边框颜色
digraph example { b [color="red"]; a -> b; }
效果如下图:
4.3.3. 填充颜色
4.3.3.1. 渐变色填充
digraph example { b [style=filled, fillcolor="red:black"]; // 红色到黑色的渐变 a -> b; }
效果如下图:
4.3.3.2. 纯色填充
digraph example { b [style=filled, fillcolor="red"]; a -> b; }
效果如下图:
4.3.4. 节点局部属性设置
如果想更改图中某些局部节点的样式,可以把这些节点加在一对花括号中,并设置样式。示例如下:
digraph example { a -> b -> c -> d; // 设置“点1”和“点2”的样式 { node [shape=plaintext, style="filled", fillcolor="red"]; 点1 -> 点2; } }
效果如下图:
4.4. label 属性的高级用法——结构体
下面展示下如何用 label 将一个节点“分割”成多个部分,类似 C 语言的结构体。
如下代码,将一个节点表达成一个链表结构,然后用连线来连接:
digraph example { node [shape=record]; // label属性中,用“|”分割出元素,“<..>”之间的定义成一个变量 struct1 [label="<head> head | item1 | item2 | ... | <tail>tail"]; // 用“节点名:元素名”的方式引用结构体的变量 struct1:head -> struct1:tail; }
效果如下图:
还可以对结构体元素中再进行分割,只需在元素中用“{ … }”划分。如下,表达 ELF 结构体:
digraph example { node [shape=record]; struct1 [label="ELF Header | Header Table | {Segment1 | Segment2 | ...} | Section Header Table"]; }
效果如下图:
5. 子图
子图展示出来的效果就像下面这样,把一组节点单独划分到一个“图”中:
只需用 subgraph 关键字定义子图即可,注意子图的名字必须以“cluster”开头。上图对应的代码如下:
digraph example { a -> b; subgraph cluster_sub1 { // 子图可以设置自己的样式 label ="子图"; labelloc = "t"; bgcolor = "gray"; a -> c -> d; } }
5.1. 将某个节点指向一个子图
DOT 语言中不能将一个节点指向一个子图,例如表达成:
a -> cluster_sub1
不能这样表达,DOT 也不支持节点指向子图。要达到这种效果,只有靠设置属性来改变生成后的外观效果。
例如下面的代码:
digraph example { compound = true; // 必须设置该属性 subgraph cluster_sub1 { c -> d; c -> e; } A -> B; A -> C; A -> d [lhead=cluster_sub1]; }
其中 c、d 和 e 节点单独在一个子图中,我们想让 A 节点直接指向子图 cluster_sub1,这时只用将 A 节点指向子图的某个节点,然后为这条线设置 lhead 属性即可。上面代码生成后的效果如下:
6. 用其他工具修改图片
Graphviz 最大的问题就是输出图片时是依赖自动布局算法,某些时候可能需输出的图片并非我们想要的样子,如果想调整图片,可以先输出成 SVG 格式:
$ dot -Tsvg example.dot -o example.svg
然后再用 Inkscape 等工具修正即可。
7. 附录
7.1. 安装 Graphviz
1、GNU/Linux 下可通过自带的包管理器安装,如 Fedora:
$ sudo dnf install graphviz
2、官方下载二进制包:http://www.graphviz.org/。
7.2. 命令使用方法
在 shell 下执行如下命令生成图片:
$ dot -Tpng [dot脚本文件] -o [输出的文件名.png]
-T 参数指定输出的格式。可通过 -Tv 参数可列出支持导出的格式:
$ dot -Tv Format: "v" not recognized. Use one of: bmp canon cmap cmapx cmapx_np dot eps fig gtk gv ico imap imap_np ismap jpe jpeg jpg pdf pic plain plain-ext png pov ps ps2 svg svgz tif tiff tk vdx vml vmlz x11 xdot xdot1.2 xdot1.4 xlib