Graphviz介绍

Graphviz是一个由AT&T实验室启动的开源工具包,用于绘制DOT语言脚本描述的图形。借助Graphviz可像实现像写代码一样画图,非常方便。下面是一个DOT示例和它对应的图形:

digraph G {
    a -> b;
    a -> c;
    a -> d;
}

相关链接:

Graphviz官网:https://graphviz.org/

DOT语言官方文档:https://graphviz.org/doc/info/lang.html

DOT语言维基百科:https://zh.wikipedia.org/wiki/DOT语言

DOT教程:http://www.graphviz.org/pdf/dotguide.pdf

测试工具:

在vscode上安装Graphviz (dot) language support for Visual Studio Code这个插件,创建DOT文件,按Ctrl+Shift+P后输入Graphviz,选择Graphviz: Open Preview to the Side即可实时预览图片。

几个图形示例

示例来源:https://zh.wikipedia.org/wiki/DOT语言https://graphviztutorial.readthedocs.io/zh_CN/latest/index.html

无向图

graph {
    a -- b -- c;
    b -- d;
}

有向图

digraph G {
    a -> b -> c;
    b -> d;
}

节点属性和边属性

digraph G {
    // label属性可以改变节点显示的名称
    a [label = "Foo"];
    // shape属性可以改变节点的形状
    b [shape = box];
    // color指定连接线的颜色
    a -> b -> c [color = blue];
    // style指定连接线的风格
    b -> d [style = dotted];
}

控制边的方向

digraph {
    a -> b [dir = both];
    b -> c [dir = none];
    c -> d [dir = back];
    d -> a [dir = forward];
}

控制边的起点位置和终点位置

digraph {
    // 统一将下面的节点设置成矩形
    node [shape = box];
    // n/e/s/w分别表示north, east, south, west, 可组合使用
    c1:n  -> d1    [label=n];
    c2:ne -> d2:ne [label=ne];
    c3:e  -> d3:ne [label=e];
    c4:se -> d4:n  [label=se];
    c5:s  -> d5:n  [label=s];
    c6:sw -> d6:n  [label=sw];
    c7:w  -> d7:nw [label=w];
    c8:nw -> d8:nw [label=nw];
}

Dot语法

整体语法如下:

[strict] (graph | digraph) [ID] {
	stmt_list;
}

strict关键字表示是否允许重复的边,graph | digraph表示无向图或有向图,ID表示图的名称,stmt_list是图的具体内容。

关于图的具体内容定义如下:

stmt_list : [ stmt [';'] stmt_list ]

这是一个递归的定义,表示stmt_list由多个stmt组成,每个stmt表示一个语句,后面可用分号结尾。对于stmt,也就是语句,定义如下:

stmt: node_stmt | edge_stmt | attr_stmt | ID='ID' | subgraph

这里表示语句可以分成几种类型,包括node_stmt,表示节点类型,edge_stmt,表示边类型,attr_stmt,表示属性类型,ID类型,表示ID重命名,以及subgraph,表示子图。以下表格描述了各种类型的格式:

类型形式说明示例示例说明
node_stmtID [attr_list]定义一个节点,ID是表示节点名称的字符串,可以带双引号a [label = "Foo"];定义a节点,使用Foo作为显示文本
attr_list[k1=v1 k2=v2 ...]声明属性列表,key=value形式,多个属性空格隔开[style=bold label="hello world"]修饰的类型具有加粗属性,显示文本为"helloworld"
attr_stmt(graph | node | edge) attr_list定义图,节点,边的全局属性,三选一,该语句之后生效,前面的不影响

graph [ fontsize=10 fontname="Verdana" ]

全图字体为Verdana,大小为10
node [shape=box]该语句后面的所有节点形状为矩形
edge [style=dotted] 全图的边都使用虚线
edge_stmt(node_id | subgraph) edgeRHS [attr_list] 定义一条边,包括起始位置,连接操作(->--),结束位置,以及可选的后续位置

a -> b -> c
a -- b -- c

a:ne -> b:n


edgeRHSedgeop (node_id | subgraph) [edgeRHS]
node_idID[:n | ne | e | se | s | sw | w | nw | c]表示一个位置,最常见的是用节点名称来表示,也可以后面跟上:表示方向
subgraph[ subgraph [ID] ] { stmt_list }表示一个子图

subgraph sub_0 { a->b  }

{ rank=same; a b c }


属性

参考官方文档:https://graphviz.org/doc/info/attrs.html

支持对图的各项元素设置属性,比如修改节点形状,边的类型,图的字体和大小等。属性有具体的作用对象,并且属性并不是通用的,也就是一类属性只能修饰一类元素。属性具有属性名和属性值,两者都是区分大小写的。具体的属性列表可以参考上面的官方文档,下面列表一些常用的属性,作用对象中的E, N, G, S, C分别表示edge, node, the root graph, subgraph, cluster subgraph。

名称作用对象类型默认值说明示例
labelENGCstring
标签文本a -> b [label="Foo"]
shapeNstring
指定节点形状a [shape="box"]
dirE字符串枚举,forward/back/both/none
指定箭头方向a -> b [dir="both"]
styleENGC

指定显示风格a [style="filled"]

节点形状

参考官方文档:https://graphviz.org/doc/info/shapes.html

节点形状可分为三大类型:基于多边形(polygon-based),基于记录值(record-based),以及用户自定义(user-defined)。基于记录值的形状定义正在由HTML替代,而用户自定义则和PostScript有关,比较复杂。

基于多边形

基于多边形是指用图形来描述形状,比如 shape=box,以下是常见的图形效果,几个特殊值描述如下:

box/rec/rectangle等效,都表示矩形

none/plaintext等效,表示无边框

基于记录值

shape=recordshape=Mrecord,用节点的值来描述节点的形状,而不是用属性来指定,以下是一个示例:

digraph structs {
    node [shape=record];
    struct1 [label="<f0> left|<f1> mid\ dle|<f2> right"];
    struct2 [label="<f0> one|<f1> two"];
    struct3 [label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"]; // 宏bug,\n无法被渲染成换行
    struct1:f1 -> struct2:f0;
    struct1:f2 -> struct3:here;
}

效果如下:

通过示例可以很好地理解record-based的形状如何指定。值得注意的是,这种方式需要处理好转义字符,括号,大于小于号,竖线,空格都需要使用反斜杠进行转义。如果设置rankdir=LR属性,则会以下面的方式呈现:
如果将struct1改成shape=Mrecord,则会变成下面这样:
另外record-based的形状还可以通过HTML来指定,这点参考官方文档,学不下去了。

箭头形状

参考官方文档:https://graphviz.org/doc/info/arrows.html

和箭头相关的属性包括arrowhead, arrowsize, arrowtail三项,这三项属性都是对边(edge)生效,如果要修改全局的箭头形状,只需要在图开头指定edge的属性即可。按官方文档的描述,除了可以设置箭头形状,还可以设置箭头是否空心,是否只保留左半边或右半边,是否同时应用多个箭头形状等属性。另外,arrowtail只有在dir属性为back或both才生效。以下是一个示例:

digraph {
    a0 -> b0                            // 默认是normal类型
    a1 -> b1 [arrowhead=none]           // 无箭头
    a2 -> b2 [arrowhead=diamond]        // 更新箭头为diamond
    a3 -> b3 [dir=both arrowtail=diamond arrowhead=diamond] // 箭头和箭尾为diamond,只有dir为back或both才生效
    a4 -> b4 [arrowhead=lteeoldiamond ] // 参考官方文档,空心,左半边,tee | diamond
}

一些其他的示例

子图1

A -> {B C}

上面的语句等效于:

A -> B
A -> C

子图2

subgraph {
    rank=same; A; B; C;
}

这个子图含有3个节点,并且这3个节点会并排显示。

以cluster开头命名的子图

以cluster开头命名的子图会被渲染引擎进行特殊渲染,包括将节点画在一起,并且用一个矩形框包括起来,支持设置矩形框的颜色和文本,以下是一个示例:

digraph {
    node [shape=record];

    subgraph cluster_0 {
        node [style=filled];
        label = "cluster 0";
        color = blue;
        a -> b;
    }
    
    subgraph cluster_1 {
        node [style=filled];
        label = "cluster 1";
        color = blue;
        c -> d;
    }
}

中心对齐结点和边

来源:https://stackoverflow.com/questions/27504703/in-graphviz-how-do-i-align-an-edge-to-the-top-center-of-a-node


graph {
    splines=ortho;
    {0, 1, 2, 3 [width=0, shape=point, style=invis];}
    {rank=same; 1 -- 2 -- 3;}
    0 -- 2;
    node [shape=box];
    {rank=same; A -- 0 -- B;}
    1 -- C;
    1 -- D;
    3 -- E;
    3 -- F;
}

双链表和树

来源:https://stackoverflow.com/questions/13898151/drawing-outgoing-edges-starting-from-the-center-of-record-elements-in-graphviz

digraph G {
    node [shape = record];
    edge[headclip=false, tailclip=false];

    node0 [ label ="<f0> | <f1> J | <f2> "];
    node1 [ label ="<f0> | <f1> E | <f2> "];
    node4 [ label ="<f0> | <f1> C | <f2> "];
    node6 [ label ="<f0> | <f1> I | <f2> "];
    node2 [ label ="<f0> | <f1> U | <f2> "];
    node5 [ label ="<f0> | <f1> N | <f2> "];
    node9 [ label ="<f0> | <f1> Y | <f2> "];
    node8 [ label ="<f0> | <f1> W | <f2> "];
    node10 [ label ="<f0> | <f1> Z | <f2> "];
    node7 [ label ="<f0> | <f1> A | <f2> "];
    node3 [ label ="<f0> | <f1> G | <f2> "];

    // identical result: "node0" -> "node1";
    "node0":f1:c -> "node1":f1:c;
    "node0":f1:c -> "node2":f1:c;

    "node1":f1:c -> "node4":f1:c;
    "node1":f1:c -> "node6":f1:c;
    "node4":f1:c -> "node7":f1:c;
    "node4":f1:c -> "node3":f1:c;

    "node2":f1:c -> "node5":f1:c;
    "node2":f1:c -> "node9":f1:c;

    "node9":f1:c -> "node8":f1:c;
    "node9":f1:c -> "node10":f1:c;
}

连接子图

来源:https://stackoverflow.com/questions/2012036/graphviz-how-to-connect-subgraphs

digraph G {

    graph [fontsize=10 fontname="Verdana" compound=true];
    node [shape=record fontsize=10 fontname="Verdana"];

    subgraph cluster_0 {
        node [style=filled];
        "Item 1" "Item 2";
        label = "Container A";
        color=blue;
    }

    subgraph cluster_1 {
        node [style=filled];
        "Item 3" "Item 4";
        label = "Container B";
        color=blue;
    }

    subgraph cluster_2 {
        node [style=filled];
        "Item 5" "Item 6";
        label = "Container C";
        color=blue;
    }

    // Edges between nodes render fine
    "Item 1" -> "Item 2";
    "Item 2" -> "Item 3";

    // Edges that directly connect one cluster to another
    "Item 1" -> "Item 3" [ltail=cluster_0 lhead=cluster_1];
    "Item 1" -> "Item 5" [ltail=cluster_0 lhead=cluster_2];
}

另一个连接子图

来源同上。

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

其他

. 关键字不区别大小写。

. 有向图用 digraph ,无向图用 graph ,有向图节点之间使用 -> 连接,无向图节点使用 -- 连接。

. 支持注释,支持C风格的注释, /*...*/ 多行注释, // 单行注释,也可以用Shell风格的 # 开头的注释。

. 语句结尾的分号可选。

. 给图加上strict 关键字可消除重复的边,对有向图和无向图都适用。






















  • 无标签