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 其實就是一個字符串,為該組件的名稱或者屬性的名稱,命名規則如下:
- 所有的字母
[a-zA-Z\200-\377]
下劃線,数字[0-9]
,数字不能出現在起始位置 - 純数字 $[-]^?(.[0-9]^+ | [0-9]^+(.[0-9]*)6? $
- 所有用雙引號引用的字符串
"..."
- HTML 格式的字符串
<>
dot 語法的關鍵字
- strict, 嚴格的圖限定,禁止創建多個相同的邊
- graph, 無向圖. 在圖的創建時必須聲明為有向圖還是無向圖
- digraph, 有向圖
- node, 節點
- edge, 邊
- subgraph, 子圖
通過 dot 的抽象語法可以看到
- 整個 graph 必須使用 graph 或 digraph {} 進行限定說明圖的屬性
- 圖裡面的聲明列表可以為空,也可以為多個,每個聲明后的 ; 為可選項
- 聲明有幾種類型
- 節點 node
- 邊 edge
- 子圖 subgraph
- 屬性列表
- ID = ID, 這個類型暫時還沒有看到有什麼作用
- 屬性列表
- 必須使用中括號 [ ] 將列表項括起來
- 列表項為可選
- 屬性列表項
- 以 key = value 的形式存在,列表項可選擇 ',' 和 ';' 結尾
- 可存在多個列表項
- 邊的聲明
- 首端為 節點標識符或者子圖,
- 右部分由邊連接節點標識符或者子圖構成,右部分可以存在多個
- 尾部可選屬性列表
- 節點的聲明
示例 節點的用法 node0 [label = "<postid1> string|<postid2> string|<postid3> string3", height=.5]` node0:head[color=lightblue] // 設置該部分的顏色
- 首部為節點標識符 節點部分(post) 方向 組成,其中后兩項為可選項。
- 後半部分為可選的屬性列表
方向 | 說明 |
---|---|
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
繪製屬性
一個圖中有非常多的 node 和 edge,如果每次都需要聲明一個節點的屬性會非常麻煩,有一個簡單的方式為聲明一個公共的屬性如
digraph action {
rankdir = LR // 設置方向
node [shape=box color=blue]
edge [color=red]
node1 // 默認節點屬性
node2 [color=lightblue] // 屬於該節點的顏色屬性
node1 -> node2 // 默認邊屬性
node2 -> node1 [color=green] // 屬於該變的屬性
}
在聲明位置之後的節點都有一個 默認 的形狀和顏色屬性。
全部的屬性見,這裏列舉部分常用的屬性
- charset 編碼,一般設置 UTF-8
- fontname 字體名稱,這個在中文的情況需要設置,否則導出圖片的時候會亂碼,一般設置微軟雅黑("Microsoft YaHei"), linux 下也是同樣設置系統帶的字體就好,其他字體設置見 屬性
- fontcolor 字體顏色
- fontsize 字體大小,用於文本內容
- fillcolor 用於填充節點或者集群(cluster)的背景顏色。
- size 圖形的最大寬度和高度
- label 圖形上的文本標記
- margin 設置圖形的邊距
- pad 指定將繪製區域擴展到繪製圖形所需的最小區域的長度(以英寸為單位)
- style 設置圖形組件的樣式信息。 對於聚類子圖或者節點,如果style = "filled",則填充聚類框的背景
- rankdir 設置圖形布局的排列方向 (全局只有一個生效). "TB", "LR", "BT", "RL", 分別對應於從上到下,從左到右,從下到上和從右到左繪製的有向圖。
- ranksep 以英寸為單位提供所需的排列間隔
- ratio 設置生成圖片的縱橫比
節點(node)
節點的默認屬性為 shape = ellipse, width = .75, height = 0.5 並且用節點標識符作為節點的显示文字。
如圖一中所示,聲明兩個節點 node0 和 node2,node0 或 node2 就表示這個節點的節點標識符,後面緊跟的是該節點的屬性列表;另一種用法為 節點標識符:節點部分:方向[屬性列表] node0:body[style=filled color=lightblue]
, 這個為單一節點聲明的方式。
節點中最基本的屬性為:
- shape 形狀,全部形狀見,一些常用的圖形有
- width height, 圖形的寬度和高度,如果設置了 fixedsize 為 true,則寬和高為最終的長度
- fixedsize, 如果為false,節點的大小由其文本內容所需要的最小值決定
- rank 子圖中節點上的排列等級約束. 最小等級是最頂部或最左側,最大等級是最底部或最右側。
- same. 所有節點都位於同一等級
- min. 所有節點都位於最小等級上
- source. 所有節點都位於最小等級上,並且最小等級上的唯一節點屬於某個等級 source 或 min 的子圖.
- max sink. 和上類似
邊 (edge)
有向圖中的的邊用 ->
表示,無向圖用 --
表示。
可以同時連接多個節點或者子圖,但是只能有一個屬性列表,如下
digraph {
rankdir = LR
A -> B -> c[color=green]
}
一些關於邊的屬性如下:
digraph {
rankdir = LR
splines = ortho
A -> B -> C -> D -> F [color = green]
E -> F -> B -> D [color = blue]
B -> E -> H[color = red]
}
- len 首選邊的長度
- weight 邊的權重, 權重越大越接近邊的長度
- lhead 邏輯邊緣的頭部(箭頭那個位置),compound 設置為 true 時,邊被裁減到子圖的邊界處
- ltail 類似 lhead
- headlabel 邊上靠近箭頭部分的標籤
taillabel 邊上靠近尾部部分的標籤
設置 A->B->C->D->F的權重最大,修改綠色的分支的權重為 100,使其變成主要邏輯分支。
- splines 控制如何以及是否表示邊緣。其值如下
- none 或者 "", 無邊
- true 或者 spline, 樣條線(無規則,可為直或者曲線)
- false 或者 line, 直線段
- polyline, 折線
- curved, 曲弧線,兩條?
- ortho, 正直的線(橫豎)
dir 設置繪製箭頭的邊緣類型
子圖
subgraph 必須配合 cluster 一起使用,用法為 subgraph cluster* {}
需要設置 compound 為 true,則在群集之間留出邊緣,子圖的邊界關係在 邊 的定義中有給出,這裏直接給個示例。
digraph G {
compound = true // 允許子圖間存在邊
ranksep = 1
node [shape = record]
subgraph cluster_hardware {
label = "hardware"
color = lightblue
CPU Memory
}
subgraph cluster_kernel {
label = "kernel"
color = green
Init IPC
}
subgraph cluster_libc {
label = "libc"
color = yellow
glibc
}
CPU -> Init [lhead = cluster_kernel ltail = cluster_hardware]
IPC -> glibc [lhead = cluster_libc ltail = cluster_kernel]
}
示例
TCP IP 狀態流程圖
展示了兩個版本,怎麼把這些圖形節點稍微規範的显示出來
digraph {
compound=true
fontsize=10
margin="0,0"
ranksep = .75
nodesep = .65
node [shape=Mrecord fontname="Inconsolata, Consolas", fontsize=12, penwidth=0.5]
edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal]
"TCP/IP State Transition" [shape = "plaintext", fontsize = 16]
// now start server state transition
"CLOSED" -> "LISTEN" [style = blod, label = "應用:被動打開\n發送:<無>"];
"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, label = "被動關閉", 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, label = "主動關閉", 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]
}
"TIME_WAIT" -> "CLOSED" [style = dashed, label = "2MSL超時"]
}
這是一個很挫的版本,排版亂飛了。
digraph rankdot {
compound=true
margin="0,0"
ranksep = .75
nodesep = 1
pad = .5
//splines = ortho
node [shape=Mrecord, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14]
edge [charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11, arrowhead = normal]
CLOSED -> LISTEN [style = dashed, label = "應用:被動打開\n發送:<無>", weight = 100];
"TCP/IP State Transition" [shape = "plaintext", fontsize = 16]
{
rank = same
SYN_RCVD SYN_SENT
point_1 [shape = point, width = 0]
SYN_SENT -> point_1 [style = dotted, label = "應用關閉或者超時"]
// SYN_SENT -> SYN_RCVD 這個一行代碼和上一行衝突了,syn_sent 會在syn_rcvd右邊
SYN_RCVD -> SYN_SENT [style = dotted, dir = back, headlabel = "接收:SYN\n發送:SYN,ACK\n同時打開"]
}
LISTEN -> SYN_RCVD [style = dashed, headlabel = "接收:SYN\n發送:SYN,ACK"]
SYN_RCVD -> LISTEN [style = dotted, headlabel = "接收:RST"]
CLOSED:es -> SYN_SENT [style = blod, label = "應用:主動打開\n發送:SYN"]
{
rank = same
ESTABLISHED CLOSE_WAIT
ESTABLISHED -> CLOSE_WAIT [style = dashed, label = "接收:SYN,ACK\n發送:ACK"]
}
SYN_RCVD -> ESTABLISHED [style = dashed, label = "接收:ACK\n發送:<無>", 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 = 0]
FIN_WAIT_1 -> CLOSING [style = dotted, label = "接收:FIN\n發送:ACK"]
LAST_ACK -> point_2 [style = dashed, label = "接收:ACK\n發送:<無>"]
}
CLOSE_WAIT -> LAST_ACK [style = dashed, label = "應用:關閉\n發送:FIN", weight = 10]
{
rank = same
FIN_WAIT_2 TIME_WAIT
point_3 [shape = point, width = 0]
TIME_WAIT -> point_3 [style = blod, label = "2MSL超時"]
}
ESTABLISHED -> FIN_WAIT_1 [style = blod, label = "應用:關閉\n發送:FIN"]
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 屬性了,這樣就不能在若干不同部分的節點周邊畫線(對比關閉的區域)了。
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 = "<eventpoll> struct \n eventpoll |
<lock> spinlock_t lock; |
<mutex> struct mutex mtx; |
<wq> wait_queue_head_t wq; |
<poll_wait> wait_queue_head_t poll_wait; |
<rdllist> struct list_head rdllist; |
<ovflist> struct epitem *ovflist; |
<rbr> struct rb_root_cached rbr; |
<ws> struct wakeup_source *ws; |
<user> struct user_struct *user; |
<file> struct file *file; |
<visited> int visited; |
<visited_list_link> struct list_head visited_list_link;"
]
epitem [
color = sienna,
label = "<epitem> struct \n epitem |
<rb>struct rb_node rbn;\nstruct rcu_head rcu; |
<rdllink> struct list_head rdllink; |
<next> struct epitem *next; |
<ffd> struct epoll_filefd ffd; |
<nwait> int nwait; |
<pwqlist> struct list_head pwqlist; |
<ep> struct eventpoll *ep; |
<fllink> struct list_head fllink; |
<ws> struct wakeup_source __rcu *ws; |
<event> struct epoll_event event;"
]
epitem2 [
color = sienna,
label = "<epitem> struct \n epitem |
<rb>struct rb_node rbn;\nstruct rcu_head rcu; |
<rdllink> struct list_head rdllink; |
<next> struct epitem *next; |
<ep> struct eventpoll *ep; |
··· |
··· "
]
eppoll_entry [
color = darkviolet,
label = "<entry> struct \n eppoll_entry |
<llink> struct list_head llink; |
<base> struct epitem *base; |
<wait> wait_queue_entry_t wait; |
<whead> wait_queue_head_t *whead;"
]
epitem:ep -> eventpoll:se [color = sienna]
epitem2:ep -> eventpoll:se [color = sienna]
eventpoll:ovflist -> epitem:next -> epitem2:next [color = cornflowerblue]
eventpoll:rdllist -> epitem:rdllink -> epitem2:rdllink [dir = both]
eppoll_entry:llink -> epitem:pwqlist [color = darkviolet]
eppoll_entry:base -> epitem:nw [color = darkviolet]
}
遺留問題
- 在以上TCP/IP 狀態變遷圖中,嘗試增加主動關閉方的區域邊框
- 嘗試增加 TCP/IP 的時序圖
使用 VSCode 進行預覽生成
- 在官網下載graphviz安裝包
- 安裝 vscode 插件
Graphviz Preview
- 在 settings.json 中添加
"graphvizPreview.dotPath": "graphviz_path\graphviz-2.38\\release\\bin\\dot.exe"
, graphviz_path 為所在路徑,這些修改一下既可 - 新建一個 dot 文件,右上角就會有預覽生成的按鈕了
參考
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※台灣海運大陸貨務運送流程
※兩岸物流進出口一站式服務
Orignal From: Graphviz 畫圖的一些總結
留言
張貼留言