摘要本文会介绍使用 graphviz 来绘制树形图。同时介绍使用 python 中的 graphviz 来简单 graphviz 的使用。
简介
graphviz
是一个绘图工具,可以根据 dot
脚本画出树形图等。本文会介绍 graphviz
的简单使用,使用 graphviz
来创建 dot
文件来绘制树形图。本文还会通过几个 graphviz
的例子,来介绍 graphviz
的使用。
参考资料
- graphviz 的中文文档,graphviz 中文文档
- dot 语言介绍(想要更多了解的时候可以查看),graphviz dot 语言介绍
- python graphviz 的入门文档,graphviz user guide
- python graphviz 的 example 介绍,graphviz 的例子
- graphviz 的下载,graphviz download
- 使用 graphviz 的例子,生成项目uml框架图-pyreverse介绍
graphviz 的简单介绍
graphviz 编译的源文件是 dot 文件。我们通过书写 dot 文件,来获得不同的树状图。首先需要安装 graphviz,可以通过下面的链接进行安装,graphviz download。关于 graphviz 的说明,可以参考这个链接,有详细的例子解释,graphviz 中文文档。
绘制简单树形图
我们将如下代码保存为 graph01.dot 文件:
- digraph g {
- main -> parse -> execute;
- main -> init;
- main -> cleanup;
- execute -> make_string;
- execute -> printf;
- execute -> compare;
- }
接着使用命令 dot -tjpg ./awesome_project/graph.dot -o graph01.jpg
编译,可以得到名为 graph01.jpg 的图片,图片内容如下所示。
绘制更加复杂的树形图-node 和 edge 的样式
上面是一个简单的树状图的绘制,下面我们来看一个稍微复杂的情况,有以下的几个需求:
- 给 node 和 edge 添加格式,颜色,加粗,是否虚线;
- 在箭头上添加文字,例如使用
a->b [style="dashed", color="skyblue"]
; - 支持中文,在 node 上指定 fontname 即可,
fontname="microsoft yahei"
- 指定箭头的方向,这里在连接部分使用
dir=none
,dir=both
,dir=forward
,dir=back
;
我们直接看一下最终的代码:
- digraph g {
- main [shape=box];
- main -> parse [weight=8];
- parse-> execute;
- main -> init [style=dotted, dir = none];
- main -> cleanup [dir = both];
- execute -> {make_string, printf};
- init -> make_string;
- edge [color=red];
- main -> printf [style=bold, label="100 times"];
- make_string [label = "make a\nstring"];
- node [shape=box, style=filled,color=".7, .3, 1.0", fontname="microsoft yahei"];
- execute -> 比较;
- }
最终的效果如下所示:
定义子图和子图的样式
graphviz 支持子图,即图中的部分节点和边相对对立(软件的模块划分经常如此)。比如在下面的例子中,我们将「流量生成」,「批量流量还原」和「真实流量还原」是一个子图。「仿真环境」等是一个模块,完整的代码如下(需要注意,子图的名称必须以 cluster
开头,否则 graphviz
无法设别):
- digraph g {
- sumo_rl [style="filled", fontsize = 20, color="black", fillcolor="chartreuse"];
- sumo_rl -> 流量生成 [color="red"];
- subgraph cluster_traffic{
- bgcolor="mintcream";
- label = "强化学习数据生成";
- 流量生成 -> 真实流量还原;
- 流量生成 -> 批量生成流量;
- }
- 强化学习训练 [style="dashed", color="yellowgreen"];
- 强化学习测试 [style="dashed", color="yellowgreen"];
- 批量生成流量 -> 强化学习训练 [style="dashed", color="skyblue"];
- 真实流量还原 -> 强化学习测试 [style="dashed", color="skyblue"];
- sumo_rl -> 仿真环境 [color="red"];
- subgraph cluster_rl_env{
- bgcolor = "mintcream";
- label = "强化学习交互环境";
- 仿真环境 -> 获得车辆属性;
- 获得车辆属性 -> 提取环境特征 [label="observation and reward"];
- 仿真环境 -> 异步控制不同信号灯 [label="action"];
- }
- 与sumo交互 [style="dashed", color="yellowgreen"];
- 提取环境特征 -> 与sumo交互 [style="dashed", color="skyblue"];
- 异步控制不同信号灯 -> 与sumo交互 [style="dashed", color="skyblue"];
- }
最终的效果如下图所示,可以看到我们设置了子图的背景色(同时也可以看一下 node 和 edge 的样式的设计):
在 vs code 中显示 dot 文件
我们可以安装 graphviz preview 插件来实时显示图像。在安装之前,我们需要确保 graphviz 已经下载安装好了(graphviz download),如果是 ubuntu
,直接使用 sudo apt install graphviz
安装即可。下图是 graphviz preview
插件,在全部安装完毕之后,就可以显示了:
python graphviz 简单介绍
上面我们直接使用 dot 文件来生成树形图。但是这样还是有些不是很方便,特别是当树形图比较复杂的时候。这个时候就可以结合 python 的 graphviz 库来进行绘制。首先需要使用 pip 来安装 graphviz。
- pip install graphviz
最基础的 python graphviz 图像
我们用 graphviz 的例子来进行说明。首先创建一个最基础的 graphviz
图像,创建一个 hello -> world
的连接图。
- from graphviz import digraph
- g = digraph('g', filename='hello.gv')
- g.node('node1', label='hello')
- g.node('node2', label='world')
- g.edge('node1', 'node2')
- g.view()
- 上面我们首先初始化了一个 digraph 类,它可以让我们设置这个
graph
的名称,上面是'g'
,和最后dot
文件的名称,即filename
这个参数。 - 接着我们使用
node
来创建节点,其中第一个参数是node
的name
,第二个参数是node
的label
,也就是最终会显示在图中的文字。 - 最后使用
edge
来将node
连起来。edge
的参数为node
的name
,第一个为起点,第二个为终点。
最终绘制出的结果如下所示:
同时,我们可以通过 g.source 来获取 dot 代码。获取的结果如下所示:
给树形图增加样式
在上面的基础上,我们可以增加树形图的样式。例如我们可以修改 edge 连接的箭头的样式。直接在上面初始化 digraph 之后进行修改。
- g.edge_attr.update(arrowhead='vee', arrowsize='2') # edge 的样式
此时的树形图如下所示:
上面是从整体上修改整个图的样式。我们也可以单独修改某个 node 的样子。下面我们把某个 node 的样子修改为五角星。只需要直接在 node 中增加 shape 的参数,如下所示:
- g.node('node1', label='hello', shape='star')
- g.node('node2', label='world', shape='egg')
最终的效果图如下所示,关于 node 支持的形状,可以查看链接,polygon-based nodes:
我们还可以是设置更加复杂的 node 的样式,可以使用类似 html 的 label。这些被写在 label 下,使用<
,>
来包裹内容。下面看一个例子,需要注意的是,下面在 digraph 中使用了 node_atrr,会对整个图的 node 全局有效,这里是使得 node 只显示文字,不显示边框:
- from graphviz import digraph
- g = digraph('g', node_attr={'shape': 'plaintext'}, filename='hello.gv')
- g.graph_attr['rankdir'] = 'lr'
- g.edge_attr.update(arrowhead='vee', arrowsize='2') # edge 的样式
- g.node('node1', label='hello', shape='star')
- g.node('node2', label='world', shape='egg')
- g.node('tab', label='''<