使用dot来绘图

96
wsztrush
2015.03.16 20:24* 字数 1314

写在前面


第一次了解到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文件来间接到达输出图片的目的,是不是能做的事情多多了?

简单用法


日常中用到画图的地方,知道下面几点基本上就够用了:

  1. 有向图(digraph)用a->b,无向图(graph)用a--b
  2. 节点、边通过中括号中的key=value来设置,比如main[shape=box];将main节点设置为矩形;
  3. 连接点的方向可以通过b->c:se;进行指定;
  4. 使用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格式的,这样你能想得到的大部分样子的节点都可以被定义出来了:

HTML格式的label
HTML格式的label

代码如下:

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;
}

生成的图片如下:

节点指向cluster
节点指向cluster
思考与实践