Graphviz
是有门槛的,在日常工作中,程序员可能也不会使用这个工具,大家普遍更接受 processon
以及 excalidraw
这种拖拽式的作图工具。Graphviz
更偏向由程序自动作图,直接将业务上的模块通过可视化的图形展示出来。
我比较喜欢Graphviz
的代码生成特性,业务流程每隔一段时间都会有变化,对应的业务流程图也需要做调整,这个时候只需要简单的更新代码就可以,可维护性要比拖拽式的要好。但Graphviz
也有缺点,在某些情况下很难用 Graphviz
编辑出理想中的排列结构。
选择 Graphviz 作为作图工具,主要是想通过代码创建图标,按照 Graphviz 的代码规范就可以生成 svg 的图片。当然,这样的工具也有很多,有些 markdown 编辑器也做了集成,比如:
了解 Graphviz 的第一步是官方文档,对象和属性是画图的核心:Graphive 提供的可视化对象有哪些,以及这些对象可以设置的属性。
以属性 nodesep
来说明,它表示一个属性,作用的对象是 Graphs
,Graphs
表示无向图。通过文档说明,Graphviz 提供的可视化对象就显而易见。
下面我们利用官网的示例来操作一下,加深对 Graphviz 中对象和属性的认识。建议大家在可视化界面下原型查看效果。
在 Graphviz 中表示独立的元素,流程图的关键步骤。形状、大小、颜色、边界都是它的基本属性。如果存在多个 node 元素,页面上也可以控制多个 node 水平或垂直布局、间距、位置等。
下面代码列举了节点样式,大家可关注版本变更跟踪最新属性。代码中没有声明 fillcolor
或者 color
属性,节点填充色就是透明的。
digraph G {
rankdir=TB
node1 [style=filled label=filled]
node3 [style=diagonals label=diagonals]
node4 [style=rounded label=rounded]
node5 [style=dashed label=dashed]
node6 [style=dotted label=dotted]
node7 [style=solid label=solid]
node8 [style=bold label=bold]
}
声明一个节点的基础属性,后续创建的节点默认会继承 node 声明的属性,也可以重新声明覆盖这里的默认属性。
peripheries
2表示增加一个默认的边缘,0表示关闭边缘。
digraph G {
rankdir=TB
node [ peripheries=2, style=filled, color="#eecc80" ];
{ rank="same" 0 0.1 0.2 }
1 [ peripheries=0, style=filled, color="green" ];
0.1 -> 1
}
digraph {
nodesep=0.1;
node1; node2; node3;
}
三个节点是水平排列的,属性 nodesep
控制两个相邻节点的间距,间距单位是 inch
英尺。另外,这三个节点属于相同的 rank
,如果继续增加 node4、node5,节点不会发生换行,而是会一直水平向后延伸。
graph {
rankdir="TB"
ranksep=".3 equally"
node1 -- node2 -- node3;
// node1 -- node2 -- node3 [ style="invis" ];
}
graph
表示无向图,digraph
表示有向图,两者没有明显差别。无方向的连线使用 --
表示,图中的三个节点处于不同的 rank
,rankdir
指定从上到下排列,ranksep
指定相邻节点的间距。
某些情况下,我们并不希望看到节点间的连线,我们只是想表达一种并列关系,可以通过设置 edge
属性实现隐藏效果。Graphviz 中代码注释也是 //
,注释掉的代码就是隐藏连线的实现。
graph {
rankdir="TB"
ranksep=".3 equally"
edge [ style="invis" ]
node1 -- node2;
{ rank="same" node2 -- node3 };
}
统一设置 edge
对象属性,通过 rank
指定 node2、node3位于同一层,最终效果如下图。rank
简单理解为元素按照 rankdir
的方向排列,通过指定 rank
将多个元素固定在同一行。
下面是正三角形布局,和上面略有差异。下面要实现 node1 和 node2 在竖直方向对齐,绘制直接三角形。
graph {
rankdir="TB"
ranksep=".3 equally"
node1 -- node2;
node1 -- node3;
{ rank="same" node2 -- node3 };
}
rankdir
生效的条件是 node 间设置了 edge,只有这样 TB、LR 才会生效,表现出节点间的方向上的分层排放。如果要强制将某几个节点放在同一层,可以通过 rank="smae“ 属性做强制声明。
digraph G {
rankdir=TB
{ rank="same" 0 0.1 0.2 }
0 -> 1;
1 -> 2;
2 -> 3;
}
流程上的层次结构就有了,我们可以利用这样的模式做结构排版,再通过 style=“invis” 将这些 “位置控制” 的节点掩藏掉。
我们可以依赖 group
属性:如果两个节点设置了相同的 group
,节点间的连线会尽量保持直线。
下面的代码,如果给 node3 也指定相同的 group
,直角的效果就会消失。
graph {
rankdir="TB"
ranksep=".3 equally"
node1 [ group="g1" ]
node2 [ group="g1" ]
node1 -- node2;
node1 -- node3;
{ rank="same" node2 -- node3 };
}
使用 weight
也可以实现相同的效果,指定的 weight
值越大,节点间的连线就越短、越值、越垂直。下面代码绘制的效果和上图完全相同。
graph {
rankdir="TB"
ranksep=".3 equally"
node1 -- node2 [ weight=2 ];
node1 -- node3;
{ rank="same" node2 -- node3 };
}
digraph {
rankdir="TB"
ranksep=".3 equally"
node1 -> node2 [weight=100];
node1 -> node3;
node2 -> node3 [ constraint=false ];
}
同介绍 weight
属性的代码相似,constraint
的作用对象是 edge
,属性值为 false 表示不受 rank
约束,最终的效果是 node2 和 node3 位于统一层。
下面的示例,虽然 rankdir
设置为 TB,但因为 constraint
设置为 false,最终的效果是从左到右,而不是从上到下。
digraph {
rankdir="TB"
ranksep=".3 equally"
node1 -> node2 -> node3 -> node4 [ constraint=false ];
}
结构图一般都有多个模块组成,每个模块内部又包含完整的处理流程。在 Graphviz 中使用 subgraph
来表示子图。
网上找了 subgrahp
的示例,注意观察:subgraph
后的命名都是以 cluster
为前缀的,并通过指定 compound
、ltail
、lhead
来实现 subgraph
间的连线。观察 "Item 1" -> "Item 3"
的连线属性,这里实现了在两个 subgraph
间创建连线。
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];
}
节点间的连线非常不好把握,有时候特别想连线到目标节点中间,但箭头偏偏指在了别处,虽然也将意思表达清楚了,但就是看起来不舒服。