欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(转)Graphviz 画图的一些总结

程序员文章站 2022-04-26 22:25:28
...

https://www.cnblogs.com/shuqin/p/11897207.html

Graphviz

Graphviz 是一个自动排版的作图软件,可以生成 png pdf 等格式。

dot 语言

Graphviz 构建组件为 图,节点,边,用属性对其进行描述。

以下是定义DOT语言的抽象语法,约束的规则如下:

  • 元素的终止以 粗体 显示
  • 文字字符用单引号 '' 引起来
  • 圆括号 () 的内容为必选项
  • 方括号 [] 为可选项目
  • 竖杠 | 为择一选择
声明 结构
graph [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}'
stmt_list [ stmt [ ';' ] stmt_list ]
stmt node_stmt | edge_stmt | attr_stmt | ID '=' ID | subgraph
attr_stmt (graph | node | edge) attr_list
attr_list '[' [ a_list ] ']' [ attr_list ]
a_list ID '=' ID [ (';' | ',') ] [ a_list ]
edge_stmt (node_id | subgraph) edgeRHS [ attr_list ]
edgeRHS edgeop (node_id | subgraph) [ edgeRHS ]
node_stmt node_id [ attr_list ]
node_id ID [ port ]
port ':' ID [ ':' compass_pt ] | ':' compass_pt
subgraph [ subgraph [ ID ] ] '{' stmt_list '}'
compass_pt (n | ne | e | se | s | sw | w | nw | c | _)

ID 其实就是一个字符串,为该组件的名称或者属性的名称,命名规则如下:

  1. 所有的字母 [a-zA-Z\200-\377] 下划线,数字 [0-9],数字不能出现在起始位置
  2. 纯数字 $[-]?(.[0-9]+ | [0-9]^+(.[0-9]*)6? $
  3. 所有用双引号引用的字符串 "..."
  4. HTML 格式的字符串 <>

dot 语法的关键字

  • strict, 严格的图限定,禁止创建多个相同的边
  • graph, 无向图. 在图的创建时必须声明为有向图还是无向图
  • digraph, 有向图
  • node, 节点
  • edge, 边
  • subgraph, 子图

通过 dot 的抽象语法可以看到

  1. 整个 graph 必须使用 graph 或 digraph {} 进行限定说明图的属性
  2. 图里面的声明列表可以为空,也可以为多个,每个声明后的 ; 为可选项
  3. 声明有几种类型
    1. 节点 node
    2. edge
    3. 子图 subgraph
    4. 属性列表
    5. ID = ID, 这个类型暂时还没有看到有什么作用
  4. 属性列表
    1. 必须使用中括号 [ ] 将列表项括起来
    2. 列表项为可选
  5. 属性列表项
    1. 以 key = value 的形式存在,列表项可选择 ',' 和 ';' 结尾
    2. 可存在多个列表项
  6. 边的声明
    1. 首端为 节点标识符或者子图,
    2. 右部分由边连接节点标识符或者子图构成,右部分可以存在多个
    3. 尾部可选属性列表
  7. 节点的声明
    ```cpp
    示例 节点的用法
    node0 [label = " string| string| string3", height=.5]`
    node0:head[color=lightblue]  // 设置该部分的颜色
    ```
    

  1. 首部为节点标识符 节点部分(post) 方向 组成,其中后两项为可选项。
  2. 后半部分为可选的属性列表
方向 说明
n north 北
ne north east
e east 东
se south east 东南
s south 南
sw south west 西南
w west 西
nw north west 西北
c center 中部
_ 任意

一个方向的示例

digraph action {
    node [shape = record,height=.1];

    node0 [label = "<head> head|<body> body|<foot> foot", height=.5]
    node2 [shape = box label="mind"]

    node0:head:n -> node2:n [label = "n"]
    node0:head:ne -> node2:ne [label = "ne"]
    node0:head:e -> node2:e [label = "e"]
    node0:head:se -> node2:se [label = "se"]
    node0:head:s -> node2:s [label = "s"]
    node0:head:sw -> node2:sw [label = "sw"]
    node0:head:w -> node2:w [label = "w"]
    node0:head:nw -> node2:nw [label = "nw"]
    node0:head:c -> node2:c [label = "c"]
    node0:head:_ -> node2:_ [label = "_"]

    node0:body[style=filled color=lightblue]
}

效果如下 图-1
(转)Graphviz 画图的一些总结

绘制属性

一个图中有非常多的 node 和 edge,如果每次都需要声明一个节点的属性会非常麻烦,有一个简单的方式为声明一个公共的属性如

digraph action {
    rankdir = LR // 设置方向
    node [shape=box color=blue]
    edge [color=red]

    node1 // 默认节点属性
    node2 [color=lightblue] // 属于该节点的颜色属性
    node1 -> node2 // 默认边属性 
    node2 -> node1 [color=green] // 属于该变的属性
}

在声明位置之后的节点都有一个 默认 的形状和颜色属性。
(转)Graphviz 画图的一些总结

全部的属性见graphviz官网,这里列举部分常用的属性

  • charset 编码,一般设置 UTF-8
  • fontname 字体名称,这个在中文的情况需要设置,否则导出图片的时候会乱码,一般设置微软雅黑("Microsoft YaHei"), linux 下也是同样设置系统带的字体就好,其他字体设置见fontpath 属性
  • fontcolor 字体颜色
  • fontsize 字体大小,用于文本内容
  • fillcolor 用于填充节点或者集群(cluster)的背景颜色。
  • size 图形的最大宽度和高度
  • label 图形上的文本标记
  • margin 设置图形的边距
  • pad 指定将绘制区域扩展到绘制图形所需的最小区域的长度(以英寸为单位)
  • style 设置图形组件的样式信息。 对于聚类子图或者节点,如果style = "filled",则填充聚类框的背景 (转)Graphviz 画图的一些总结
  • rankdir 设置图形布局的排列方向 (全局只有一个生效). "TB", "LR", "BT", "RL", 分别对应于从上到下,从左到右,从下到上和从右到左绘制的有向图。
    • (转)Graphviz 画图的一些总结
    • (转)Graphviz 画图的一些总结
  • ranksep 以英寸为单位提供所需的排列间隔
  • ratio 设置生成图片的纵横比

节点(node)

节点的默认属性为 shape = ellipse, width = .75, height = 0.5 并且用节点标识符作为节点的显示文字。

如图一中所示,声明两个节点 node0 和 node2,node0 或 node2 就表示这个节点的节点标识符,后面紧跟的是该节点的属性列表;另一种用法为 节点标识符:节点部分:方向[属性列表] node0:body[style=filled color=lightblue], 这个为单一节点声明的方式。

节点中最基本的属性为:

  • shape 形状,全部形状见graphviz官网,一些常用的图形有(转)Graphviz 画图的一些总结
  • width height, 图形的宽度和高度,如果设置了 fixedsize 为 true,则宽和高为最终的长度
  • fixedsize, 如果为false,节点的大小由其文本内容所需要的最小值决定
  • rank 子图中节点上的排列等级约束. 最小等级是最顶部或最左侧,最大等级是最底部或最右侧。
    • same. 所有节点都位于同一等级
    • min. 所有节点都位于最小等级上
    • source. 所有节点都位于最小等级上,并且最小等级上的唯一节点属于某个等级 source 或 min 的子图.
    • max sink. 和上类似

边 (edge)

有向图中的的边用 -> 表示,无向图用 -- 表示。

可以同时连接多个节点或者子图,但是只能有一个属性列表,如下

digraph {
	rankdir = LR
	A -> B -> c[color=green]
}

(转)Graphviz 画图的一些总结

一些关于边的属性如下:

digraph {
	rankdir = LR
	splines = ortho
A -&gt; B -&gt; C -&gt; D -&gt; F [color = green]
E -&gt; F -&gt; B -&gt; D [color = blue]
B -&gt; E -&gt; H[color = red]

}

  • len 首选边的长度

  • weight 边的权重, 权重越大越接近边的长度

  • lhead 逻辑边缘的头部(箭头那个位置),compound 设置为 true 时,边被裁减到子图的边界处

  • ltail 类似 lhead

  • headlabel 边上靠近箭头部分的标签

  • taillabel 边上靠近尾部部分的标签
    设置 A->B->C->D->F的权重最大,修改绿色的分支的权重为 100,使其变成主要逻辑分支。
    (转)Graphviz 画图的一些总结

  • splines 控制如何以及是否表示边缘。其值如下

    • none 或者 "", 无边 (转)Graphviz 画图的一些总结
    • true 或者 spline, 样条线(无规则,可为直或者曲线)(转)Graphviz 画图的一些总结
    • false 或者 line, 直线段 (转)Graphviz 画图的一些总结
    • polyline, 折线 (转)Graphviz 画图的一些总结
    • curved, 曲弧线,两条? (转)Graphviz 画图的一些总结
    • ortho, 正直的线(横竖)(转)Graphviz 画图的一些总结
  • dir 设置绘制箭头的边缘类型
    (转)Graphviz 画图的一些总结

子图

subgraph 必须配合 cluster 一起使用,用法为 subgraph cluster* {}

需要设置 compound 为 true,则在群集之间留出边缘,子图的边界关系在 边 的定义中有给出,这里直接给个示例。

digraph G {
	compound = true  // 允许子图间存在边
	ranksep = 1
	node [shape = record]
subgraph cluster_hardware {
	<span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"hardware"</span>

color = lightblue
CPU Memory
}

subgraph cluster_kernel {
	<span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"kernel"</span>

color = green
Init IPC
}

subgraph cluster_libc {
	<span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"libc"</span>

color = yellow
glibc
}

CPU -&gt; Init [lhead = cluster_kernel ltail = cluster_hardware]
IPC -&gt; glibc [lhead = cluster_libc ltail = cluster_kernel]

}

(转)Graphviz 画图的一些总结

示例

TCP IP 状态流程图

展示了两个版本,怎么把这些图形节点稍微规范的显示出来

digraph {
	compound=true
	fontsize=10
	margin="0,0"
	ranksep = .75
	nodesep = .65
node [shape=Mrecord fontname=<span class="hljs-string">"Inconsolata, Consolas"</span>, fontsize=<span class="hljs-number">12</span>, penwidth=<span class="hljs-number">0.5</span>]
edge [fontname=<span class="hljs-string">"Inconsolata, Consolas"</span>, fontsize=<span class="hljs-number">10</span>, arrowhead=normal]


<span class="hljs-string">"TCP/IP State Transition"</span> [shape = <span class="hljs-string">"plaintext"</span>, fontsize = <span class="hljs-number">16</span>]

// now start server state transition
<span class="hljs-string">"CLOSED"</span> -&gt; <span class="hljs-string">"LISTEN"</span> [style = blod, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用:被动打开\n发送:&lt;无&gt;"</span>];

“LISTEN” -> “SENT_REVD” [style = blod, label = “接收:SYN\n发送:SYN,ACK”]
“SENT_REVD” -> “ESTABLISHED” [style = blod, label = “接收:ACK\n发送:<无>”, weight = 20]
“ESTABLISHED” -> “CLOSE_WAIT” [style = blod, label = “接收:FIN\n发送:ACK”, weight = 20]

subgraph cluster_passive_close {
style = dotted
margin = 10

	passive_close [shape = plaintext, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"被动关闭"</span>, fontsize = 14]
"CLOSE_WAIT" -> "LAST_ACK" [style = blod, label = "应用:关闭\n发送:FIN", weight = 10] } "LAST_ACK" -> "CLOSED" [style = blod, label = "接收:ACK\n发送:<无>"] // now start client state transition "CLOSED" -> "SYN_SENT" [style = dashed, label = "应用:主动打开\n发送:SYN"]; "SYN_SENT" -> "ESTABLISHED" [style = dashed, label = "接收:SYN,ACK\n发送:ACK", weight = 25] "SYN_SENT" -> "SENT_REVD" [style = dotted, label = "接收:SYN\n发送:SYN,ACK\n同时打开"] "ESTABLISHED" -> "FIN_WAIT_1" [style = dashed, label = "应用:关闭\n发送:FIN", weight = 20] subgraph cluster_active_close { style = dotted margin = 10
	active_open [shape = plaintext, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"主动关闭"</span>, fontsize = 14]
"FIN_WAIT_1" -> "FIN_WAIT_2" [style = dashed, label = "接收:ACK\n发送:<无>"] "FIN_WAIT_2" -> "TIME_WAIT" [style = dashed, label = "接收:FIN\n发送:ACK"] "FIN_WAIT_1" -> "CLOSING" [style = dotted, label = "接收:ACK\n发送:<无>"] "FIN_WAIT_1" -> "TIME_WAIT" [style = dotted, label = "接收:SYN,ACK\n发送:ACK"] "CLOSING" -> "TIME_WAIT" [style = dotted] }
<span class="hljs-string">"TIME_WAIT"</span> -&gt; <span class="hljs-string">"CLOSED"</span> [style = dashed, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"2MSL超时"</span>]

}

这是一个很挫的版本,排版乱飞了。
(转)Graphviz 画图的一些总结

digraph rankdot {
	compound=true
	margin="0,0"
	ranksep = .75
	nodesep = 1
	pad = .5
	//splines = ortho
node [shape=Mrecord, charset = <span class="hljs-string">"UTF-8"</span> fontname=<span class="hljs-string">"Microsoft YaHei"</span>, fontsize=<span class="hljs-number">14</span>]
edge [charset = <span class="hljs-string">"UTF-8"</span> fontname=<span class="hljs-string">"Microsoft YaHei"</span>, fontsize=<span class="hljs-number">11</span>, arrowhead = normal]


CLOSED -&gt; LISTEN [style = dashed, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用:被动打开\n发送:&lt;无&gt;"</span>, weight = 100];
"TCP/IP State Transition" [shape = "plaintext", fontsize = 16]
{
	rank = same
	SYN_RCVD SYN_SENT
	point_1 [shape = point, width = <span class="hljs-number">0</span>]
	
	SYN_SENT -&gt; point_1 [style = dotted, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用关闭或者超时"</span>]

// SYN_SENT -> SYN_RCVD 这个一行代码和上一行冲突了,syn_sent 会在syn_rcvd右边
SYN_RCVD -> SYN_SENT [style = dotted, dir = back, headlabel = “接收:SYN\n发送:SYN,ACK\n同时打开”]
}

LISTEN -&gt; SYN_RCVD [style = dashed, headlabel = <span class="hljs-string">"接收:SYN\n发送:SYN,ACK"</span>]
SYN_RCVD -&gt; LISTEN [style = dotted, headlabel = <span class="hljs-string">"接收:RST"</span>]
CLOSED:es -&gt; SYN_SENT [style = blod, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用:主动打开\n发送:SYN"</span>]
{ rank = same ESTABLISHED CLOSE_WAIT
	ESTABLISHED -&gt; CLOSE_WAIT [style = dashed, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"接收:SYN,ACK\n发送:ACK"</span>]

}

SYN_RCVD -&gt; ESTABLISHED [style = dashed, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"接收:ACK\n发送:&lt;无&gt;"</span>, weight = 9]

SYN_SENT -> ESTABLISHED [style = blod, label = “接收:SYN,ACK\n发送:ACK”, weight = 10]

{
rank = same

	FIN_WAIT_1
	CLOSING 
	LAST_ACK
	point_2 [shape = point, width = <span class="hljs-number">0</span>]

	FIN_WAIT_1 -&gt; CLOSING [style = dotted, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"接收:FIN\n发送:ACK"</span>]

LAST_ACK -> point_2 [style = dashed, label = “接收:ACK\n发送:<无>”]
}

CLOSE_WAIT -&gt; LAST_ACK [style = dashed, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用:关闭\n发送:FIN"</span>, weight = 10]
{ rank = same FIN_WAIT_2 TIME_WAIT
	point_3 [shape = point, width = <span class="hljs-number">0</span>]
	TIME_WAIT -&gt; point_3 [style = blod, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"2MSL超时"</span>]

}

ESTABLISHED -&gt; FIN_WAIT_1 [style = blod, <span class="hljs-keyword">label</span><span class="bash"> = <span class="hljs-string">"应用:关闭\n发送:FIN"</span>]

FIN_WAIT_1 -> FIN_WAIT_2 [style = blod, headlabel = “接收:ACK\n发送:<无>”, weight = 15]
FIN_WAIT_2 -> TIME_WAIT [style = blod, label = “接收:FIN\n发送:ACK”, weight = 10]

CLOSING -> TIME_WAIT [style = dotted, label = “接收:ACK\n发送:<无>”, weight = 15]
FIN_WAIT_1 -> TIME_WAIT [style = dotted, label = “接收:ACK\n发送:<无>”]

point_3 -> point_2 [arrowhead = none, style = dotted, weight = 10]
point_2 -> point_1 [arrowhead = none, style = dotted]
point_1 -> CLOSED [style = dotted]
}

这个版本看起来有内味了,最最最的主要的原因就是我使用 rank = same 属性,将一些图形固定在 同一行,一些需要横竖的直线的地方使用 weight 来调整权重,达到横竖的直接的效果,很多地方都是微调的结果。有一个很差的地方是 使用了rank限制若干图形后,就不能使用 subgraph 属性了,这样就不能在若干不同部分的节点周边画线(对比关闭的区域)了。
(转)Graphviz 画图的一些总结

epoll 相关数据结构及关系

digraph rankdot {
	compound=true
	margin="0,0"
	ranksep = .75
	nodesep = 1
	pad = .5
	rankdir = LR
node [shape=record, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14]
edge [style = dashed, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11]

epoll [shape = plaintext, label = "epoll 相关结构及部分关系"]

eventpoll [
	color = cornflowerblue,
	label = "<span class="hljs-tag">&lt;<span class="hljs-name">eventpoll</span>&gt;</span> struct \n eventpoll |
		<span class="hljs-tag">&lt;<span class="hljs-name">lock</span>&gt;</span> spinlock_t lock; |
		<span class="hljs-tag">&lt;<span class="hljs-name">mutex</span>&gt;</span> struct mutex mtx; |
		<span class="hljs-tag">&lt;<span class="hljs-name">wq</span>&gt;</span> wait_queue_head_t wq; |
		<span class="hljs-tag">&lt;<span class="hljs-name">poll_wait</span>&gt;</span> wait_queue_head_t poll_wait; |
		<span class="hljs-tag">&lt;<span class="hljs-name">rdllist</span>&gt;</span> struct list_head rdllist; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ovflist</span>&gt;</span> struct epitem *ovflist; |
		<span class="hljs-tag">&lt;<span class="hljs-name">rbr</span>&gt;</span> struct rb_root_cached rbr; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ws</span>&gt;</span> struct wakeup_source *ws; |
		<span class="hljs-tag">&lt;<span class="hljs-name">user</span>&gt;</span> struct user_struct *user; |
		<span class="hljs-tag">&lt;<span class="hljs-name">file</span>&gt;</span> struct file *file; |
		<span class="hljs-tag">&lt;<span class="hljs-name">visited</span>&gt;</span> int visited; |
		<span class="hljs-tag">&lt;<span class="hljs-name">visited_list_link</span>&gt;</span> struct list_head visited_list_link;"
]

epitem [
	color = sienna,
	label = "<span class="hljs-tag">&lt;<span class="hljs-name">epitem</span>&gt;</span> struct \n epitem  |
		<span class="hljs-tag">&lt;<span class="hljs-name">rb</span>&gt;</span>struct rb_node rbn;\nstruct rcu_head rcu; |
		<span class="hljs-tag">&lt;<span class="hljs-name">rdllink</span>&gt;</span> struct list_head rdllink; |
		<span class="hljs-tag">&lt;<span class="hljs-name">next</span>&gt;</span> struct epitem *next; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ffd</span>&gt;</span> struct epoll_filefd ffd; |
		<span class="hljs-tag">&lt;<span class="hljs-name">nwait</span>&gt;</span> int nwait; |
		<span class="hljs-tag">&lt;<span class="hljs-name">pwqlist</span>&gt;</span> struct list_head pwqlist; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ep</span>&gt;</span> struct eventpoll *ep; |
		<span class="hljs-tag">&lt;<span class="hljs-name">fllink</span>&gt;</span> struct list_head fllink; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ws</span>&gt;</span> struct wakeup_source __rcu *ws; |
		<span class="hljs-tag">&lt;<span class="hljs-name">event</span>&gt;</span> struct epoll_event event;"
]

epitem2 [
	color = sienna,
	label = "<span class="hljs-tag">&lt;<span class="hljs-name">epitem</span>&gt;</span> struct \n epitem |
		<span class="hljs-tag">&lt;<span class="hljs-name">rb</span>&gt;</span>struct rb_node rbn;\nstruct rcu_head rcu; |
		<span class="hljs-tag">&lt;<span class="hljs-name">rdllink</span>&gt;</span> struct list_head rdllink; |
		<span class="hljs-tag">&lt;<span class="hljs-name">next</span>&gt;</span> struct epitem *next; |
		<span class="hljs-tag">&lt;<span class="hljs-name">ep</span>&gt;</span> struct eventpoll *ep; |
		 ··· |
		 ··· "
]

eppoll_entry [
	color = darkviolet,
	label = "<span class="hljs-tag">&lt;<span class="hljs-name">entry</span>&gt;</span> struct \n eppoll_entry |
		<span class="hljs-tag">&lt;<span class="hljs-name">llink</span>&gt;</span> struct list_head llink; |
		<span class="hljs-tag">&lt;<span class="hljs-name">base</span>&gt;</span> struct epitem *base; |
		<span class="hljs-tag">&lt;<span class="hljs-name">wait</span>&gt;</span> wait_queue_entry_t wait; |
		<span class="hljs-tag">&lt;<span class="hljs-name">whead</span>&gt;</span> wait_queue_head_t *whead;"
]

epitem:ep -&gt; eventpoll:se [color = sienna]
epitem2:ep -&gt; eventpoll:se [color = sienna]
eventpoll:ovflist -&gt; epitem:next -&gt; epitem2:next [color = cornflowerblue]
eventpoll:rdllist -&gt; epitem:rdllink -&gt; epitem2:rdllink [dir = both]
eppoll_entry:llink -&gt; epitem:pwqlist [color = darkviolet]
eppoll_entry:base -&gt; epitem:nw  [color = darkviolet]

}

(转)Graphviz 画图的一些总结

遗留问题

  1. 在以上TCP/IP 状态变迁图中,尝试增加主动关闭方的区域边框
  2. 尝试增加 TCP/IP 的时序图

使用 VSCode 进行预览生成

  1. 在官网下载graphviz安装包
  2. 安装 vscode 插件 Graphviz Preview
  3. 在 settings.json 中添加 "graphvizPreview.dotPath": "graphviz_path\graphviz-2.38\\release\\bin\\dot.exe" , graphviz_path 为所在路径,这些修改一下既可
  4. 新建一个 dot 文件,右上角就会有预览生成的按钮了

12/05 更新,用了一圈发现并没有那么好用,自动排版是优势,但有的时候也是劣势,需要固定位置的作图时还是手动控制比较好一些,ProcessOn 用了几次觉得很不错,推荐!

参考

  1. graphviz官方文档
相关标签: Graphviz 算法