首頁 > 軟體

Graphviz的使用及中文亂碼問題解決

2020-06-16 17:42:31

緣起
在Linux下的繪圖軟體,自動的,半自動的,最後遇到神器Graphviz。整個流程還算波折,起初,編譯docbook的部分遇到這個軟體,後來,在看Korat: Automated Testing Based on Java Predicates(2000年ACM的最佳論文),發現其軟體實現Korat中使用了Graphviz的實現來繪圖程式依賴圖,最後,在學習Latex的時候,考慮要嵌入圖片,想起這個繪圖工具,就對這個著名的工具做了一些研究,其實就是Google了一下,只能算是文獻調研。

以下,是OpenFoundry上看到的介紹Graphviz的文章,原文為繁體字,用google translation轉換為簡體中文,修改一下一些術語,新增了一下內容(亂碼問題)。

1. 簡介
Graphviz 是一個運用廣泛的命令列繪圖軟體,不過說是繪圖軟體,它能繪的圖並不是一般人想像中的漫畫或logo,而是數學意義上的"graph",比較通俗的說法就是「關係圖」。

舉例來說,像是下面這種圖:

 

2. 安裝
Graphviz 支援Windows、Mac OS X、FreeBSD、Solaris、Linux 等多種系統。Ubuntu或Debian下安裝很簡單,sudo apt-get install graphviz。

Windows使用者,請前往這裡下載安裝檔:http://www.graphviz.org/Download_windows.php

Mac OS X的使用者請往這邊走:http://www.graphviz.org/Download_macos.php

3. Graphviz的使用
# Graphviz

<cmd> <inputfile> -T <format> -o <outputfile>

#舉例:輸出png

dot input.dot -T png -o output.png

#舉例:一樣是輸出png ,只不過檔名是txt

dot input.dot -T png -o output.txt

首先,我們看看上面的<cmd> 部份。

Graphviz 的<cmd> 有好幾種,每種使用方法都完全相同,差別只在於渲染出來的圖片效果不一樣。man中的簡介是這樣的:

dot
 渲染的圖具有明確方向性。
 
neato
 渲染的圖缺乏方向性。
 
twopi
 渲染的圖採用放射性布局。
 
circo
 渲染的圖採用環型布局。
 
fdp
 渲染的圖缺乏方向性。
 
sfdp
 渲染大型的圖,圖片缺乏方向性。
 

可以透過man <cmd> 取得進一步說明。但還是親自用用比較容易理解。在本文中,凡沒有說明的圖,預設都是以dot渲染出來的。

繼續往下看。在Graphviz中,若您不指定-T引數,Graphviz並不會自動猜測您想要產生什麼格式,只會以預設格式渲染。可選格式相當多,包括(但不限於)jpg、png、svg等,全部列表可見官網說明頁的最下方。

-o 可讓您指定儲存檔案的檔名。如果您不用-o 選項指定輸出檔名,Graphviz 則會將結果輸出到標準輸出上(圖片格式輸出到標準輸出就是亂碼的二進位制檔案)。

除非用法很特殊,否則這兩個引數,每次都要輸入並傳遞給dot。這樣輸入命令就是:

dot -Tpng demo.dot -o demo.png

為了減少打字的次數,編寫簡單的shell指令碼dot.sh,其中涉及獲取檔名和擴充套件,從網上搜的,工作的很好,但不太清楚實現原理:

#!/bin/sh

# dot.sh

file=$1

filename=${file%.*}

extension=${file##*.}

outfile=${filename}.png

dot -Tpng $file -o $outfile

#show generate image file

eog $outfile &
 

給指令碼dot.sh加上執行許可權,然後呼叫命令就簡化為:

./dot.sh demo.dot

當然,如果整個命令很常用的話,可以將改作函數其寫到.bashrc中。

4. dot語言說明
指揮Graphviz 繪圖時,所使用的語言叫作"dot"。下邊就來介紹如何使用它。

4.1. 有向圖與無向圖
使用dot 語言,第一步就是決定要畫哪種圖。

圖分兩種:有向圖與無向圖。

有向圖以digraph申明圖片,節點間的關係寫為"->"。

/*demo1。順便一提,在dot語言中可使用C++中允許的註解。本行為C風格註解*/
digraph demo1{ //這也是註解,C++風格的。
a -> b -> c;
c -> a;
}

結果圖:

無向圖以graph 宣告圖片,節點間的關係可以寫為"--"。

//demo2
graph demo2{
a -- b -- c;
c -- a;
}

結果圖:


其中demo1 與demo2 是圖片的名稱。

4.2. 使用引號
上文中的a, b, c 除了作為程式內的識別字以外,也會成為節點的顯示名稱(label)。不過如果這名稱中混了中文或夾了空格,Graphviz 就有可能搞錯你的意思。

為防不必要的誤解,所以平常最好都用英文引號括住。就像下面這樣:

//demo3
digraph {
"總攻" -> "受";
"強攻" -> "受";
"健氣攻" -> "受";
}

圖4:混合了空白的示範

這樣就沒問題了!

子圖與簡化技巧

來看個複雜一點的例子,這是一份地中海海域的大略連線圖:

//demo4

graph G{

"黑海" -- "亞速海";

"黑海" -- "博斯普魯斯海峽"

"達達尼爾海峽" -- "愛琴海"

subgraph cluster_T{//新東西

label = "黑海海峽";//新東西

"達達尼爾海峽" -- "馬爾馬拉海" -- "博斯普魯斯海峽";

}

subgraph cluster_M{

label = "地中海海域";

"中部地中海" -- {"愛琴海" "愛奧尼亞海" "西西里海峽"}; //也是新東西

"西部地中海" -- {"西西里海峽" "第勒尼安海" "利古里亞海" "伊比利海" "阿爾沃蘭海"};

"愛奧尼亞海" -- "亞得里亞海";

"阿爾沃蘭海" -- "直布羅陀海峽";

}

}

結果圖:

 圖5:地中海海域連線圖,使用dot渲染。

這張圖有些新東西可以看。

第一個是subgraph 關鍵字。一如名字所示,他是用來定義「次級圖片」用的。

次級圖片在dot的官方檔案中常被叫作cluster subgraph,特指圖示中被方框包裹起來的那兩塊,其定義方式和一般的graph非常相似,不過使用上有兩件事需要留意:

graph的命名得以cluster字首開頭,否則語法雖然能過關,但生不出圖面上您預期的效果。

如果父圖是無向圖,他本身也得是無向圖;反之如果父圖是有向圖,這邊也得乖乖照著來。

第二個重點是下面這段:

1 "中部地中海" -- {"愛琴海" "愛奧尼亞海" "西西里海峽"};

用大括號括起,用空格分開-這是一口氣將好幾個節點群組起來同時操作的方法,其等效於:

1 "中部地中海" -- "愛琴海"; 2 "中部地中海" -- "愛奧尼亞海"; 3 "中部地中海" -- "西西里海峽";

您甚至可以用以下程式碼畫出圖6:

//demo5

digraph G{//{}中的注意空格

{ a b c} -> { d e f }

}


圖6:大括號效果示意圖

這語法糖很方便好吃,可以靈活運用。

第三個不同處在於label=XXX 這行。這是「屬性」的指定方式。

關於屬性,我們下章再講。

4.3. 屬性
有了前面介紹過的技巧,所有圖面關係都可以順利地繪製出來。

然而,通常我們畫圖的時候,還會對圖片做一些特別的處理。好比說把字加粗、把圖變色、把標籤或連線線的外型改變、把某些節點水平對齊......諸如此類。

要控制這些東西,就要用到屬性。

屬性有四種:

1. 用在節點上(Node, N)

2. 用線上段上(Edge, E)

3. 用在根圖片上(Graph, G)

4. 用在子圖片上(Cluster subgraph, C)

您可以閱讀手冊中的表,判斷哪些屬效能用在哪些地方。

那麼,屬性要怎麼用呢?

4.3.1. 屬性的套用
如果要設定根圖片或子圖片的屬性,得像前面範例中所示的那樣,在圖片的大括號範圍內指定..

屬性名稱=值;

這樣就行了。

對於節點(node) 的屬性,有以下幾種指定法:

1節點名[節點屬性名=值];

2節點名[節點屬性名=值,節點屬性名=值];

3 node [節點屬性名=值,節點屬性名=值];

屬性指定的語句必須要被中括號括起。當一次指定多值時,需用英文逗點隔開。

第三行中的node 是個關鍵字,用來代稱「圖片範圍內」所有「還沒建立」的節點,或者您也可將它理解為:在當前大括號的範圍內,所有尚未建立節點的屬性預設值,會被這個語句給變更。

線段(edge)的屬性指定,與節點屬性指定方式很類似:

1節點名->節點名[線段屬性名=值];

2節點名--節點名[線段屬性名=值,線段屬性名=值];

3 edge [線段屬性名=值,線段屬性名=值];

其中edge 是關鍵字。

這邊順便補充一個關於線段的觀念:有些線段相關的屬性,具有head值與tail值。而這邊說的head 與tail,得將它想像成一個「箭頭」的形狀(就像是「a -> b」這樣)。

對於線段來說,這個箭頭指向的頭部才是head。這可能和直覺上不一樣,因為這邊說的「Head」其實是兩個節點中,後面的那一個。

4.3.2. 屬性範例
把先前的看過的例子加上一些屬性試試。

//demo6

graph G{

"黑海" [shape = circle, color = blueviolet, fontcolor = blueviolet, fontsize = 20];

"黑海" -- "亞速海" [label = "刻赤海峽"];

subgraph cluster_T{

label = "黑海海峽";

fontsize = 24;

fillcolor = darkslategray;

style = filled;

fontcolor = white;

node [fontcolor = white, color = white];

"博斯普魯斯海峽" -- "馬爾馬拉海" -- "達達尼爾海峽" [color = white];

"博斯普魯斯海峽" [shape = parallelogram];

"達達尼爾海峽" [shape = parallelogram];

}

"黑海" -- "博斯普魯斯海峽" [color = red ,penwidth = 2];

"達達尼爾海峽" -- "愛琴海" [color = red ,penwidth = 2];

subgraph cluster_M{

label = "地中海海域";

fontsize = 24;

"西部地中海" [shape = Mcircle, style = filled, color = grey, fillcolor = aquamarine, fontsize = 20];

"中部地中海" [shape = Mcircle, style = filled, color = grey, fillcolor = aquamarine, fontsize = 20];

"直布羅陀海峽" [shape = parallelogram, fontcolor = red];

"西西里海峽" [shape = parallelogram ];

"中部地中海" -- {"愛琴海" "愛奧尼亞海" "西西里海峽"};

"西部地中海" -- {"西西里海峽" "第勒安海" "利古里亞海" "伊比利海" "阿爾沃蘭海"};

"愛奧尼亞海" -- "亞得里亞海"; 30 "阿爾沃蘭海" -- "直布羅陀海峽";

}

}


圖7:地中海海域連線圖(加入屬性)。

更多詳情見請繼續閱讀下一頁的精彩內容http://www.linuxidc.com/Linux/2016-05/131050p2.htm


IT145.com E-mail:sddin#qq.com