graphviz工具及其原理

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上,到時候歡迎各路高人蔘加此項目。

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