有個軟件,名叫 dot,來自 Graphviz。它只懂 DOT 語言,可以根據這種語言的描述來畫圖。node
它會畫什麼圖呢?git
有向圖。編程
馬爾克斯的《百年孤獨》這本書的主角是一個家族。這個家族的譜系,就能夠構成有向圖。若未讀過《百年孤獨》,不妨去讀幾遍。雖然也算世界名著,但應該很快就能看完。有很多人一年能讀幾百本書,《百年孤獨》老是會出如今他們所列書單以內。下面就嘗試用 DOT 語言描述一下這個家族的譜系。segmentfault
這個很孤獨的家族,一共七代人:ide
@ 七代人 # {第一代 何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉} {第二代 何塞·阿爾卡蒂奧(水手) 庇拉爾·特爾內拉(占卜者) 奧雷里亞諾·布恩迪亞(上校) 阿瑪蘭妲(灼手者) 十七位不知名女性} {第三代 阿爾卡蒂奧(小獨裁者) 桑塔索菲亞·德拉·彼達 奧雷里亞諾·何塞(姑媽控) 奧雷里亞諾·特里斯特、奧雷里亞·諾斯特諾以及其餘十五人} {第四代 蕾梅黛絲(美人兒) 何塞·阿爾卡蒂奧第二(鬥雞者) 奧雷里亞諾第二(養牛者) 費爾南達·德爾·卡皮奧(女王樣)} {第五代 何塞·阿爾卡蒂奧(掘金者)雷納塔·蕾梅黛絲(梅梅) 馬烏里肖·巴比倫 阿瑪蘭妲·烏爾蘇拉(牝貓)} {第六代 奧雷里亞諾·巴比倫(姨媽控)} {第七代 豬尾巴} @
爲了簡單起見,這個家族的風流韻事以及未能留下子嗣的女性,被我刻意忽略了。我以爲這些女性與這個家族的關係能夠作成一張無向圖,這個之後再說吧。gitlab
將上述 DOT 代碼嵌入到有向圖的結構中,可得ui
@ 第一次嘗試 # digraph test_1st { # 七代人 @ } @
digraph
是一個關鍵字,或將其視爲一個指令,用於聲明有向圖結構。spa
注:假若看過《 文化編程》一文,即可使用 orez 從這份文檔中抽取 DOT 代碼。
假設上述 DOT 代碼皆位於 foo.dot 文件(文本文件),繪圖命令以下:code
$ orez -t -e "第一次嘗試" foo.md -o first.dot $ dot -Tpng first.dot -o first.png
這樣,dot 即可以畫出圖,但結果非我所期,年代與人物都位於同一行,並且圖片扁且長,肉眼難辨:blog
這是由於,個人描述中所用的「第 x 代」,這裏面的時間只對我有意義, dot 並無這種時間概念。我只好明確地告訴它:
@ 時間 # 第一代 -> 第二代 -> 第三代 -> 第四代 -> 第五代 -> 第六代 -> 第七代 @ @ dot,我告訴你時間!# digraph have_time { # 時間 @ # 七代人 @ } @
結果依然非我所期,dot 的確能把年代按照時間的順序畫出,可是人物依然還都在同一行。見下圖
這是由於,儘管在描述七代人之時,我已刻意將同一代人放入大括號內,但 dot 依然不清楚各我的物屬於哪代。例如
{第一代 何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉}
這種形式,在 DOT 語言裏,稱爲子圖。它的完整形式能夠寫爲:
subgraph 子圖_1 {第一代 何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉}
subgraph
用於聲明一個子圖,而 子圖_1
是子圖的名字——可根據須要,自行取名。在 dot 看來,大括號內的 第一代
、何塞·阿爾卡蒂奧·布恩迪亞
以及 烏爾蘇拉
,它們並無區別,都是圖的結點,相似於漁網上的結點。
要讓 dot 理解各我的物屬於哪代,須要指定子圖內的結點的階級(rank)屬性。dot 可以理解四種階級屬性,same,min,source,max 或 sink。same 表示子圖內的結點屬於同一階級。min 表示子圖內的結點與子圖外的最底層結點屬於同一階級。max 表示表示子圖內的結點與子圖外的最高層結點屬於同一階級。source 與 sink 比 min 與 max 更嚴酷,分別讓子圖內的結點位於最底層或最高層。
如今只須要在各代人物所構成的子圖內使用 same 屬性,即:
{rank=same 第一代 何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉} {rank=same 第二代 何塞·阿爾卡蒂奧(水手) 庇拉爾·特爾內拉(占卜者) 奧雷里亞諾·布恩迪亞(上校) 蕾梅黛絲·摩斯科特 阿瑪蘭妲(灼手者) 十七位不知名女性} {rank=same 第三代 阿爾卡蒂奧(小獨裁者) 桑塔索菲亞·德拉·彼達 奧雷里亞諾·何塞(姑媽控) 奧雷里亞諾·特里斯特、奧雷里亞·諾斯特諾以及其餘十五人} …… …… …… {rank=same 第七代 豬尾巴}
因爲每一個子圖都是 rank=same
,那麼就能夠將這個屬性提到全部子圖的外部,因而有
@ 第二次嘗試 # digraph test_2nd { # 時間 @ rank=same # 七代人 @ } @
共同屬性提出來,放在各個子圖的上部,那麼它描述的就是這些子圖的母圖屬性。母圖的屬性會被屬性設定語句以後的子圖繼承。
dot 如今真正明白了個人意圖,便畫出下面的圖:
接下來,要理清這七代人之間的關係:
@ 七代人的關係 # {何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉} -> {何塞·阿爾卡蒂奧(水手) 奧雷里亞諾·布恩迪亞(上校) 阿瑪蘭妲(灼手者)} {何塞·阿爾卡蒂奧(水手) 庇拉爾·特爾內拉(占卜者)} -> 阿爾卡蒂奧(小獨裁者) {奧雷里亞諾·布恩迪亞(上校) 庇拉爾·特爾內拉(占卜者)} -> 奧雷里亞諾·何塞(姑媽控) {奧雷里亞諾·布恩迪亞(上校) 十七位不知名女性} -> 奧雷里亞諾·特里斯特、奧雷里亞·諾斯特諾以及其餘十五人 {阿爾卡蒂奧(小獨裁者) 桑塔索菲亞·德拉·彼達} -> {蕾梅黛絲(美人兒) 何塞·阿爾卡蒂奧第二(鬥雞者) 佩特拉·科斯特 奧雷里亞諾第二(養牛者)} {奧雷里亞諾第二(養牛者) 費爾南達·德爾·卡皮奧(女王樣)} -> {何塞·阿爾卡蒂奧(掘金者) 雷納塔·蕾梅黛絲(梅梅) 阿瑪蘭妲·烏爾蘇拉(牝貓)} {雷納塔·蕾梅黛絲(梅梅) 馬烏里肖·巴比倫} -> 奧雷里亞諾·巴比倫(姨媽控) @
家族的譜系,記錄的不過是誰和誰生了誰。將上述 DOT 代碼中的 ->
理解爲「生」便可。例如:
{何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉} -> {何塞·阿爾卡蒂奧(水手) 奧雷里亞諾·布恩迪亞(上校) 阿瑪蘭妲(灼手者)}
表示何塞·阿爾卡蒂奧·布恩迪亞與烏爾蘇拉,生育了三個孩子,何塞·阿爾卡蒂奧、奧雷里亞諾·布恩迪亞、阿瑪蘭妲。屬於同一代人的合法夫婦能夠用兩個子圖來構成結點之間的走向,本質上這是一種乘法運算。
這個家族的第五代與第六代,有一對男女發生了亂倫,這時就無法用子圖的形式來表示這對男女生了豬尾巴,不然會致使 dot 誤覺得第五代與第六代是同一代人……程序就是這樣傻,因此不要隨便亂倫。
奧雷里亞諾·巴比倫與阿瑪蘭妲·烏爾蘇拉生了豬尾巴,這件事,只能像下面這樣記錄:
@ 亂倫 # 奧雷里亞諾·巴比倫(姨媽控) -> 豬尾巴 阿瑪蘭妲·烏爾蘇拉(牝貓) -> 豬尾巴 @
因而有
@ 第三次嘗試 # digraph test_3rd { # 時間 @ rank=same # 七代人 @ # 七代人的關係 @ # 亂倫 @ } @
dot 的輸出結果爲:
像這樣的圖,就叫有向圖。
dot 畫出來的這種有向圖,其特色具備層次性,因此特別適合繪製像家譜這種形式的圖。
如今,這個家譜圖是畫了出來,可是它基本上仍是寫意的,並不美觀。要美化一下,也是能夠的,但這須要勞心而傷神。對於這個家譜圖,從「美學」的角度,我願意作的美化是去掉「第 x 代」的橢圓框,而且將家族中的女性的名字標爲藍色。女人是水作的,並且仍是海水。
首先去掉「第 x 代」的外框:
@ 第四次嘗試 # digraph test_4th { rank=same node[shape=plaintext] # 時間 @ node[shape=ellipse] # 七代人 @ # 七代人的關係 @ # 亂倫 @ } @
這裏所做的變更是,在時間段落以前增長 node[shape=plaintext]
語句。shape=plaintext
的意思是,結點的形狀是無外框的普通文本。
因爲 node[shape=plaintext]
設置的是圖中全部結點的形狀,而我想保留每一個人的名字的橢圓外框,所以不得不在 # 七代人 @
代碼段落以前將結點外形從新設爲 ellipse
。
如今,dot 能夠畫出:
接下來,修改全部女性結點的名字顏色,沒有什麼好方法(或許是有,但我不知道),只能逐一設定:
@ 女性名字爲藍色的七代人 # {第一代 何塞·阿爾卡蒂奧·布恩迪亞 烏爾蘇拉[color=blue, fontcolor=blue]} {第二代 何塞·阿爾卡蒂奧(水手) 庇拉爾·特爾內拉(占卜者)[color=blue, fontcolor=blue] 奧雷里亞諾·布恩迪亞(上校) 阿瑪蘭妲(灼手者)[color=blue, fontcolor=blue] 十七位不知名女性[color=blue, fontcolor=blue]} {第三代 阿爾卡蒂奧(小獨裁者) 桑塔索菲亞·德拉·彼達[color=blue,fontcolor=blue] 奧雷里亞諾·何塞(姑媽控) 奧雷里亞諾·特里斯特、奧雷里亞·諾斯特諾以及其餘十五人} {第四代 蕾梅黛絲(美人兒)[color=blue,fontcolor=blue] 何塞·阿爾卡蒂奧第二(鬥雞者) 奧雷里亞諾第二(養牛者) 費爾南達·德爾·卡皮奧(女王樣)[color=blue,fontcolor=blue]} {第五代 何塞·阿爾卡蒂奧(掘金者) 雷納塔·蕾梅黛絲(梅梅)[color=blue,fontcolor=blue] 馬烏里肖·巴比倫 阿瑪蘭妲·烏爾蘇拉(牝貓)[color=blue,fontcolor=blue]} {第六代 奧雷里亞諾·巴比倫(姨媽控)} {第七代 豬尾巴} @
將重寫的七代人名單再加入 DOT 的圖結構中:
@ 第五次嘗試 # digraph test_5th { rank=same node[shape=plaintext] # 時間 @ node[shape=ellipse] # 女性名字爲藍色的七代人 @ # 七代人的關係 @ # 亂倫 @ } @
如今,dot 即可以畫出:
圖中的各條邊,也能夠增長一些文字標註。譬如,第五代與第六代的亂倫生子,能夠強調一下:
@ 帶標註的亂倫 # 奧雷里亞諾·巴比倫(姨媽控) -> 豬尾巴 [color=red,label="亂倫"] 阿瑪蘭妲·烏爾蘇拉(牝貓) -> 豬尾巴 [color=red,label="亂倫"] @
因而 dot 可根據如下代碼
@ 第六次嘗試 # digraph test_6th { rank=same node[shape=plaintext] # 時間 @ node[shape=ellipse] # 女性名字爲藍色的七代人 @ # 七代人的關係 @ # 帶標註的亂倫 @ } @
畫出
關於 DOT 的更多知識,天然是要去看它的文檔:https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf