Graphviz入門
安裝Graphviz
在官網上面下載相關文件,地址:http://www.graphviz.org/download/。
graphviz
簡介
graphviz
是貝爾實驗室設計的一個開源的畫圖工具,它的強大主要體現在“所思即所得”(WYTIWYG,what you think is what you get),這是和office的“所見即所得“(WYSIWYG,what you see is what you get)完全不同的一種方式。它使用一個特定的DSL(領域特定語言): dot作為腳本語言,然后使用布局引擎來解析此腳本,並完成自動布局。它的輸入是一個用dot語言 編寫的繪圖腳本,通過對輸入腳本的解析,分析出其中的點,邊以及子圖,然后根據屬性進行繪制。graphviz
提供豐富的導出格式,如常用的圖片格式,SVG,PDF格式等。用graphviz
來繪圖的時候,你的主要工作就是編寫dot腳本,你只要關注圖中各個點之間的關系就好了,你不需要考慮如何安排各個節點的位置,怎樣布局能夠使你所繪制的圖看起來更美觀一些。
graphviz
中包含了眾多的布局器:
- dot 默認布局方式,渲染的圖具有明確方向性,主要用於有向圖
- neato 渲染的圖缺乏方向性,基於spring-model(又稱force-based)算法
- twopi 渲染的圖用放射性布局,徑向布局
- circo 渲染的圖用環型布局,圓環布局
- fdp 渲染的圖缺乏方向性,用於無向圖
- sfdp 渲染大型的圖,圖片缺乏方向性
graphviz
的設計初衷是對有向圖/無向圖等進行自動布局,開發人員使用dot腳本定義圖形元素,然后選擇算法進行布局,最終導出結果。
首先,在dot腳本中定義圖的頂點和邊,頂點和邊都具有各自的屬性,比如形狀,顏色,填充模式,字體,樣式等。然后使用合適的布局算法進行布局。布局算法除了繪制各個頂點和邊之外,需要盡可能的將頂點均勻的分布在畫布上,並且盡可能的減少邊的交叉(如果交叉過多,就很難看清楚頂點之間的關系了)。所以使用graphviz
的一般流程為:
- 定義一個圖,並向圖中添加需要的頂點和邊
- 為頂點和邊添加樣式
- 使用布局引擎進行繪制
一旦熟悉這種開發模式,就可以快速的將你的想法繪制出來。
第一個graphviz圖
比如,要繪制一個有向圖,包含5個節點a,b,c,d,e。其中a和b指向c,c和d指向e。可以定義下列腳本:
digraph test{
a;
b;
c;
d;
e;
a->c;
b->c;
c->e;
d->e;
}
使用dot布局方式,繪制出來的效果如下:
默認的頂點中的文字為定義頂點變量的名稱,形狀為橢圓。邊的默認樣式為黑色實線箭頭,我們可以在腳本中做一下修改,將頂點改為方形,邊改為虛線。
設置點和線的形狀和顏色
在digraph的花括號內,添加頂點和邊的新定義:
node [shape="record"];
edge [style="dashed"];
則繪制的效果如下:
+ 進一步修改頂點和邊樣式
進一步,我們將頂點a的顏色改為淡綠色,並將c到d的邊改為紅色,腳本如下:
digraph test{
node [shape="record"];
edge [style="dashed"];
a [style="filled", color="black", fillcolor="skyblue"];
b;
c;
d;
e;
a->c;
b->c;
c->e;
d->e [color="red"];
}
繪制的結果如下:
應當注意到,頂點和邊都接受屬性的定義,形式為在頂點和邊的定義之后加上一個由方括號括起來的key-value列表,每個key-value對由逗號隔開。如果圖中頂點和邊采用統一的風格,則可以在圖定義的首部定義node, edge的屬性。比如上圖中,定義所有的頂點為方框,所有的邊為虛線,在具體的頂點和邊之后定義的屬性將覆蓋此全局屬性。如特定與a的綠色,c到d的邊的紅色。
- 以圖片為節點
除了顏色,節點還可以使用圖片。不過需要注意的是,在使用圖片作為節點的時候,需要將本來的形狀設置為none,並且將label置為空字符串,避免出現文字對圖片的干擾。
digraph test{
node [shape="record"];
edge [style="dashed"];
a [style="filled", color="black", fillcolor="skyblue"];
b;
c;
d [shape="none", image="C:\Users\Marvin\Desktop\timg.jpg", label=""];
e;
a->c;
b->c;
c->e;
d->e [color="red"];
}
digraph是有向圖,graph是無向圖,要注意,->用在有向圖中,–用在無向圖中表示一條邊,不能混用。
//digraph是有向圖,graph是無向圖,要注意,->用在有向圖中,--用在無向圖中表示一條邊,不能混用。
digraph G { //第一行給出了圖的類型和名字
main -> parse -> execute; //當一個點第一次出現,它就被創建了
main -> init; //用->標示符創建一條邊
main -> cleanup;
execute -> make_string;
execute -> printf
init -> make_string;
main -> printf;
execute -> compare;
}
//然后在cmd下用這個文件運行dot
//dot -Tps graph1.dot -o graph1.ps
//這是ps格式,你也可以改成jpg等格式。
//-Tps選擇了postscript output,
//就畫出了這個圖。
來看下一個稍微復雜點的例子,我們開始手動的設置一下圖的屬性。可以給點設置屬性,也可以給邊設置屬性。先來講講怎么設置邊的屬性,在每條邊后面的雙括號里設置邊的屬性。也可以在用edge設置邊的默認值。而給點設置屬性就必須給每個點單獨的設置一個屬性,node表示點的默認值。
//點的默認參數是shape=ellipse, width=.75, height=.5 and labeled by the node name.
//一些點的形狀在appendix.h 中,一些常用的形狀有bos,circle,record,plaintext。
digraph G {
size ="4,4";// 把圖的尺寸設為4 inch,4 inch
main [shape=box];//把main點的形狀設為方形
main -> parse [weight=8]; //weight是設置了這條邊的重要程度,默認是1。
parse -> execute;
main -> init [style=dotted]; //讓這條線是點狀的
main -> cleanup;
execute -> { make_string; printf} //這條語句一次連了兩條線
init -> make_string;
edge [color=red]; // so is this 把邊的默認顏色設為了red
main -> printf [style=bold,label="100 times"]; //label就是在邊上寫了一行字
make_string [label="make a\nstring"];// 讓make_string變成了一個兩行的字符串(注意那個\n)。
node [shape=box,style=filled,color=".7 .3 1.0"];// 設置了一下點的默認參數,藍色,這個被用在了compare中。
execute -> compare;
}
畫出以下圖形:
//可以設置每條邊箭頭的方向,用dir,有forward(default),back,both,none 四種。
digraph html {
A -> B[dir = both];
B -> C[dir = none];
C -> D[dir = back];
D -> A[dir = forward];
}
//點的shape 除了record 和Mrecord 這兩種之外,其他的形狀都是多邊形,而我們可以對多邊形進行一下屬性上的設置,
//shape = polygon。Sides 用於設置它的邊數,peripheries 用於設置多邊形的外框的層數,
//regular = true 可以讓你的多邊形是一個規則的多邊形,orientation =*,可以讓你的多邊形旋轉一個角度,
//如orientation = 15 就是轉了15 度。Skew 后面跟一個(-1.0~1.0)的小數,能讓你的圖形斜切一個角度,distortion 是讓你的圖形產生透視效果。
digraph G {
a -> b -> c;
b -> d;
a [shape=polygon,sides=5,peripheries=3,color=lightblue,style=filled];
c [shape=polygon,sides=4,skew=.4,label="hello world"]
d [shape=invtriangle];
e [shape=polygon,sides=4,distortion=.7];
}
digraph A{
A -> B;
A[orientation = 15, regular = true, shape = polygon, sides = 8, peripheries = 4, color= red style = filled];
B[shape = polygon, sides = 4, skew = 0.5, color = blue];
}
//record 和Mrecord 的區別就是Mrecord 的角是圓的。Record 就是由衡的和豎的矩形組成的圖形。
digraph structs {
node [shape=record];
struct1 [shape=record,label="<f0> left|<f1> mid\ dle|<f2> right"];
struct2 [shape=record,label="<f0> one|<f1> two"];
struct3 [shape=record,label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"];
struct1 -> struct2;
struct1 -> struct3;
}
當你的線和線label 比較多時,可以給線的屬性decorate = true,使得每條線的label 與所屬線之間連線。還可以給每條線加上headlabel 和taillabel,給每條線的起始點和終點加上label,他們的顏色由labelfontcolor 來決定,而label 的顏色由fontcolor 來決定。
//
graph A{
label = "I love you"; //給這幅圖設置,名字
labelloc = b; //圖名字的位置在bottom,也可以是t
labeljust = l; //圖名字的位置在left,也可以是r
edge[decorate = true];
C -- D[label = "s1"];
C -- E[label = "s2"];
C -- F[label = "s3"];
D -- E[label = "s4"];
D -- F[label = "s5"];
edge[decorate = false, labelfontcolor = blue, fontcolor = red];
C1 -- D1[headlabel = "c1", taillabel = "d1", label = "c1 - d1"];
}
在dot 中我們可以用html 語言寫一個table。在label 后用< >而不是”“就能引入html 語言。
//在dot 中我們可以用html 語言寫一個table。在label 后用< >而不是""就能引入html 語言。 digraph html { abc [shape=none, margin=0, label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> <TR><TD ROWSPAN="3"><FONT COLOR="red">hello</FONT><BR/>world</TD> <TD COLSPAN="3">b</TD> <TD ROWSPAN="3" BGCOLOR="lightgrey">g</TD> <TD ROWSPAN="3">h</TD> </TR> <TR><TD>c</TD> <TD PORT="here">d</TD> <TD>e</TD> </TR> <TR><TD COLSPAN="3">f</TD> </TR> </TABLE>>]; }
//這樣創造了一個5 行5 列的表格,我們可以在表格中打字。 digraph html { abc [shape=none, margin=0, label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> <TR><TD>0</TD><TD>1</TD><TD>2</TD><TD>3</TD><TD>4</TD> </TR> <TR><TD>1</TD><TD></TD><TD></TD><TD></TD><TD></TD> </TR> <TR><TD>2</TD><TD></TD><TD></TD><TD></TD><TD></TD> </TR> <TR><TD>3</TD><TD></TD><TD></TD><TD></TD><TD></TD> </TR> <TR><TD>4</TD><TD></TD><TD></TD><TD></TD><TD></TD> </TR> </TABLE>>]; }
設置點和線的位置,子圖的概念
默認時圖中的線都是從上到下的,我們可以將其改為從左到右,在文件的最上層打入rankdir=LR 就是從左到右,默認是TB(top -> bottom),也可以是RL,BT。當圖中時間表之類的東西時,我們會需要點能排在一行(列),這時要用到rank,用花括號把rank=same,然后把需要並排的點一次輸入。
//
digraph html {
rankdir = LR;
{
node[shape = plaintext];
1995 -> 1996 -> 1997 -> 1998 -> 1999 -> 2000 -> 2001;
}
{
node[shape = box, style = filled];
WAR3 -> Xhero -> Footman -> DOTA;
WAR3 -> Battleship;
}
{rank = same; 1996; WAR3;}
{rank = same; 1998; Xhero; Battleship;}
{rank = same; 1999; Footman;}
{rank = same; 2001; DOTA;}
}
設立一條邊時,我們可以制定這條邊從起點的那個位置射出和從哪個位置結束。控制符有”n”,”ne”,”e”, “se”, “s”, “sw”, “w” 和 “nw”,具體效果見下:
digraph html {
node[shape = box];
c:n -> d[label = n];
c1:ne -> d1[label = ne];
c2:e -> d2[label = e];
b:se -> a[label = se];
c3:s -> d3[label = s];
c4:sw -> d4[label = sw];
c5:w -> d5[label = w];
c6:nw -> d6[label = nw];
}
我們也可以在record 中給點定義一些port,因為record 類型中都是一個個格子。
digraph html {
label = "Binary search tree";
node[shape = record];
A[label = "<f0> | <f1> A |<f2> "];
B[label = "<f0> | <f1> B |<f2> "];
C[label = "<f0> | <f1> C |<f2> "];
D[label = "<f0> | <f1> D |<f2> "];
E[label = "<f0> | <f1> E |<f2> "];
A:f0:sw -> B:f1;
A:f2:se -> C:f1;
B:f0:sw -> D:f1;
B:f2:se -> E:f1;
}
//構造一個HASH 表
digraph G {
nodesep=.05;
rankdir=LR;
node [shape=record,width=.1,height=.1];
node0 [label = "<f0> |<f1> |<f2> |<f3> |<f4> |<f5> |<f6> | ",height=2.5];
node [width = 1.5];
node1 [label = "{<n> n14 | 719 |<p> }"];
node2 [label = "{<n> a1 | 805 |<p> }"];
node3 [label = "{<n> i9 | 718 |<p> }"];
node4 [label = "{<n> e5 | 989 |<p> }"];
node5 [label = "{<n> t20 | 959 |<p> }"] ;
node6 [label = "{<n> o15 | 794 |<p> }"] ;
node7 [label = "{<n> s19 | 659 |<p> }"] ;
node0:f0 -> node1:n;
node0:f1 -> node2:n;
node0:f2 -> node3:n;
node0:f5 -> node4:n;
node0:f6 -> node5:n;
node2:p -> node6:n;
node4:p -> node7:n;
}
子圖的繪制
graphviz支持子圖,即圖中的部分節點和邊相對對立(軟件的模塊划分經常如此)。比如,我們可以將頂點c和d歸為一個子圖:
digraph test{
node [shape="record"];
edge [style="dashed"];
a [style="filled", color="black", fillcolor="skyblue"];
b;
c;
subgraph cluster_de{
label="d and e";
bgcolor="mintcream";
d [shape="none", image="C:\Users\Marvin\Desktop\timg.jpg", label=""];
e;
}
a->c;
b->c;
c->e;
d->e [color="red"];
}
將d和e划分到cluster_de這個子圖中,標簽為d and e,並添加背景色,以方便與主圖區分開,繪制結果如下:
畫一個子圖就是subgraph cluster#,必須有cluster 前綴。
digraph g {
subgraph cluster0 {
//我是一個子圖,subgraph定義了我,
node[style = filled, color = white];
//我之內的節點都是這種樣式
style = filled;
//我的樣式是填充
color = lightgrey;
//我的顏色
a0->a1->a2->a3;
label = "prcess #1"
//我的標題
}
subgraph cluster1 {
//我也是一個子圖
node[style = filled];
b0->b1->b2->b3;
label = "process #2";
color = blue;
}
//定義完畢之后,下面還是連接了
start->a0;
start->b0;
a1->b3;
b2->a3;
a3->end;
b3->end;
start[shape=Mdiamond];
end[shape=Msquare];
}
當你想把一條邊連到一個子圖的邊界上,先輸入compound = true,然后就能用lhead 和ltail來設置連接的子圖了。
digraph G{
compound=true;
subgraph cluster0{
a->b;
a->c;
b->d;
c->d;
}
subgraph cluster1{
e->g;
e->f;
}
b->f[lhead=cluster1];
d->e;
c->g[ltail=cluster0,lhead=cluster1];
c->e[ltail=cluster0];
d->h;
}
多邊形結點(http://www.graphviz.org/doc/info/shapes.html)
下面顯示了可能的多邊形形狀。
數據結構的可視化
實際開發中,經常要用到的是對復雜數據結構的描述,graphviz提供完善的機制來繪制此類圖形。
一個hash表的數據結構
比如一個hash表的內容,可能具有下列結構:
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表的數據結構
從代碼上看,由於結構體存在引用關系,不夠清晰,如果層次較多,則很難以記住各個結構之間的關系,我們可以通過下圖來更清楚的展示:
腳本如下:
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"];
}
狀態圖
有限自動機示意圖
上圖是一個簡易有限自動機,接受a及a結尾的任意長度的串。其腳本定義如下:
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的表示不用繪制邊框,僅展示純文本內容,這個在繪圖中,繪制指示性的文本時很有用,如上圖中的Machine: a。
其他實例
一棵簡單的抽象語法樹(AST)
表達式 (3+4)*5 在編譯時期,會形成一棵語法樹,一邊在計算時,先計算3+4的值,最后與5相乘。
對應的腳本如下:
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;
}
簡單的UML類圖
下面是一簡單的UML類圖,Dog和Cat都是Animal的子類,Dog和Cat同屬一個包,且有可能有聯系(0..n)。
腳本如下:
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..*"];
}
狀態圖
腳本:
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)" ];
}
時序圖
““
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"];
}
““
復雜實例
腳本如下
digraph G { rankdir=LR node [shape=plaintext] 05 a [ label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> <TR> <TD ROWSPAN="3" BGCOLOR="yellow">class</TD> </TR> <TR> <TD PORT="here" BGCOLOR="lightblue">qualifier</TD> </TR> </TABLE>> ] b [shape=ellipse style=filled label=< <TABLE BGCOLOR="bisque"> <TR> <TD COLSPAN="3">elephant</TD> <TD ROWSPAN="2" BGCOLOR="chartreuse" VALIGN="bottom" ALIGN="right">two</TD> </TR> <TR> <TD COLSPAN="2" ROWSPAN="2"> <TABLE BGCOLOR="grey"> <TR> <TD>corn</TD> </TR> <TR> <TD BGCOLOR="yellow">c</TD> </TR> <TR> <TD>f</TD> </TR> </TABLE> </TD> <TD BGCOLOR="white">penguin</TD> </TR> <TR> <TD COLSPAN="2" BORDER="4" ALIGN="right" PORT="there">4</TD> </TR> </TABLE>> ] c [ label=< long line 1<BR/>line 2<BR ALIGN="LEFT"/>line 3<BR ALIGN="RIGHT"/>> ] subgraph { rank=same b c } a:here -> b:there [dir=both arrowtail = diamond] c -> b d [shape=triangle] d -> c [label=< <TABLE> <TR> <TD BGCOLOR="red" WIDTH="10"> </TD> <TD>Edge labels<BR/>also</TD> <TD BGCOLOR="blue" WIDTH="10"> </TD> </TR> </TABLE>> ] }