安装Graphviz

在Graphviz官网下载各个操作系统的Graphviz.
安装后再将安装目录的graphviz\bin加入环境变量PATH里.
比如我的安装路径为C:\Program Files (x86)\Graphviz, 我就在环境变量中的PATH里添加C:\Program Files (x86)\Graphviz\bin.
打开cmd输入dot -V, 若显示graphviz的版本即表示配置成功. 我这里显示的dot - graphviz version 2.38.0 (20140413.2041)

配置Graphviz编辑器

安装Graphviz时, 有些安装包会默认安装一个简易的编辑器gvedit, 可以实现简单的可视化. 但并没有代码补全等功能.
这里推荐使用vscoede配合插件(Graphviz (dot) language support for Visual Studio Code)进行编辑, 可以实现代码补全, 提示和实时的图像.

graphviz01

当然你也可以直接在cmd界面输出图像, dot -T[输出图像后缀名] [后缀为gv的文件名] -o [输出图像文件名]

图形类别

无向图

在最简单的应用中, DOT语言可以用来描述一张无向图. 无向图显示了对象间最简单的关系. 使用关键字graph开始一张无向图的定义, 并用大括号包含要描述的节点. 双连字号(–)被用来描述节点间的关系. 另外, 一行的末尾需要加上分号(;).

1
2
3
4
graph graphname {
a -- b -- c;
b -- d;
}

graphviz02

有向图

类似于无向图, DOT语言也可以用来描述一张有向图, 类似于流程图和树状图. 其语法与无向图相似, 但要在图的最开始使用关键字digraph, 并用箭头(->)表示节点直接的关系.

1
2
3
4
5
digraph graphname {
a->b->c;
c->a;
b->d;
}

graphviz03

子图

字体 subgraph 的作用主要有 3 个:

  1. 表示图的结构, 对节点和边进行分组.
  2. 提供一个单独的上下位文设置属性.
  3. 针对特定引擎使用特殊的布局. 比如下面的例子, 如果 subgraph 的名字以 cluster 开头, 所有属于这个子图的节点会用一个矩形和其他节点分开.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
digraph graphname{ 
a -> {b c};
c -> e;
b -> d;

subgraph cluster_bc {
bgcolor=red;
b;
c;
}

subgraph cluster_de {
label="Block"
d;
e;
}
}

graphviz04

属性

节点形状

节点的默认属性为 shape=ellipse, width=.75, height=.5, 标签默认为节点名.
节点又分为基于多边形的节点和基于记录的节点.

基于多边形的节点

基于多边形的节点还有 box, polygon, oval, circle, point, egg, triangle, plaintext, plain, diamond, trapezium, parallelogram, house, pentagon, hexagon, septagon, octagon, doublecircle, doubleoctagon, tripleoctagon, invtriangle, invtrapezium,invhouse, Mdiamond, Msquare, Mcircle, rect, rectangle, square, star, none, underline, cylinder, note, tab, folder, box3d, component, promoter, cds, terminator, utr, primersite, restrictionsite, fivepoverhang, threepoverhang,noverhang, assembly, signature, insulator , ribosite, rnastab, proteasesite, proteinstab, rpromoter, rarrow, larrow, lpromoter. 其中很大一部分形状都可以由属性sides设置边的数目, 而设置regular=true, 节点形状会强制规整, 如正三角形.

tips: 除非给定 fixedsize=true, 不然节点的大小还是会根据实际情况(如标签长度等)而改变.

具体形状如下图:

graphviz05

基于记录的节点

基于记录的节点主要是shape=recordshape=Mrecord两种.
基于记录的节点的结构主要是由label属性决定的, 基本结构如下例:

1
2
3
4
digraph structs {
name [shape=record, label="a | { b |<subname1> c | d } | <subname2> e"];
name:subname2 -> f;
}

graphviz06

其中name为节点名; subname为节点内次节点的标志, 主要用来作为边的起点或终点; |用来分隔文本; {} 会使得括号内结构与外部方向相反.

举一个更详细的例子:

1
2
3
4
5
6
7
8
digraph structs {
node [shape=record];
struct1 [label="<f0> left|<f1> mid&#92; dle|<f2> right"];
struct2 [label="<f0> one|<f1> two"];
struct3 [label="hello&#92;nworld |{ b |{c|<here> d|e}| f}| g | h"];
struct1:f1 -> struct2:f0;
struct1:f2 -> struct3:here;
}

graphviz07

想要了解更多, 可以参考 官方文档.

箭头形状

关于箭头形状, 这里就不详细讨论了, 详情可以参考 官方文档对箭头的说明.

节点和边的属性

节点和边的属性挺多的, 但是平常很少用到, 这里不详细展开了, 有兴趣可以参考 官方文档对节点和边属性的说明.

标签

之前已经提到过了, 节点默认的标签是节点名, 边默认是没有标签的, 事实上节点和边都可以使用label属性来单独设置标签.

虽然看起来用name来设置标签很方便, 但是很多时候我们会遇到重复的标签, 或者不能用于name的字符, 这种时候就需要用到label属性了.

label属性支持逃逸字符, 同时, 也可以使用 labelloc 属性来指定标签的位置, 可选的位置有t, 上部(top); b, 底部(bottom). 还可以使用 labeljust 属性决定字符左对齐还是右对齐, 左对齐: l; 右对齐: r.

label 默认的字号是14, 字体为 Times-Roman, 黑色. 这些都可以通过 fontsize, fontname, fontcolor 进行修改.

节点只有一种标签, 而边有三种标签, 一种是在边头的headlabel, 一种是在边尾的taillabel, 还有在边中的label.

1
2
3
4
digraph{
a[shape=box, label="你好,世界", fontname="SimHei", fontsize=18, fontcolor="red", labelloc="t"];
a -> b [headlabel="I'm headlabel", label="I'm label", taillabel="I'm taillabel"]
}

graphviz08

除此之外, 标签还支持 html 语言, 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
digraph html {
abc [shape=none, margin=0, label=<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
<TR><TD ROWSPAN="3"><FONT COLOR="red">hello</FONT><BR/>world</TD>
<TD COLSPAN="3">b</TD>
<TD ROWSPAN="3" BGCOLOR="lightgrey">g</TD>
<TD ROWSPAN="3">h</TD>
</TR>
<TR>
<TD>c</TD>
<TD PORT="here">d</TD>
<TD>e</TD>
</TR>
<TR>
<TD COLSPAN="3">f</TD>
</TR>
</TABLE>
>];
}

graphviz09

颜色

官方给出的颜色表.

布局

默认情况下图是从上到下布局的, rankdir属性 设置图形布局的排列方向(全局只有一个生效). “TB”, “LR”, “BT”, “RL”, 分别对应于从上到下,从左到右,从下到上和从右到左绘制的有向图.

还可以通过设置rank属性给节点排序, 最小等级是最顶部或最左侧, 最大等级是最底部或最右侧, 可选属性有:

  • same: 所有节点都位于同一等级
  • min source: 所有节点都位于最小等级上
  • max sink: 所有节点都位于最大等级上.

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
digraph example {

2017[shape=plaintext];
2018[shape=plaintext];
2019[shape=plaintext];
2020[shape=plaintext];

2017 -> 2018 -> 2019 -> 2020;
201701 -> 201702 -> 201703;
201801 -> 201802 -> 201803;
201901 -> 201902 -> 201903;
202001 -> 202002;

{ rank=min; 2018; 201801; 201802; 201803};
{ rank=same; 2019; 201901; 201902; 201903};
{ rank=max; 2017; 201701; 201702; 201703};
{ rank=sink; 2020; 202001; 202002};
}

graphviz10

进阶

自动生成 python UML图

Pyreverse工具就是基于Graphiviz的自动生成UML图的工具. pylint中集成了该工具.

通过命令pyreverse -ASmy -o gv test.py就可以在当前目录下生成UML类图文件.

  • -o:指定输出的图形格式
  • test.py: 指定要生成类图的源码文件, 也可以是目录

后记

总觉得自己教程写得挺烂的, 推荐一篇大神参考官方文档写的教程: Graphviz 画图的一些总结.

平常绘制流程图, 我还是推荐使用 draw.io.

……

虽然在最后说有点问题, 但我还是想说, mermaid其实也是挺好用的, 更美观一些, 不过二者优劣势还是挺明显的, 可以看情况选择使用哪一个.

参考文献

1. Emden R. Gansner and Eleftherios Koutsofios and Stephen North. Drawing graphs with dot. January 5, 2015.
2. lfyzjck. Graphviz 入门指南 - 知乎. 2017.
3. Graphviz 官方文档