Graphviz介紹
graphviz是貝爾實驗室幾個計算機牛人設計的一個開源 的圖表(計算機科學中數據結構中的圖)可視化項目,主要用C語言實現,主要實現了一些圖佈局算法。通過這些算法,可以將圖中的節點在畫布上比較均勻的分佈,縮短節點之間的邊長,並且儘量的減少邊的交叉。
graphviz提供命令式的繪圖方式,它提供一個dot語言 用來編寫繪圖腳本,然後對這個腳本進行解析,分析出其中的定點,邊以及子圖,然後根據屬性進行繪製。具體的可以看一個例子,這個例子來自官方的文檔。
- digraph G {
- main -> parse -> execute;
- main -> init;
- main -> cleanup;
- execute -> make_string;
- execute -> printf
- init -> make_string;
- main -> printf;
- execute -> compare;
- }
digraph指定該圖是一個有向圖(directed graph),->表示一條邊,main,parse,execute等是頂點,運行出來的效果很好看,如下圖:
需要注意的是,我在這個dot腳本中沒有指定任何的關於圖的位置的信息,佈局器會自動的根據圖形的類型進行佈局,並最終展現出來。
再來看一個比較複雜,並且是程序員經常使用的功能,數據結構圖:
- digraph g {
- node [shape = record,height=.1];
- node0[label = "<f0> |<f1> G|<f2> "];
- node1[label = "<f0> |<f1> E|<f2> "];
- node2[label = "<f0> |<f1> B|<f2> "];
- node3[label = "<f0> |<f1> F|<f2> "];
- node4[label = "<f0> |<f1> R|<f2> "];
- node5[label = "<f0> |<f1> H|<f2> "];
- node6[label = "<f0> |<f1> Y|<f2> "];
- node7[label = "<f0> |<f1> A|<f2> "];
- node8[label = "<f0> |<f1> C|<f2> "];
- "node0":f2 -> "node4":f1;
- "node0":f0 -> "node1":f1;
- "node1":f0 -> "node2":f1;
- "node1":f2 -> "node3":f1;
- "node2":f2 -> "node8":f1;
- "node2":f0 -> "node7":f1;
- "node4":f2 -> "node6":f1;
- "node4":f0 -> "node5":f1;
- }
運行後的效果如下圖所示:
不知道其他的程序員怎樣,反正我對命令行情有獨鍾,比較喜歡這一類的工具。最早接觸的計算機系統正是一個沒有圖形系統的BSD,由此對命令行的,沒有界面的程序都特別感興趣。
相關的想法
自從使用了graphviz以後,一直想着把這個好東西移植到java下來,大概的思想跟graphviz類似:
- 解析dot腳本,生成圖的對象,這個圖中包括節點,邊以及子圖等對象,這些對象上都綁定着相應的屬性
- 將解析出來的圖對象發送給layout engine進行處理,layout engine可以選擇佈局策略,比如流佈局等
- 從layout engine中得到佈局後的圖對象,並交給image engine處理,得到最終結果,負責展示或者保存等
dot 的語法定義比較簡單,我已經用javacc構造了一個dot的分析器,現在可以從dot文件中構建出圖對象出來,不過還需要進一步完善,可以看看這個BNF定義:
- graph -> [strict] (digraph|graph) id '{' stmt-list '}'
- stmt-list -> [stmt [';'] [stmt-list] ]
- stmt -> attr-stmt | node-stmt | edge-stmt | subgraph | id '=' id
- attr-stmt -> (graph | node | edge) attr-list
- attr-list -> '[' [a-list] ']' [attr-list]
- a-list -> id '=' id [','][a-list]
- node-stmt -> node-id [attr-list]
- node-id -> id [port]
- port -> port-location [port-angle] | port-angle [port-location]
- port-location -> ':' id | ':' '(' id ',' id ')'
- port-angle ->'@' id
- edge-stmt -> (node-id | subgraph) edgeRHS [attr-list]
- edgeRHS -> edgeop (node-id | subgraph) [edgeRHS]
- subgraph -> [subgraph id] '{' stmt-list '}' | subgraph id
(最近老是感覺時間不夠用,有很多有意思的項目要做,比如要完善前幾天說的那個bbms(Bus Based Message Service), 再比如修改用Swing和Smack做一個jabber的客戶端jTalk,都是很有搞頭的,唉,扯遠了。)
當然graphviz的功能不至於此,它提供一個lib,可以用來將繪圖引擎嵌入在自己的應用中。這是一個很有意義的事,我們可以不必掌握佈局部分的複雜算法,把精力放在業務邏輯部分,將最後的圖對象交給這個引擎來處理即可。當然,如果你正好和我一樣,想了解其神奇的佈局算法,不妨翻翻它的源碼,歡迎交流之至。
我在週末分析了下graphviz的內部結構,並建立了一個java的項目jraph ,用來做簡單的移植,主要是學習之用,其實,Java的圖庫還是相當豐富的,比如JGraph ,JGraphT,Prefuse,TouchGraph等等,用來做項目當然是很便捷的,但是我還是比較喜歡看着一個算法被自己實現的過程,特別是這種比較神奇的算法,哈哈。
預期中的API如下:
- public static void main(String[] args){
- Parser parser = new GCodeParser("file.g");
- GraphSet gs = parser.parse();
- GraphSet layouted =
- new GraphLayoutEngine(gs).layout();
- ImageEngine imgEngine =
- new GraphImageEngine(layouted);
- imgEngine.export(0);
- }
同graphviz一樣,先調用分析器構造出圖的集合GraphSet(包括多個圖,每個圖中包括Vertex,Edge,及SubGraph),然後進行佈局,最後將通過佈局後的GraphSet繪製結果。目前完成了框架的設計部分,分析器部分基本完成,layout部分只實現了一個策略,即force-based 佈局算法,不過layout engine被設計成可以插拔的模型,如果有了新的算法實現,可以很容易的整合起來。
p.s.如果時間允許,我會將這個項目jraph(暫定名)託管到google code上,到時候歡迎各路高人蔘加此項目。