菜鸟笔记
提升您的技术认知

python graphviz 的使用-ag真人游戏

摘要本文会介绍使用 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 文件:

  1. digraph g {
  2.     main -> parse -> execute;
  3.     main -> init;
  4.     main -> cleanup;
  5.     execute -> make_string;
  6.     execute -> printf;
  7.     execute -> compare;
  8. }

接着使用命令 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

我们直接看一下最终的代码:

  1. digraph g {
  2.     main [shape=box];
  3.     main -> parse [weight=8];
  4.     parse-> execute;
  5.     main -> init [style=dotted, dir = none];
  6.     main -> cleanup [dir = both];
  7.     execute -> {make_string, printf};
  8.     init -> make_string;
  9.     edge [color=red];
  10.     main -> printf [style=bold, label="100 times"];
  11.     make_string [label = "make a\nstring"];
  12.     node [shape=box, style=filled,color=".7, .3, 1.0", fontname="microsoft yahei"];
  13.     execute -> 比较;
  14. }

最终的效果如下所示:

 

定义子图和子图的样式

graphviz 支持子图,即图中的部分节点和边相对对立(软件的模块划分经常如此)。比如在下面的例子中,我们将「流量生成」,「批量流量还原」和「真实流量还原」是一个子图。「仿真环境」等是一个模块,完整的代码如下(需要注意,子图的名称必须以 cluster 开头,否则 graphviz 无法设别):

  1. digraph g {
  2.     sumo_rl [style="filled", fontsize = 20, color="black", fillcolor="chartreuse"];
  3.     sumo_rl -> 流量生成 [color="red"];
  4.     subgraph cluster_traffic{
  5.         bgcolor="mintcream";
  6.         label = "强化学习数据生成";
  7.         流量生成 -> 真实流量还原;
  8.         流量生成 -> 批量生成流量;
  9.     }
  10.     强化学习训练 [style="dashed", color="yellowgreen"];
  11.     强化学习测试 [style="dashed", color="yellowgreen"];
  12.     批量生成流量 -> 强化学习训练 [style="dashed", color="skyblue"];
  13.     真实流量还原 -> 强化学习测试 [style="dashed", color="skyblue"];
  14.     sumo_rl -> 仿真环境 [color="red"];
  15.     subgraph cluster_rl_env{
  16.         bgcolor = "mintcream";
  17.         label = "强化学习交互环境";
  18.         仿真环境 -> 获得车辆属性;
  19.         获得车辆属性 -> 提取环境特征 [label="observation and reward"];
  20.         仿真环境 -> 异步控制不同信号灯 [label="action"];
  21.     }
  22.     与sumo交互 [style="dashed", color="yellowgreen"];
  23.     提取环境特征 -> 与sumo交互 [style="dashed", color="skyblue"];
  24.     异步控制不同信号灯 -> 与sumo交互 [style="dashed", color="skyblue"];
  25. }

最终的效果如下图所示,可以看到我们设置了子图的背景色(同时也可以看一下 node 和 edge 的样式的设计):

 

在 vs code 中显示 dot 文件

我们可以安装 graphviz preview 插件来实时显示图像。在安装之前,我们需要确保 graphviz 已经下载安装好了(graphviz download),如果是 ubuntu,直接使用 sudo apt install graphviz 安装即可。下图是 graphviz preview 插件,在全部安装完毕之后,就可以显示了:

 

 

python graphviz 简单介绍

上面我们直接使用 dot 文件来生成树形图。但是这样还是有些不是很方便,特别是当树形图比较复杂的时候。这个时候就可以结合 python 的 graphviz 库来进行绘制。首先需要使用 pip 来安装 graphviz。

  1. pip install graphviz

 

最基础的 python graphviz 图像

我们用 graphviz 的例子来进行说明。首先创建一个最基础的 graphviz 图像,创建一个 hello -> world 的连接图。

  1. from graphviz import digraph
  2. g = digraph('g', filename='hello.gv')
  3. g.node('node1', label='hello')
  4. g.node('node2', label='world')
  5. g.edge('node1', 'node2')
  6. g.view()
  •  上面我们首先初始化了一个 digraph 类,它可以让我们设置这个 graph 的名称,上面是 'g',和最后 dot 文件的名称,即 filename 这个参数。
  • 接着我们使用 node 来创建节点,其中第一个参数是 nodename,第二个参数是 nodelabel,也就是最终会显示在图中的文字。
  • 最后使用 edge 来将 node 连起来。edge 的参数为 nodename,第一个为起点,第二个为终点。

最终绘制出的结果如下所示:

同时,我们可以通过 g.source 来获取 dot 代码。获取的结果如下所示:

 

给树形图增加样式

在上面的基础上,我们可以增加树形图的样式。例如我们可以修改 edge 连接的箭头的样式。直接在上面初始化 digraph 之后进行修改。

  1. g.edge_attr.update(arrowhead='vee', arrowsize='2') # edge 的样式

此时的树形图如下所示:

上面是从整体上修改整个图的样式。我们也可以单独修改某个 node 的样子。下面我们把某个 node 的样子修改为五角星。只需要直接在 node 中增加 shape 的参数,如下所示:

  1. g.node('node1', label='hello', shape='star')
  2. g.node('node2', label='world', shape='egg')

最终的效果图如下所示,关于 node 支持的形状,可以查看链接,polygon-based nodes:

我们还可以是设置更加复杂的 node 的样式,可以使用类似 html 的 label。这些被写在 label 下,使用<,> 来包裹内容。下面看一个例子,需要注意的是,下面在 digraph 中使用了 node_atrr,会对整个图的 node 全局有效,这里是使得 node 只显示文字,不显示边框:

  1. from graphviz import digraph
  2. g = digraph('g', node_attr={'shape': 'plaintext'}, filename='hello.gv')
  3. g.graph_attr['rankdir'] = 'lr'
  4. g.edge_attr.update(arrowhead='vee', arrowsize='2') # edge 的样式
  5. g.node('node1', label='hello', shape='star')
  6. g.node('node2', label='world', shape='egg')
  7. g.node('tab', label='''<
  8.  
left right >''')
  • g.edge('node1', 'node2')
  • g.edge('node1', 'tab')
  • g.view()
  • 最终的效果如下图所示:

     

    python graphviz 例子

    这里会记录一些 graphviz 的例子,一些其他的树形图可以基于这些例子做进一步的扩展。这一部分的内容参考自,graphviz 的例子。

    自定义 node 样式-fsm

    这个例子中,我们修改 node 的样式,使得如果通过 node 来进行定义,node 为双圆的样式。

    1. from graphviz import digraph
    2. f = digraph('finite_state_machine', filename='fsm.gv')
    3. f.attr(rankdir='lr', size='20,5')
    4. # 单独定义的 node 会有双圆结构
    5. f.attr('node', shape='doublecircle')
    6. f.node('lr_0')
    7. f.node('lr_3')
    8. f.node('lr_4')
    9. f.node('lr_8')
    10. f.attr('node', shape='circle')
    11. f.edge('lr_0', 'lr_2', label='ss(b)')
    12. f.edge('lr_0', 'lr_1', label='ss(s)')
    13. f.edge('lr_1', 'lr_3', label='s($end)')
    14. f.edge('lr_2', 'lr_6', label='ss(b)')
    15. f.edge('lr_2', 'lr_5', label='ss(a)')
    16. f.edge('lr_2', 'lr_4', label='s(a)')
    17. f.edge('lr_5', 'lr_7', label='s(b)')
    18. f.edge('lr_5', 'lr_5', label='s(a)')
    19. f.edge('lr_6', 'lr_6', label='s(b)')
    20. f.edge('lr_6', 'lr_5', label='s(a)')
    21. f.edge('lr_7', 'lr_8', label='s(b)')
    22. f.edge('lr_7', 'lr_5', label='s(a)')
    23. f.edge('lr_8', 'lr_6', label='s(b)')
    24. f.edge('lr_8', 'lr_5', label='s(a)')
    25. f.view()

    最终的结果如下所示,可以看到上面单独定义的,lr_0、lr_3、lr_4、lr_8 都是两个圆:

     

    包含子图的情况

    我们可以创建多个 graph,不同的 graph 的样式不同,最后将他们合并。直接看一下下面的例子。

    1. from graphviz import digraph
    2. g = digraph('g', filename='cluster.gv')
    3. # note: the subgraph name needs to begin with 'cluster' (all lowercase)
    4. #       so that graphviz recognizes it as a special cluster subgraph
    5. with g.subgraph(name='cluster_0') as c:
    6.     c.attr(style='filled', color='lightgrey')
    7.     c.node_attr.update(style='filled', color='white')
    8.     c.edges([('a0', 'a1'), ('a1', 'a2'), ('a2', 'a3')])
    9.     c.attr(label='process #1')
    10. with g.subgraph(name='cluster_1') as c:
    11.     c.attr(color='blue')
    12.     c.node_attr['style'] = 'filled'
    13.     c.edges([('b0', 'b1'), ('b1', 'b2'), ('b2', 'b3')])
    14.     c.attr(label='process #2')
    15. g.edge('start', 'a0')
    16. g.edge('start', 'b0')
    17. g.edge('a1', 'b3')
    18. g.edge('b2', 'a3')
    19. g.edge('a3', 'a0')
    20. g.edge('a3', 'end')
    21. g.edge('b3', 'end')
    22. g.node('start', shape='mdiamond')
    23. g.node('end', shape='msquare')
    24. g.view()

    最终的结果如下所示,左侧的使用灰色进行填充,右侧的为蓝色的边框:

     

    使用类 html 标签

    上面我们介绍过类 html 标签来进行绘制,实际上我们可以进行一些简化,如下所示:

    1. from graphviz import digraph
    2. s = digraph('structs', filename='structs_revisited.gv',
    3.             node_attr={'shape': 'record'})
    4. s.node('struct1', ' left| middle| right')
    5. s.node('struct2', ' one| two')
    6. s.node('struct3', r'hello\nworld |{ b |{c| d|e}| f}| g | h')
    7. s.edges([('struct1:f1', 'struct2:f0'), ('struct1:f2', 'struct3:here')])
    8. s.view()

    最终的结果如下所示,上面的 f0 等其实都是 label,连线的时候会根据这些 label 进行连线。

    我们把上面的 f0 等都去掉,做一些变化:

    1. from graphviz import digraph
    2. s = digraph('structs', filename='structs_revisited.gv',
    3.             node_attr={'shape': 'record'})
    4. s.node('struct1', 'left| middle| right')
    5. s.node('struct2', '{a|{b1|b2|b3}|c}')
    6. s.node('struct3', r'hello\nworld |{ b |{c|d|e}| f}| g | h')
    7. s.edges([('struct1', 'struct2'), ('struct1', 'struct3')])
    8. s.view()

    可以得到下面的图,有横向和纵向的分割:

     

    绘制树结构

    下面使用 graphviz 来绘制二叉树:

    1. from graphviz import digraph, nohtml
    2. g = digraph('g', filename='btree.gv',
    3.             node_attr={'shape': 'record', 'height': '.1'})
    4. g.node('node0', nohtml(' | g|'))
    5. g.node('node1', nohtml(' | e|'))
    6. g.node('node2', nohtml(' | b|'))
    7. g.node('node3', nohtml(' | f|'))
    8. g.node('node4', nohtml(' | r|'))
    9. g.node('node5', nohtml(' | h|'))
    10. g.node('node6', nohtml(' | y|'))
    11. g.node('node7', nohtml(' | a|'))
    12. g.node('node8', nohtml(' | c|'))
    13. g.edge('node0:f2', 'node4:f1')
    14. g.edge('node0:f0', 'node1:f1')
    15. g.edge('node1:f0', 'node2:f1')
    16. g.edge('node1:f2', 'node3:f1')
    17. g.edge('node2:f2', 'node8:f1')
    18. g.edge('node2:f0', 'node7:f1')
    19. g.edge('node4:f2', 'node6:f1')
    20. g.edge('node4:f0', 'node5:f1')
    21. g.view()

    最终的结果如下所示:

    相关推荐

    网站地图