使用dot来绘图

写在前面


第一次了解到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

代码如下:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 119,343评论 16 133
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 155,877评论 24 680
  • 夏天是最不无聊的季节, 一直以来,都是蜜递君最喜欢的季节。 无论时光怎样更迭,只要到了夏天,好像就会回到胶原蛋白满...
    蜜递伊甸园阅读 412评论 0 0
  • 这是一段发生在江南茶乡的普通故事,普通到我们每个人可能都见过都听过…… 老黄很老了,老到天天只能保持一个坐姿在门口...
    凌家龙少阅读 620评论 4 6
  • 亲爱的程程你好,这是妈妈给你的第二封情书。 现在你应该是坐在教室里听课吧?我知道你上课总是非常积极的回答问...
    日出东方天刚晓阅读 76评论 0 0