『dot 学习笔记』二. 记录 Shell 的变迁
上一篇「基本概念」中,我阐述了我所理解的 dot 绘图的基本知识,现在到了绝知此事要躬行的时刻。从本篇开始,我希望能够通过一些应用实例来历练一下 dot 绘图技能,许多关于 dot 的绘图细节知识与技巧也在这些实例的分析中进行探讨。
本篇所采用的实例来源于 graphviz 软件包附带的一个示例。我所做的就是对之详加分析,并且按照我的审美标准进行了一点修改。
Go ahead!
要做什么
本文全部的工作就是要绘制下面这幅图形,它直观地描述了 Unix-like 系统 shell 的变迁。
设置全局属性
图形中的结点、边可能会有一些相同的属性设置,我们可以在绘图开始时进行全局性的设定。在 Shell 变迁图中,我要求所有的结点使用的字体大小为 11pt,所有的结点的高度设为 dot 预定义的最小值 0.02 英寸,所有的边的箭头尺寸为默认尺寸的 0.5 倍,具体代码如下:
digraph shells {
node [fontsize = 11, height = .02]
edge [arrowsize= .5]
}
或许你会担心结点的高度设为 0.02 英寸有些太小了。事实上,只要不开启结点的 'fixedsize = true
'
属性,dot 会根据结点的 label 文本长度适应性确定结点的高度。对结点的宽度属性,同理。
绘制时间线
Shell 变迁图的左侧是从上之下的时间轴线,是由 plaintext 结点和有向边构成的,具体绘制代码如下:
digraph shells {
node [fontsize = 11, height = .02]
edge [arrowsize= .5]
node [shape = plaintext]
1972 -> 1976 -> 1978 -> 1980 -> 1982 -> 1984
1984 -> 1986 -> 1988 -> 1990 -> future;
}
现在可以使用 dot 编译一下这份文件,你会看到时间线。
年表
1972 年,Thompson 写了第一个 Unix Shell。对于这一史实,我们可以使用 dot 语言将其表示为:
{rank = same; 1972; Thompson}
上述代码是将 1972 与 Thomepson 两个结点放在一个匿名的子图里。如果你不嫌麻烦,可以将其写为带有名字的子图,譬如:
subgraph subgraph_name {rank = same; 1972; Thompson}
之所以采用子图,是因为我们希望 1972 与 Thompson 出现在同一层次。因为我们还要继续描述其它年间出现的各个 Shell,它们需要分别与时间轴上对应的时间结点出现在同一层次上,这就是图的 'rank = same
' 属性要解决的问题。关于 rank 属性的详细解释,请参考『Drawing graphs with dot』文档的表 3。
1976 年,在 Thompson 的 Shell 基础上,发展出了 Mashey 和 Bourne。现在先不关心这些 Shell 之间的继承与派生关系,单单去描述 1976 出现了 Mashey 和 Bourne。我们可以像描述 Thompson Shell 那样来做,即:
{rank = same; 1976; Mashey; Bourne}
同理,我们可以很容易地依次写出用于描述其它各年间出现的 Shell 的 dot 代码。
由于在绘制时间轴的步骤中,我将结点的 shape 属性设为了 plaintext,现在,我们需要将它设回 box。所以,用于绘制 Shell 年表的代码可以表示为:
node [shape = box] {rank=same; 1972; Thompson} {rank=same; 1976; Mashey; Bourne} ... ... ...
这里,要注意的是,我在各个子图之前所设定的结点 shape 属性为 box 并不会影响到像 1972、1976 这些时间结点,它们还是会保持着 plaintext 的形状属性。不要问我为什么,因为 dot 就是这么做的。把这个知识点当作知识记住吧。
上述代码可以进一步简化一下。因为图的属性设置是可以被子图继承的,因此,可以将各子图中的 'rank=same
' 属性提到上一层。另外,那些结点之间的 ';
' 符号也不是必须的。
基于上述讨论,我们现在可以完整的写出时间轴 + Shell 年表图形的绘制代码,如下:
digraph shells {
node [fontsize = 11, height = .02]
edge [arrowsize= .5]
node [shape = plaintext]
1972 -> 1976 -> 1978 -> 1980 -> 1982 -> 1984
1984 -> 1986 -> 1988 -> 1990 -> future
node [shape = box]
rank = same
{1972 Thompson}
{1976 Mashey Bourne}
{1978 Formshell csh}
{1980 esh vsh}
{1982 ksh "System-V"}
{1984 v9sh tcsh}
{1986 "ksh-i"}
{1988 KornShell Perl rc}
{1990 tcl Bash}
{"future" POSIX "ksh-POSIX"}
}
生成的图形如下:
继承与派生关系
各个 Shell 之间的继承与派生关系可以利用有向边的走向来示意,这是 dot 的拿手好戏。基于前面的工作,再加上自己对于 Shell 历史知识的把握,用 dot 来描述这些 Shell 之间的继承与派生关系,事实上只是个体力活,具体代码如下:
digraph shells {
node [fontsize = 11, height = .02]
edge [arrowsize= .5]
node [shape = plaintext]
1972 -> 1976 -> 1978 -> 1980 -> 1982 -> 1984
1984 -> 1986 -> 1988 -> 1990 -> future
node [shape = box]
rank = same
{1972 Thompson}
{1976 Mashey Bourne}
{1978 Formshell csh}
{1980 esh vsh}
{1982 ksh "System-V"}
{1984 v9sh tcsh}
{1986 "ksh-i"}
{1988 KornShell Perl rc}
{1990 tcl Bash}
{"future" POSIX "ksh-POSIX"}
Thompson -> Mashey
Thompson -> Bourne
Thompson -> csh
csh -> tcsh
Bourne -> ksh
Bourne -> esh
Bourne -> vsh
Bourne -> "System-V"
Bourne -> v9sh
v9sh -> rc
Bourne -> Bash
"ksh-i" -> Bash
KornShell -> Bash
esh -> ksh
vsh -> ksh
Formshell -> ksh
csh -> ksh
KornShell -> POSIX
"System-V" -> POSIX
ksh -> "ksh-i"
"ksh-i" -> KornShell
KornShell -> "ksh-POSIX"
Bourne -> Formshell
}
现在,我们已经完成了一幅 Shell 变迁图,可以见识一下它的真实面目了。
相信你会有些困惑。因为它看起来并不像我在这篇文章的开始所给出的那幅图形。事实上,dot 就是这样,它并不保证所产生的图形有多美观,它的工作只是正确表示出你想表达的那种思想。
欺骗
如果你真的希望得到本文开始处的那幅图,就不得不使用一些障眼法,只需将以下代码放在上一节代码的尾部即可。
edge [style=invis]
1984 -> v9sh -> tcsh
1988 -> rc -> KornShell
Formshell -> csh
KornShell -> Perl
这个障眼法,就是将边设置为不可见,偷偷地通过连接几个结点来调整某些结点的位置。
我不推荐在这种障眼法上浪费太多时间。除非你真的无法忍受丑陋的真实。
2008年11月10日 08:55
真不错。这么画图才有效率。
2009年1月15日 23:40
不错啊,最经一个项目要生成关系图,要程序生成DOT,正好学习。希望楼主继续介绍啊