Graphviz基础教程

Graphviz是一个开源的图可视化工具。它可将DOT语言描述的图结构渲染为SVG、PNG、PDF等图像。在图结构可视化分析、系统结构图可视化多个场景都可用。DOT语言作为文本形式表示的语言,可以轻松地纳入Git中进行版本管理。

Graphviz的使用上,既可以通过安装Graphviz软件进行本地渲染,也可以通过部分网站在线渲染。部分网站还能生成具有特定风格的图像。

下图展示了一个通过在线生成的Sketchy风格图结构。

Graphviz快速入门

在使用Graphviz时,只需编辑DOT文件并即时查看对应效果,不断调整至完成即可。在这一过程中,我们只需要对DOT语言有简单的了解即可实现一般的图结构可视化工作。下面通过一些例子来演示DOT语言与Graphviz的用法。

在数据结构中,图可以分为有向图与无向图。Graphviz也继承了这种概念,将图划分为有向图与无向图两种。它们在语法上有所区别。

有向图

有向图中,节点间的边具有方向,如节点A与节点B间的边可以有“A到B”和“B到A”两类,它们在图中表示为带箭头的线。这类图使用 digraph 作为关键字,并使用 A -> B 这样的形式来表示图中的边。

下面是一个简单的有向图。我们通过这个例子来分析DOT中的语法元素。

1
2
3
4
5
6
digraph {
a -> "b";
c[label="node label"]
b -> c -> d
d -> a
}

在DOT中,最外层通过 digraph 表示我们希望绘制一个有向图,并在随后的大括号内描述我们所需的有向图结构。其中,我们通过声明给出需要Graphviz为我们绘制的图元素,如节点、边等。在进行声明时,我们通常以行作为单位,在一行中描述一组关系,并在一行描述完成后可选地添加分号作为分隔符。

通过 nodeID[key=value] 的形式,可以声明一个节点。当不指定额外的属性,且希望节点显示的标签与id一致时,我们可以简化声明,直接给出节点标签。当节点标签的内容包含特殊字符(如空格)时,可以通过双引号来避免混淆。通过给定一些额外的属性,我们可以对节点的形状、颜色等内容进行调整,以适应我们的需要。

有向图中的边通过 A -> B [key=value] 的形式来描述,且可以通过连续的 -> 来一次性给出多个节点间的边。其中,A、B为图中的节点,当这些节点没有在图中另外声明时,会隐式地加入这些新的节点。与节点类似,边也可以通过给定额外的属性来调整标签、箭头形状等表现。

无向图

与有向图对应,无向图中“A到B”与“B到A”实际上描述同一个关系,在图中表示为不带箭头的线。

如下面的例子所示,无向图与有向图类似。区别在于无向图使用graph作为关键字,并通过--来表示节点间的边。

1
2
3
4
5
6
7
8
graph {
a -- b;
b -- c;
a -- c;
d -- c;
e -- c;
e -- a;
}

渲染工具

在编写完DOT语法描述的图结构后,我们需要使用渲染工具来生成可视化的图像。在进行渲染时,我们可以将Graphviz安装到本地来生成图像,也可以通过在线工具进行渲染。下面我推荐两个在线的Graphviz编写与渲染工具。

GraphvizOnline. 可以在这个页面上进行DOT语言的输入,并实时地生成对应的图像。这个页面上提供了多种渲染引擎和输出格式。通过选择不同的渲染引擎,可以将相同的图描述生成为具有不同特点的图像,在使用时可以对引擎进行调整并选用效果最好的结果。而输出格式的调整则可以用于生成svg、png等不同的格式,可用于不同的使用场景。

Sketchviz . 可以在这个网站上生成具有Sketchy风格的图像,提高一些趣味性。但这个网站没有提供渲染引擎和输出格式的选择,只能保存为png图片。

高级用法与示例

通过前面的两个例子,我们可以了解到DOT语言的基本用法。下面我们结合示例来了解一些额外的技巧和用法。这里的技巧通常不区分有向图与无向图,在两类图形中均可使用。

子图与全连接神经网络

在绘制全连接神经网络时,在两个相邻的层之间,所有节点都有连接。如果采用前面的节点间两两连接的描述方式,需要编写大量内容。通过子图,我们可以通过很少的描述语句实现所需的效果。

1
2
3
4
5
digraph G {
node[label=""];
{L11 L12 L13 L14} -> {L21 L22 L23 L24};
{L21 L22 L23 L24} -> {L31 L32 L33 L34} -> L4;
}

如上面的例子所示,我们通过大括号生成了一些子图(L1、L2、L3)。在指定边时,我们没有将边直接指定在节点上,而是通过指定子图间的关系,为不同子图中的节点间生成了全连接的边。使用这一技巧时,实际上并没有限定一个节点只能在一个子图中,因此我们可以在某些子图间全连接的同时在另外的子图中略去部分节点。这在带来大量的简化的同时保留了巨大的灵活性。

在这个例子中,我们还通过node[label=””]给出了后续节点的默认属性。它将后面生成的节点标签全部调整为空字符串。这一特点也同样可以应用在边(edge)、子图(subgraph)等对象上。

聚簇

我们有时会希望将部分节点进行分组,并在图形上表示出这种特殊的关系。在部分渲染引擎中,可以通过将子图命名为以cluster开头的名字来为子图生成边框。

1
2
3
4
5
digraph G {
node[label=""]
subgraph clusterA {L21 L22 L23 L24};
{L11 L12 L13 L14} -> {L21 L22 L23 L24} -> L3;
}

参考资料