2015年11月10日更新 在實(shí)踐中,我又發(fā)現(xiàn)了一些graphviz的有趣的特性,比如時(shí)序圖,rank以及圖片節(jié)點(diǎn)等。在這里一并更新。
前言
日常的開(kāi)發(fā)工作中,為代碼添加注釋是代碼可維護(hù)性的一個(gè)重要方面,但是僅僅提供注釋是不夠的,特別是當(dāng)系統(tǒng)功能越來(lái)越復(fù)雜,涉及到的模塊越來(lái)越多的時(shí)候,僅僅靠代碼就很難從宏觀的層次去理解。因此我們需要圖例的支持,圖例不僅僅包含功能之間的交互,也可以包含復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的示意圖,數(shù)據(jù)流向等。
但是,常用的UML建模工具,如Visio等都略顯復(fù)雜,且體積龐大。對(duì)于開(kāi)發(fā)人員,特別是后臺(tái)開(kāi)發(fā)人員來(lái)說(shuō),命令行,腳本才是最友好的,而圖形界面會(huì)很大程度的限制開(kāi)發(fā)效率。相對(duì)于鼠標(biāo),鍵盤才是開(kāi)發(fā)人員最好的朋友。
graphviz簡(jiǎn)介
本文介紹一個(gè)高效而簡(jiǎn)潔的繪圖工具graphviz。graphviz是貝爾實(shí)驗(yàn)室開(kāi)發(fā)的一個(gè)開(kāi)源的工具包,它使用一個(gè)特定的DSL(領(lǐng)域特定語(yǔ)言): dot作為腳本語(yǔ)言,然后使用布局引擎來(lái)解析此腳本,并完成自動(dòng)布局。graphviz提供豐富的導(dǎo)出格式,如常用的圖片格式,SVG,PDF格式等。
graphviz中包含了眾多的布局器:
- dot 默認(rèn)布局方式,主要用于有向圖
- neato 基于spring-model(又稱force-based)算法
- twopi 徑向布局
- circo 圓環(huán)布局
- fdp 用于無(wú)向圖
graphviz的設(shè)計(jì)初衷是對(duì)有向圖/無(wú)向圖等進(jìn)行自動(dòng)布局,開(kāi)發(fā)人員使用dot腳本定義圖形元素,然后選擇算法進(jìn)行布局,最終導(dǎo)出結(jié)果。
首先,在dot腳本中定義圖的頂點(diǎn)和邊,頂點(diǎn)和邊都具有各自的屬性,比如形狀,顏色,填充模式,字體,樣式等。然后使用合適的布局算法進(jìn)行布局。布局算法除了繪制各個(gè)頂點(diǎn)和邊之外,需要盡可能的將頂點(diǎn)均勻的分布在畫布上,并且盡可能的減少邊的交叉(如果交叉過(guò)多,就很難看清楚頂點(diǎn)之間的關(guān)系了)。所以使用graphviz的一般流程為:
- 定義一個(gè)圖,并向圖中添加需要的頂點(diǎn)和邊
- 為頂點(diǎn)和邊添加樣式
- 使用布局引擎進(jìn)行繪制
一旦熟悉這種開(kāi)發(fā)模式,就可以快速的將你的想法繪制出來(lái)。配合一個(gè)良好的編輯器(vim/emacs)等,可以極大的提高開(kāi)發(fā)效率,與常見(jiàn)的GUI應(yīng)用的所見(jiàn)即所得模式對(duì)應(yīng),此模式稱為所思即所得。比如在我的機(jī)器上,使用Sublime Text 編輯dot腳本,然后將F7/Cmd-B映射為調(diào)用dot引擎去繪制當(dāng)前腳本,并打開(kāi)一個(gè)新的窗口來(lái)顯示運(yùn)行結(jié)果:
對(duì)于開(kāi)發(fā)人員而言,經(jīng)常會(huì)用到的圖形繪制可能包括:函數(shù)調(diào)用關(guān)系,一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),系統(tǒng)的模塊組成,抽象語(yǔ)法樹等。
基礎(chǔ)知識(shí)
graphviz包含3中元素,圖,頂點(diǎn)和邊。每個(gè)元素都可以具有各自的屬性,用來(lái)定義字體,樣式,顏色,形狀等。下面是一些簡(jiǎn)單的示例,可以幫助我們快速的了解graphviz的基本用法。
第一個(gè)graphviz圖
比如,要繪制一個(gè)有向圖,包含4個(gè)節(jié)點(diǎn)a,b,c,d。其中a指向b,b和c指向d??梢远x下列腳本:
1 2 3 4 5 6 7 8 9 10 | digraph abc{
a;
b;
c;
d;
a -> b;
b -> d;
c -> d;
}
|
使用dot布局方式,繪制出來(lái)的效果如下:
默認(rèn)的頂點(diǎn)中的文字為定義頂點(diǎn)變量的名稱,形狀為橢圓。邊的默認(rèn)樣式為黑色實(shí)線箭頭,我們可以在腳本中做一下修改,將頂點(diǎn)改為方形,邊改為虛線。
定義頂點(diǎn)和邊的樣式
在digraph的花括號(hào)內(nèi),添加頂點(diǎn)和邊的新定義:
1 2 | node [shape= "record" ];
edge [style= "dashed" ];
|
則繪制的效果如下:
進(jìn)一步修改頂點(diǎn)和邊樣式
進(jìn)一步,我們將頂點(diǎn)a的顏色改為淡綠色,并將c到d的邊改為紅色,腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | digraph abc{
node [shape= "record" ];
edge [style= "dashed" ];
a [style= "filled" , color= "black" , fillcolor= "chartreuse" ];
b;
c;
d;
a -> b;
b -> d;
c -> d [color= "red" ];
}
|
繪制的結(jié)果如下:
應(yīng)當(dāng)注意到,頂點(diǎn)和邊都接受屬性的定義,形式為在頂點(diǎn)和邊的定義之后加上一個(gè)由方括號(hào)括起來(lái)的key-value列表,每個(gè)key-value對(duì)由逗號(hào)隔開(kāi)。如果圖中頂點(diǎn)和邊采用統(tǒng)一的風(fēng)格,則可以在圖定義的首部定義node, edge的屬性。比如上圖中,定義所有的頂點(diǎn)為方框,所有的邊為虛線,在具體的頂點(diǎn)和邊之后定義的屬性將覆蓋此全局屬性。如特定與a的綠色,c到d的邊的紅色。
以圖片為節(jié)點(diǎn)
除了顏色,節(jié)點(diǎn)還可以使用圖片。不過(guò)需要注意的是,在使用圖片作為節(jié)點(diǎn)的時(shí)候,需要將本來(lái)的形狀設(shè)置為none,并且將label置為空字符串,避免出現(xiàn)文字對(duì)圖片的干擾。
1 2 3 4 5 6 7 8 9 10 11 12 13 | digraph abc{
node [shape= "record" ];
edge [style= "dashed" ];
a [style= "filled" , color= "black" , fillcolor= "chartreuse" ];
b;
c [shape= "none" , image= "logos/browser-icon-chrome-resized.png" , label= "" ];
d;
a -> b;
b -> d;
c -> d [color= "red" ];
}
|
子圖的繪制
graphviz支持子圖,即圖中的部分節(jié)點(diǎn)和邊相對(duì)對(duì)立(軟件的模塊劃分經(jīng)常如此)。比如,我們可以將頂點(diǎn)c和d歸為一個(gè)子圖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | digraph abc{
node [shape= "record" ];
edge [style= "dashed" ];
a [style= "filled" , color= "black" , fillcolor= "chartreuse" ];
b;
subgraph cluster_cd{
label= "c and d" ;
bgcolor= "mintcream" ;
c;
d;
}
a -> b;
b -> d;
c -> d [color= "red" ];
}
|
將c和d劃分到cluster_cd這個(gè)子圖中,標(biāo)簽為c and d,并添加背景色,以方便與主圖區(qū)分開(kāi),繪制結(jié)果如下:
應(yīng)該注意的是,子圖的名稱必須以cluster開(kāi)頭,否則graphviz無(wú)法設(shè)別。
數(shù)據(jù)結(jié)構(gòu)的可視化
實(shí)際開(kāi)發(fā)中,經(jīng)常要用到的是對(duì)復(fù)雜數(shù)據(jù)結(jié)構(gòu)的描述,graphviz提供完善的機(jī)制來(lái)繪制此類圖形。
一個(gè)hash表的數(shù)據(jù)結(jié)構(gòu)
比如一個(gè)hash表的內(nèi)容,可能具有下列結(jié)構(gòu):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct st_hash_type {
int (*compare) ();
int (*hash) ();
};
struct st_table_entry {
unsigned int hash;
char *key;
char *record;
st_table_entry *next;
};
struct st_table {
struct st_hash_type *type;
int num_bins; /* slot count */
int num_entries; /* total number of entries */
struct st_table_entry **bins; /* slot */
};
|
繪制hash表的數(shù)據(jù)結(jié)構(gòu)
從代碼上看,由于結(jié)構(gòu)體存在引用關(guān)系,不夠清晰,如果層次較多,則很難以記住各個(gè)結(jié)構(gòu)之間的關(guān)系,我們可以通過(guò)下圖來(lái)更清楚的展示:
腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | digraph st2{
fontname = "Verdana" ;
fontsize = 10;
rankdir=TB;
node [fontname = "Verdana" , fontsize = 10, color= "skyblue" , shape= "record" ];
edge [fontname = "Verdana" , fontsize = 10, color= "crimson" , style= "solid" ];
st_hash_type [label= "{<head>st_hash_type|(*compare)|(*hash)}" ];
st_table_entry [label= "{<head>st_table_entry|hash|key|record|<next>next}" ];
st_table [label= "{st_table|<type>type|num_bins|num_entries|<bins>bins}" ];
st_table:bins -> st_table_entry:head;
st_table:type -> st_hash_type:head;
st_table_entry:next -> st_table_entry:head [style= "dashed" , color= "forestgreen" ];
}
|
應(yīng)該注意到,在頂點(diǎn)的形狀為record的時(shí)候,label屬性的語(yǔ)法比較奇怪,但是使用起來(lái)非常靈活。比如,用豎線”|”隔開(kāi)的串會(huì)在繪制出來(lái)的節(jié)點(diǎn)中展現(xiàn)為一條分隔符。用<>括起來(lái)的串稱為錨點(diǎn),當(dāng)一個(gè)節(jié)點(diǎn)具有多個(gè)錨點(diǎn)的時(shí)候,這個(gè)特性會(huì)非常有用,比如節(jié)點(diǎn)st_table的type屬性指向st_hash_type,第4個(gè)屬性指向st_table_entry等,都是通過(guò)錨點(diǎn)來(lái)實(shí)現(xiàn)的。
我們發(fā)現(xiàn),使用默認(rèn)的dot布局后,綠色的這條邊覆蓋了數(shù)據(jù)結(jié)構(gòu)st_table_entry,并不美觀,因此可以使用別的布局方式來(lái)重新布局,如使用circo算法:
則可以得到更加合理的布局結(jié)果。
hash表的實(shí)例
另外,這個(gè)hash表的一個(gè)實(shí)例如下:
腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | digraph st{
fontname = "Verdana" ;
fontsize = 10;
rankdir = LR;
rotate = 90;
node [ shape= "record" , width=.1, height=.1];
node [fontname = "Verdana" , fontsize = 10, color= "skyblue" , shape= "record" ];
edge [fontname = "Verdana" , fontsize = 10, color= "crimson" , style= "solid" ];
node [shape= "plaintext" ];
st_table [label=<
<table border= "0" cellborder= "1" cellspacing= "0" align= "left" >
<tr>
<td>st_table</td>
</tr>
<tr>
<td>num_bins=5</td>
</tr>
<tr>
<td>num_entries=3</td>
</tr>
<tr>
<td port= "bins" >bins</td>
</tr>
</table>
>];
node [shape= "record" ];
num_bins [label= " <b1> | <b2> | <b3> | <b4> | <b5> " , height=2];
node[ width=2 ];
entry_1 [label= "{<e>st_table_entry|<next>next}" ];
entry_2 [label= "{<e>st_table_entry|<next>null}" ];
entry_3 [label= "{<e>st_table_entry|<next>null}" ];
st_table:bins -> num_bins:b1;
num_bins:b1 -> entry_1:e;
entry_1:next -> entry_2:e;
num_bins:b3 -> entry_3:e;
}
|
上例中可以看到,節(jié)點(diǎn)的label屬性支持類似于HTML語(yǔ)言中的TABLE形式的定義,通過(guò)行列的數(shù)目來(lái)定義節(jié)點(diǎn)的形狀,從而使得節(jié)點(diǎn)的組成更加靈活。
軟件模塊組成圖
Apache httpd 模塊關(guān)系
在實(shí)際的開(kāi)發(fā)中,隨著系統(tǒng)功能的完善,軟件整體的結(jié)構(gòu)會(huì)越來(lái)越復(fù)雜,通常開(kāi)發(fā)人員會(huì)將軟件劃分為可理解的多個(gè)子模塊,各個(gè)子模塊通過(guò)協(xié)作,完成各種各樣的需求。
下面有個(gè)例子,是某軟件設(shè)計(jì)時(shí)的一個(gè)草稿:
IDP支持層為一個(gè)相對(duì)獨(dú)立的子系統(tǒng),其中包括如數(shù)據(jù)庫(kù)管理器,配置信息管理器等模塊,另外為了提供更大的靈活性,將很多其他的模塊抽取出來(lái)作為外部模塊,而支持層提供一個(gè)模塊管理器,來(lái)負(fù)責(zé)加載/卸載這些外部的模塊集合。
這些模塊間的關(guān)系較為復(fù)雜,并且有部分模塊關(guān)系密切,應(yīng)歸類為一個(gè)子系統(tǒng)中,上圖對(duì)應(yīng)的dot腳本為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | digraph idp_modules{
rankdir = TB;
fontname = "Microsoft YaHei" ;
fontsize = 12;
node [ fontname = "Microsoft YaHei" , fontsize = 12, shape = "record" ];
edge [ fontname = "Microsoft YaHei" , fontsize = 12 ];
subgraph cluster_sl{
label= "IDP支持層" ;
bgcolor= "mintcream" ;
node [shape= "Mrecord" , color= "skyblue" , style= "filled" ];
network_mgr [label= "網(wǎng)絡(luò)管理器" ];
log_mgr [label= "日志管理器" ];
module_mgr [label= "模塊管理器" ];
conf_mgr [label= "配置管理器" ];
db_mgr [label= "數(shù)據(jù)庫(kù)管理器" ];
};
subgraph cluster_md{
label= "可插拔模塊集" ;
bgcolor= "lightcyan" ;
node [color= "chartreuse2" , style= "filled" ];
mod_dev [label= "開(kāi)發(fā)支持模塊" ];
mod_dm [label= "數(shù)據(jù)建模模塊" ];
mod_dp [label= "部署發(fā)布模塊" ];
};
mod_dp -> mod_dev [label= "依賴..." ];
mod_dp -> mod_dm [label= "依賴..." ];
mod_dp -> module_mgr [label= "安裝..." , color= "yellowgreen" , arrowhead= "none" ];
mod_dev -> mod_dm [label= "依賴..." ];
mod_dev -> module_mgr [label= "安裝..." , color= "yellowgreen" , arrowhead= "none" ];
mod_dm -> module_mgr [label= "安裝..." , color= "yellowgreen" , arrowhead= "none" ];
}
|
狀態(tài)圖
有限自動(dòng)機(jī)示意圖
上圖是一個(gè)簡(jiǎn)易有限自動(dòng)機(jī),接受a及a結(jié)尾的任意長(zhǎng)度的串。其腳本定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | digraph automata_0 {
size = "8.5, 11" ;
fontname = "Microsoft YaHei" ;
fontsize = 10;
node [shape = circle, fontname = "Microsoft YaHei" , fontsize = 10];
edge [fontname = "Microsoft YaHei" , fontsize = 10];
0 [ style = filled, color=lightgrey ];
2 [ shape = doublecircle ];
0 -> 2 [ label = "a " ];
0 -> 1 [ label = "other " ];
1 -> 2 [ label = "a " ];
1 -> 1 [ label = "other " ];
2 -> 2 [ label = "a " ];
2 -> 1 [ label = "other " ];
"Machine: a" [ shape = plaintext ];
}
|
形狀值為plaintext的表示不用繪制邊框,僅展示純文本內(nèi)容,這個(gè)在繪圖中,繪制指示性的文本時(shí)很有用,如上圖中的Machine: a。
OSGi中模塊的生命周期圖
OSGi中,模塊具有生命周期,從安裝到卸載,可能的狀態(tài)具有已安裝,已就緒,正在啟動(dòng),已啟動(dòng),正在停止,已卸載等。如下圖所示:
對(duì)應(yīng)的腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | digraph module_lc{
rankdir=TB;
fontname = "Microsoft YaHei" ;
fontsize = 12;
node [fontname = "Microsoft YaHei" , fontsize = 12, shape = "Mrecord" , color= "skyblue" , style= "filled" ];
edge [fontname = "Microsoft YaHei" , fontsize = 12, color= "darkgreen" ];
installed [label= "已安裝狀態(tài)" ];
resolved [label= "已就緒狀態(tài)" ];
uninstalled [label= "已卸載狀態(tài)" ];
starting [label= "正在啟動(dòng)" ];
active [label= "已激活(運(yùn)行)狀態(tài)" ];
stopping [label= "正在停止" ];
start [label= "" , shape= "circle" , width=0.5, fixedsize= true , style= "filled" , color= "black" ];
start -> installed [label= "安裝" ];
installed -> uninstalled [label= "卸載" ];
installed -> resolved [label= "準(zhǔn)備" ];
installed -> installed [label= "更新" ];
resolved -> installed [label= "更新" ];
resolved -> uninstalled [label= "卸載" ];
resolved -> starting [label= "啟動(dòng)" ];
starting -> active [label= "" ];
active -> stopping [label= "停止" ];
stopping -> resolved [label= "" ];
}
|
其他實(shí)例
一棵簡(jiǎn)單的抽象語(yǔ)法樹(AST)
表達(dá)式 (3+4)*5 在編譯時(shí)期,會(huì)形成一棵語(yǔ)法樹,一邊在計(jì)算時(shí),先計(jì)算3+4的值,最后與5相乘。
對(duì)應(yīng)的腳本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | digraph ast{
fontname = "Microsoft YaHei" ;
fontsize = 10;
node [shape = circle, fontname = "Microsoft YaHei" , fontsize = 10];
edge [fontname = "Microsoft YaHei" , fontsize = 10];
node [shape= "plaintext" ];
mul [label= "mul(*)" ];
add [label= "add(+)" ];
add -> 3
add -> 4;
mul -> add;
mul -> 5;
}
|
簡(jiǎn)單的UML類圖
下面是一簡(jiǎn)單的UML類圖,Dog和Cat都是Animal的子類,Dog和Cat同屬一個(gè)包,且有可能有聯(lián)系(0..n)。
腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | digraph G{
fontname = "Courier New"
fontsize = 10
node [ fontname = "Courier New" , fontsize = 10, shape = "record" ];
edge [ fontname = "Courier New" , fontsize = 10 ];
Animal [ label = "{Animal |+ name : String\l+ age : int\l|+ die() : void\l}" ];
subgraph clusterAnimalImpl{
bgcolor= "yellow"
Dog [ label = "{Dog||+ bark() : void\l}" ];
Cat [ label = "{Cat||+ meow() : void\l}" ];
};
edge [ arrowhead = "empty" ];
Dog->Animal;
Cat->Animal;
Dog->Cat [arrowhead= "none" , label= "0..*" ];
}
|
狀態(tài)圖
腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | digraph finite_state_machine {
rankdir = LR;
size = "8,5"
node [shape = doublecircle];
LR_0 LR_3 LR_4 LR_8;
node [shape = circle];
LR_0 -> LR_2 [ label = "SS(B)" ];
LR_0 -> LR_1 [ label = "SS(S)" ];
LR_1 -> LR_3 [ label = "S($end)" ];
LR_2 -> LR_6 [ label = "SS(b)" ];
LR_2 -> LR_5 [ label = "SS(a)" ];
LR_2 -> LR_4 [ label = "S(A)" ];
LR_5 -> LR_7 [ label = "S(b)" ];
LR_5 -> LR_5 [ label = "S(a)" ];
LR_6 -> LR_6 [ label = "S(b)" ];
LR_6 -> LR_5 [ label = "S(a)" ];
LR_7 -> LR_8 [ label = "S(b)" ];
LR_7 -> LR_5 [ label = "S(a)" ];
LR_8 -> LR_6 [ label = "S(b)" ];
LR_8 -> LR_5 [ label = "S(a)" ];
}
|
時(shí)序圖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | digraph G {
rankdir= "LR" ;
node[shape= "point" , width=0, height=0];
edge[arrowhead= "none" , style= "dashed" ]
{
rank= "same" ;
edge[style= "solided" ];
LC[shape= "plaintext" ];
LC -> step00 -> step01 -> step02 -> step03 -> step04 -> step05;
}
{
rank= "same" ;
edge[style= "solided" ];
Agency[shape= "plaintext" ];
Agency -> step10 -> step11 -> step12 -> step13 -> step14 -> step15;
}
{
rank= "same" ;
edge[style= "solided" ];
Agent[shape= "plaintext" ];
Agent -> step20 -> step21 -> step22 -> step23 -> step24 -> step25;
}
step00 -> step10 [label= "sends email new custumer" , arrowhead= "normal" ];
step11 -> step01 [label= "declines" , arrowhead= "normal" ];
step12 -> step02 [label= "accepts" , arrowhead= "normal" ];
step13 -> step23 [label= "forward to" , arrowhead= "normal" ];
step24 -> step14;
step14 -> step04 [arrowhead= "normal" ];
}
|
rankdir=”LR”表示,布局從左L到右R??梢钥吹?,在代碼中有{}括起來(lái)的部分。
1 2 3 4 5 6 | {
rank= "same" ;
edge[style= "solided" ];
Agency[shape= "plaintext" ];
Agency -> step10 -> step11 -> step12 -> step13 -> step14 -> step15;
}
|
每一個(gè)rank=”same”的block中的所有節(jié)點(diǎn)都會(huì)在同一條線上。我們?cè)O(shè)置了所有的線為虛線,但是在該block中,將線改為solided。
附錄
事實(shí)上,從dot的語(yǔ)法及上述的示例中,很容易看出,dot腳本很容易被其他語(yǔ)言生成。比如,使用一些簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢就可以生成數(shù)據(jù)庫(kù)中的ER圖的dot腳本。
如果你追求高效的開(kāi)發(fā)速度,并希望快速的將自己的想法畫出來(lái),那么graphviz是一個(gè)很不錯(cuò)的選擇。
當(dāng)然,graphviz也有一定的局限,比如繪制時(shí)序圖(序列圖)就很難實(shí)現(xiàn)。graphviz的節(jié)點(diǎn)出現(xiàn)在畫布上的位置事實(shí)上是不確定的,依賴于所使用的布局算法,而不是在腳本中出現(xiàn)的位置,這可能使剛開(kāi)始接觸graphviz的開(kāi)發(fā)人員有點(diǎn)不適應(yīng)。graphviz的強(qiáng)項(xiàng)在于自動(dòng)布局,當(dāng)圖中的頂點(diǎn)和邊的數(shù)目變得很多的時(shí)候,才能很好的體會(huì)這一特性的好處:
比如上圖,或者較上圖更復(fù)雜的圖,如果采用手工繪制顯然是不可能的,只能通過(guò)graphviz提供的自動(dòng)布局引擎來(lái)完成。如果僅用于展示模塊間的關(guān)系,子模塊與子模塊間通信的方式,模塊的邏輯位置等,graphviz完全可以勝任,但是如果圖中對(duì)象的物理位置必須是準(zhǔn)確的,如節(jié)點(diǎn)A必須位于左上角,節(jié)點(diǎn)B必須與A相鄰等特性,使用graphviz則很難做到。畢竟,它的強(qiáng)項(xiàng)是自動(dòng)布局,事實(shí)上,所有的節(jié)點(diǎn)對(duì)與布局引擎而言,權(quán)重在初始時(shí)都是相同的,只是在渲染之后,節(jié)點(diǎn)的大小,形狀等特性才會(huì)影響權(quán)重。
本文只是初步介紹了graphviz的簡(jiǎn)單應(yīng)用,如圖的定義,頂點(diǎn)/邊的屬性定義,如果運(yùn)行等,事實(shí)上還有很多的屬性,如畫布的大小,字體的選擇,顏色列表等,大家可以通過(guò)graphviz的官網(wǎng)來(lái)找到更詳細(xì)的資料。
文中的代碼都已經(jīng)在Github上。
|