Graphviz介紹

Graphviz介紹

graphviz是貝爾實驗室幾個計算機牛人設計的一個開源 的圖表(計算機科學中數據結構中的圖)可視化項目,主要用C語言實現,主要實現了一些圖佈局算法。通過這些算法,可以將圖中的節點在畫布上比較均勻的分佈,縮短節點之間的邊長,並且儘量的減少邊的交叉。

 

graphviz提供命令式的繪圖方式,它提供一個dot語言 用來編寫繪圖腳本,然後對這個腳本進行解析,分析出其中的定點,邊以及子圖,然後根據屬性進行繪製。具體的可以看一個例子,這個例子來自官方的文檔。

 

Dot代碼 
  1. digraph G {  
  2.     main -> parse -> execute;  
  3.     main -> init;  
  4.     main -> cleanup;  
  5.     execute -> make_string;  
  6.     execute -> printf  
  7.     init -> make_string;  
  8.     main -> printf;  
  9.     execute -> compare;  
  10. }  

 

digraph指定該圖是一個有向圖(directed graph),->表示一條邊,main,parse,execute等是頂點,運行出來的效果很好看,如下圖:

 


 

需要注意的是,我在這個dot腳本中沒有指定任何的關於圖的位置的信息,佈局器會自動的根據圖形的類型進行佈局,並最終展現出來。

再來看一個比較複雜,並且是程序員經常使用的功能,數據結構圖:

Java代碼 
  1. digraph g {  
  2.     node [shape = record,height=.1];  
  3.     node0[label = "<f0> |<f1> G|<f2> "];  
  4.     node1[label = "<f0> |<f1> E|<f2> "];  
  5.     node2[label = "<f0> |<f1> B|<f2> "];  
  6.     node3[label = "<f0> |<f1> F|<f2> "];  
  7.     node4[label = "<f0> |<f1> R|<f2> "];  
  8.     node5[label = "<f0> |<f1> H|<f2> "];  
  9.     node6[label = "<f0> |<f1> Y|<f2> "];  
  10.     node7[label = "<f0> |<f1> A|<f2> "];  
  11.     node8[label = "<f0> |<f1> C|<f2> "];  
  12.     "node0":f2 -> "node4":f1;  
  13.     "node0":f0 -> "node1":f1;  
  14.     "node1":f0 -> "node2":f1;  
  15.     "node1":f2 -> "node3":f1;  
  16.     "node2":f2 -> "node8":f1;  
  17.     "node2":f0 -> "node7":f1;  
  18.     "node4":f2 -> "node6":f1;  
  19.     "node4":f0 -> "node5":f1;  
  20. }  

 

運行後的效果如下圖所示:

 


 

不知道其他的程序員怎樣,反正我對命令行情有獨鍾,比較喜歡這一類的工具。最早接觸的計算機系統正是一個沒有圖形系統的BSD,由此對命令行的,沒有界面的程序都特別感興趣。

相關的想法

自從使用了graphviz以後,一直想着把這個好東西移植到java下來,大概的思想跟graphviz類似:

  • 解析dot腳本,生成圖的對象,這個圖中包括節點,邊以及子圖等對象,這些對象上都綁定着相應的屬性
  • 將解析出來的圖對象發送給layout engine進行處理,layout engine可以選擇佈局策略,比如流佈局等
  • 從layout engine中得到佈局後的圖對象,並交給image engine處理,得到最終結果,負責展示或者保存等

dot 的語法定義比較簡單,我已經用javacc構造了一個dot的分析器,現在可以從dot文件中構建出圖對象出來,不過還需要進一步完善,可以看看這個BNF定義:

 

Java代碼 
  1. graph -> [strict] (digraph|graph) id '{' stmt-list '}'  
  2. stmt-list -> [stmt [';'] [stmt-list] ]  
  3. stmt -> attr-stmt | node-stmt | edge-stmt | subgraph | id '=' id  
  4. attr-stmt -> (graph | node | edge) attr-list  
  5. attr-list -> '[' [a-list] ']' [attr-list]  
  6. a-list -> id '=' id [','][a-list]  
  7. node-stmt -> node-id [attr-list]  
  8. node-id -> id [port]  
  9. port -> port-location [port-angle] | port-angle [port-location]  
  10. port-location -> ':' id | ':' '(' id ',' id ')'  
  11. port-angle ->'@' id  
  12. edge-stmt -> (node-id | subgraph) edgeRHS [attr-list]  
  13. edgeRHS -> edgeop (node-id | subgraph) [edgeRHS]  
  14. 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如下:

Java代碼 
  1. public static void main(String[] args){  
  2.     Parser parser = new GCodeParser("file.g");  
  3.     GraphSet gs = parser.parse();  
  4.     GraphSet layouted =   
  5.         new GraphLayoutEngine(gs).layout();  
  6.     ImageEngine imgEngine =   
  7.         new GraphImageEngine(layouted);  
  8.     imgEngine.export(0);  
  9. }  

 

 

同graphviz一樣,先調用分析器構造出圖的集合GraphSet(包括多個圖,每個圖中包括Vertex,Edge,及SubGraph),然後進行佈局,最後將通過佈局後的GraphSet繪製結果。目前完成了框架的設計部分,分析器部分基本完成,layout部分只實現了一個策略,即force-based 佈局算法,不過layout engine被設計成可以插拔的模型,如果有了新的算法實現,可以很容易的整合起來。

 

p.s.如果時間允許,我會將這個項目jraph(暫定名)託管到google code上,到時候歡迎各路高人蔘加此項目。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章