Graphviz是大名鼎鼎的贝尔实验室的几位牛人开发的一个画图工具。它的理念和一般的“所见即所得”的画图工具不一样,是“所想即所得”。 Graphviz提供了dot语言来编写绘图脚本。

一、安装

graphviz可以使用在windows上,也可以使用在linux和MAC上,在centos上只需要一条指令yum install graphviz 即可完成安装。其他OS上的安装可以参考官方文档

二、使用

1、简单示例

一个简单的示例如下(其实可以再简单点,直接hello -> world):

1# vim text.dot
2digraph G {
3        hello [shape=box];
4        world [style=filled, color="1,1,1"];
5        hello -> world [label="Yes"];
6    }

编译时输出格式可以根据自己的需要来灵活选择,主要有一下三种:

1dot -Tpng test.dot -o test.png
2dot -Tsvg test.dot -o test.svg
3dot test.dot -Tpdf -o test.pdf

2、有向无向图

简单介绍下DOT语言的语法,根据输出图片的是否有箭头指向,会有有向图和无向图的区分,如下:

 1无向图:
 2graph graphname {
 3     a -- b -- c;
 4     b -- d;
 5 }
 6有向图:
 7digraph graphname {
 8     a -> b -> c;
 9     b -> d;
10 }

注:双向图可以使用dir = “both” 解决,代码如下:

1digraph graphName {
2A->B[dir="both"]
3}

3、节点和边的属性设置

边和节点的属性设置方法不一致,节点的属性被放置在只包含节点名称的表达式后,如下:

1digraph G {
2        hello [shape=box];//设置节点属性
3        world [style=filled, color="1,1,1"];//节点多个属性用逗号分开
4        hello -> world [label="Yes"];//设置边的属性
5    }

4、节点和属性总结

画图时需要对图片做一些特别的处理,例如加粗、把图变色等。我们要控制这些东西,就需要用到属性。属性有四种:

  1. 用在节点上(Node, N) ;
  2. 用在线段上(Edge, E);
  3. 用在根图片上(Graph, G);
  4. 用在子图片上(Cluster subgraph, C)

对于节点(node) 的属性,有以下几种指定法:

  • 节点名[节点属性名=值];
  • 节点名[节点属性名=值,节点属性名=值];
  • node [节点属性名=值,节点属性名=值];

属性指定的语句必须要被中括号括起。当一次指定多值时,需用英文逗点隔开。第三行中的node 是个关键字,用来代称「图片范围内」所有「还没创建」的节点,或者您也可将它理解为:在当前大括号的范围内,所有尚未创建节点的属性预设值,会被这个语句给变更。

对于线段(edge)的属性指定,与上述节点属性指定方式很类似:

  • 节点名->节点名[线段属性名=值];
  • 节点名–节点名[线段属性名=值,线段属性名=值];
  • edge [线段属性名=值,线段属性名=值]; 其中edge 是关键字。

5、子图

subgraph 的作用主要有 3 个:

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

6、布局

默认情况下图是从上到下布局的,通过设置 rankdir=“LR” 可以让图从左到右布局,对应的也有从上到下的布局为 rankdir=“TB”。

一个简单的表示 CI/CD 过程的图:

 1digraph pipleline {
 2    rankdir=LR;
 3    g [label="Gitlab"];
 4    j [label="Jenkins"];
 5    t [label="Testing"];
 6    p [label="Production" color=red];
 7    g -> j [label="Trigger"];
 8    j -> t [label="Build"];
 9    t -> p [label="Approved"];
10}

graphviz
graphviz

三、一些demo示例

1、进程内部模块调用示例

 1digraph G{
 2    size = "5, 5";//图片大小
 3    main[shape=box];/*形状*/
 4    main->parse;
 5    parse->execute;
 6    main->init[style = dotted];//虚线
 7    main->cleanup;
 8    edge[color = green]; // 连接线的颜色
 9    execute->{make_string; printf}//连接两个
10    init->make_string;
11    main->printf[style=bold, label="100 times"];//线的 label
12    make_string[label = "make a\nstring"]// \n, 这个node的label,注意和上一行的区别
13    node[shape = box, style = filled, color = ".7.3 1.0"];//一个node的属性
14    execute->compare;
15}

2、游戏资源更新流程

 1digraph startgame {
 2    label="游戏资源更新流程"
 3    rankdir="TB"
 4    start[label="启动游戏" shape=circle style=filled]
 5    ifwifi[label="网络环境判断是否 WIFI" shape=diamond]
 6    needupdate[label="是否有资源需要更新" shape=diamond]
 7    startslientdl[label="静默下载" shape=box]
 8    enterhall[label="进入游戏大厅" shape=box]
 9    enterroom[label="进入房间" shape=box]
10    resourceuptodate[label="资源不完整" shape=diamond]
11    startplay[label="正常游戏" shape=circle fillcolor=blue]
12    warning[label="提醒玩家是否更新" shape=diamond]
13    startdl[label="进入下载界面" shape=box]
14    //{rank=same; needupdate, enterhall}
15    {shape=diamond; ifwifi, needupdate}
16    start -> ifwifi
17    ifwifi->needupdate[label="是"]
18    ifwifi->enterhall[label="否"]
19    needupdate->startslientdl[label="是"]
20    startslientdl->enterhall
21    needupdate->enterhall[label="否"]
22    enterhall -> enterroom
23    enterroom -> resourceuptodate
24    resourceuptodate -> warning[label="是"]
25    resourceuptodate -> startplay[label="否"]
26    warning -> startdl[label="确认下载"]
27    warning -> enterhall[label="取消下载"]
28    startdl -> enterhall[label="取消下载"]
29    startdl -> startplay[label="下载完成"]
30}

3、生成有颜色和形状的图表

1digraph example3 {
2    Server1 -> Server2
3    Server2 -> Server3
4    Server3 -> Server1
5    Server1 [shape=box, label="Server1\nWeb Server", fillcolor="#ABACBA", style=filled]
6    Server2 [shape=triangle, label="Server2\nApp Server", fillcolor="#DDBCBC", style=filled]
7    Server3 [shape=circle, label="Server3\nDatabase Server", fillcolor="#FFAA22", style=filled]
8}

4、字节点调用图

 1graph G{
 2"黑海" [shape = circle, color = blueviolet, fontcolor = blueviolet, fontsize = 20];
 3"黑海" -- "亚速海" [label = "刻赤海峡"];
 4subgraph cluster_T{
 5label = "黑海海峡";
 6fontsize = 24;
 7fillcolor = darkslategray;
 8style = filled;
 9fontcolor = white;
10node [fontcolor = white, color = white];
11"博斯普鲁斯海峡" -- "马尔马拉海" -- "达达尼尔海峡" [color = white];
12"博斯普鲁斯海峡" [shape = parallelogram];
13"达达尼尔海峡" [shape = parallelogram];
14}
15"黑海" -- "博斯普鲁斯海峡" [color = red ,penwidth = 2];
16"达达尼尔海峡" -- "爱琴海" [color = red ,penwidth = 2];
17subgraph cluster_M{
18label = "地中海海域";
19fontsize = 24;
20"西部地中海" [shape = Mcircle, style = filled, color = grey, fillcolor = aquamarine, fontsize = 20];
21"中部地中海" [shape = Mcircle, style = filled, color = grey, fillcolor = aquamarine, fontsize = 20];
22"直布罗陀海峡" [shape = parallelogram, fontcolor = red];
23"西西里海峡" [shape = parallelogram ];
24"中部地中海" -- {"爱琴海" "爱奥尼亚海" "西西里海峡"};
25"西部地中海" -- {"西西里海峡" "第勒安海" "利古里亚海" "伊比利海" "阿尔沃兰海"};
26"爱奥尼亚海" -- "亚得里亚海"; 30 "阿尔沃兰海" -- "直布罗陀海峡";
27}
28}

5、python模块调用示例

 1import pygraphviz as pgv
 2G = pgv.AGraph(directed=True, rankdir="TB")
 3# 设置节点标签
 4Root = "道路交通流畅"
 5negative_1 = "平均延误时间"
 6negative_2 = "负荷度"
 7negative_3 = "小区位置"
 8negative_4 = "相对延误率"
 9negative_5 = "房屋密度"
10negative_6 = "人口密度"
11negative_7 = "总延误率"
12negative_8 = "排队率"
13negative_9 = "行驶时间"
14positive_1 = "通行能力"
15positive_2 = "公路层级"
16positive_3 = "路网结构"
17positive_4 = "行驶速度"
18positive_5 = "路网长度"
19positive_6 = "小区面积"
20positive_7 = "内部道路密度"
21positive_8 = "路网密度"
22# 添加节点
23G.add_node(Root, style="filled", shape="box3d", color="#feb64d")
24for negative in [eval(_) for _ in dir() if _.startswith("negative")]:
25    G.add_node(negative, style="filled", shape="ellipse", color="#CFDBF6")
26for positive in [eval(_) for _ in dir() if _.startswith("positive")]:
27    G.add_node(positive, style="filled", shape="ellipse", color="#B4E7B7")
28# 添加边
29G.add_edges_from([[Root, negative_1], [Root, negative_6], [Root, negative_8], [Root, negative_9],
30                  [negative_1, negative_2], [negative_1, negative_7], [negative_2, negative_3],
31                  [negative_2, negative_7], [negative_3, negative_4], [negative_8, negative_9],
32                  [positive_2, negative_5], [positive_3, negative_4], [positive_4, negative_5]],
33                 color="#B4DBFF", style="dashed", penwidth=1.5)
34G.add_edges_from([[Root, positive_1], [Root, positive_8], [negative_5, negative_4],
35                  [negative_6, positive_4], [negative_5, positive_4], [negative_9, positive_5],
36                  [positive_1, positive_2], [positive_2, positive_3], [positive_6, positive_5],
37                  [positive_7, positive_6], [positive_8, positive_7]],
38                 color="#B4E7B7", style="dashed", penwidth=1.5)
39# 导出图形
40G.layout()
41G.draw("因子相关性图.png", prog="dot")

6、结构体切割

 1digraph dfd2{
 2        node[shape=record]
 3        subgraph level0{
 4        enti1 [label="Customer" shape=box];
 5        enti2 [label="Manager" shape=box];
 6        }
 7        subgraph cluster_level1{
 8                        label ="Level 1";
 9                        proc1 [label="{<f0> 1.0|<f1> One process here\n\n\n}" shape=Mrecord];
10                        proc2 [label="{<f0> 2.0|<f1> Other process here\n\n\n}" shape=Mrecord];
11                        store1 [label="<f0>    |<f1> Data store one"];
12                        store2 [label="<f0>   |<f1> Data store two"];
13            {rank=same; store1, store2}
14        }
15        enti1 -> proc1
16        enti2 -> proc2
17        store1 -> proc1
18        store2 -> proc2
19    proc1 -> store2
20    store2 -> proc1
21}</f1></f0></f1></f0></f1></f0></f1></f0>