写在前面
第一次了解到dot已经是很久之前的事情了,但是到今天才决定写下一点东西,可见懒到什么程度。最开始对dot感兴趣是因为下面这张图:
对于这种网络图用visio很容易搞定,但是复制、粘贴、拖拽这些用起来总是感觉不怎么顺手。但是用dot来做就完全不一样了,只需写段文本:
digraph G {
main -> parse -> execute;
main -> init;
main -> cleanup;
execute -> make_string;
execute -> printf;
init -> make_string;
main -> printf;
execute -> compare;
}
然后在需要的时候在这段文本中增加边、节点即可。保存之后只需要用贝尔实验室搞的graphviz来生成目标图片就可以了:
dot -Tpng G.dot -o g.png
这样你的vim、emacs就马上变成了一个可以画图的工具了:)。
可能你觉得这样还不够爽,想想看如果用代码自动生成dot文件,也就是说可以通过用代码输出dot文件来间接到达输出图片的目的,是不是能做的事情多多了?
简单用法
日常中用到画图的地方,知道下面几点基本上就够用了:
- 有向图(digraph)用a->b,无向图(graph)用a--b;
- 节点、边通过中括号中的key=value来设置,比如main[shape=box];将main节点设置为矩形;
- 连接点的方向可以通过b->c:se;进行指定;
- 使用subgraph定义子流程图;
常用属性
对于各种结构的通用的属性如下:
属性名称 | 默认值 | 含义 |
---|---|---|
color | black | 颜色 |
colorscheme | X11 | 颜色描述 |
fontcolor | black | 文字颜色 |
fontname | Times-Roman | 字体 |
fontsize | 14 | 文字大小 |
label | 显示的标签,对于节点默认为节点名称 | |
penwidth | 1.0 | 线条宽度 |
style | 样式 | |
weight | 重要性 |
常用边属性如下:
属性名称 | 默认值 | 含义 |
---|---|---|
arrowhead | normal | 箭头头部形状 |
arrowsize | 1.0 | 箭头大小 |
arrowtail | normal | 箭头尾部形状 |
constraint | true | 是否根据边来影响节点的排序 |
decorate | 设置之后会用一条线来连接edge和label | |
dir | forward | 设置方向:forward,back,both,none |
headclip | true | 是否到边界为止 |
tailclip | true | 与headclip类似 |
常用节点属性如下:
属性名称 | 默认值 | 含义 |
---|---|---|
shape | ellipse | 形状 |
sides | 4 | 当shape=polygon时的边数 |
fillcolor | lightgrey/black | 填充颜色 |
fixedsize | false | 标签是否影响节点的大小 |
常用图属性如下:
属性名称 | 默认值 | 含义 |
---|---|---|
bgcolor | 背景颜色 | |
concentrate | false | 让多条边有公共部分 |
nodesep | .25 | 节点之间的间隔(英寸) |
peripheries | 1 | 边界数 |
rank | same,min,source, max,sink,设置多个节点顺序 | |
rankdir | TB | 排序方向 |
ranksep | .75 | 间隔 |
size | 图的大小(英寸) |
高级用法
在dot里面label的玩法比较多,在上面看到的每个节点都是简单的一段文字,如果想要比较复杂的结构怎么办?如下图:
对应的代码如下:
digraph structs {
node [shape=record];
struct1 [shape=record,label="<f0> left|<f1> mid\ dle|<f2> right"];
struct2 [shape=record,label="<f0> one|<f1> two"];
struct3 [shape=record,label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"];
struct1 -> struct2;
struct1 -> struct3;
}
这个还不算厉害的,label还支持HTML格式的,这样你能想得到的大部分样子的节点都可以被定义出来了:
代码如下:
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>>];
}
接着来看cluster的概念,在dot中以cluster开头的子图会被当做是一个新的布局来处理,而不是在原图的基础上继续操作。比如:
对应代码如下:
digraph G {
subgraph cluster0 {
node [style=filled,color=white];
style=filled;
color=lightgrey;
a0 -> a1 -> a2 -> a3;
label = "process #1";
}
subgraph cluster1 {
node [style=filled];
b0 -> b1 -> b2 -> b3;
label = "process #2";
color=blue
}
start -> a0;
start -> b0;
a1 -> b3;
b2 -> a3;
a3 -> a0;
a3 -> end;
b3 -> end;
start [shape=Mdiamond];
end [shape=Msquare];
}
如果没有cluster的话我们大概能想象的出来最后的结果是什么样子的。可能会想能不能将一个节点直接指向cluster?答案是不能!对于这种需求可以用lhead来搞定:
digraph G {
compound=true;
subgraph cluster0 {
a -> b;
a -> c;
b -> d;
c -> d;
}
subgraph cluster1 {
e -> g;
e -> f;
}
b -> f [lhead=cluster1];
d -> e;
c -> g [ltail=cluster0, lhead=cluster1];
c -> e [ltail=cluster0];
d -> h;
cluster0->cluster1;
}
生成的图片如下: