DOT

有個軟件,名叫 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

相關文章
相關標籤/搜索