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. 節點的聲明
    示例 節點的用法 node0 [label = "<postid1> string|<postid2> string|<postid3> 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


繪製屬性







一個圖中有非常多的 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]
}


遺留問題



  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 文件,右上角就會有預覽生成的按鈕了


參考




本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能



※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享



※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選



台灣海運大陸貨務運送流程



兩岸物流進出口一站式服務




Orignal From: Graphviz 畫圖的一些總結

留言