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

相关文章
相关标签/搜索