Basic Graph Drawing
dot 绘制定向图形。它读取属性化的图文本文件并描绘成图像, 作为图形文件或图形格式文件,如 GIF、PNG、SVG、PDF 或 PostScript。
dot 通过四种主要步骤画图。了解这一点有助于您了解 dot 布局类型以及如何控制它们。dot 使用的布局过程依赖于图形是循环的(acyclic)。因此,第一步是通过反转某些循环边的内部方向来打破输入图中出现的任何循环。下一步将节点(nodes)分配给离散等级(ranks)或级别(levels)。在从上到下的图形中,排名(rank)确定 Y 坐标。跨越多个排名(rank)的边被分解为"虚拟"节点(virtual nodes)和单位长度边的链。第三步在层级种安排节点以避免交叉。第四步将节点的 X 坐标设置以保持边短,最后一步路由边样线(edge splines)。这是相同的一般方法,大多数分层图形绘制程序,基于 Warfield,Carpano 和 Sugiyama 的工作。我们将读者推荐给 GKNV93,以便对 dot 算法有深入的了解。
dot 接受 DOT 语言的输入(参见附录 D)。此语言描述了三种主要对象类型:图形(graphs)、节点(nodes)和边(edges)。主(最外层)图形可以是有向图或无方向图形。由于 dot 使定向图形的布局,因此以下所有示例都使用图。(单独的布局实用程序 neato 绘制非定向图形 [Nor92]。在主图形中,子图定义节点和边的子集。
图 1 是 DOT 语言中的示例图。第 1 行提供图形名称和类型。后续的行创建节点、边或子图,并设置属性。所有这些对象的名称可以是 C 标识符、数字或引用的 C 字符串。引号保护标点符号和空白。
当节点的名称首次出现在文件中时,将创建节点。当边运算符 -> 加入节点时,将创建边。在此示例中,第 2 行从 main 到 parse,从 parse 到 execute。用命令 dot 执行这个文件(将其称为 graph1.gv)产生图 2 的图形。命令行选项 -Tps 选择 PostScript(EPSF)输出。graph1.ps可以打印、由 PostScript 查看器显示或嵌入到其他文档中。
1 | dot -Tps graph1.gv -o graph1.ps |
调整布局中节点和边的表示或位置通常很有用。这是通过在输入文件。属性是字符串的名称值对。图 3 和图 4 说明了一些布局属性。在图 3 的列表中,第 2 行将图形的大小设置为 4,4(以英寸为单位)。此属性控制绘图的大小;但是,此属性控制图形的大小。如果绘图太大,则根据需要均匀缩放以适合。
节点或边属性以方括号设置。在第 3 行中,节点主分配形状框。第 4 行的边缘通过增加其权重(默认值为 1)来拉直。第 6 行的边缘以虚线绘制。第 8 行从执行边制作字符串和 printf。在第 10 行中,默认边缘颜色设置为红色。这会影响在文件中的此点之后创建的任何边。第 11 行使粗体边缘标记为 100 次。在第 12 行中,节点make_string
多行标签。第 13 行将默认节点更改为一个填充蓝色阴影的框。节点比较继承这些值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | digraph G { size = "4,4"; main [shape=box]; main -> parse [weight=8]; parse -> execute; main -> init [style=dotted]; main -> cleanup; execute -> {make_string; printf}; init -> make_string; edge [color=red]; main -> prinf [style=bold, label= "100 times"]; make_string [label = "make a \nstring"]; node [shape=box,style=filled,color=".7 .3 1.0"]; execute -> compare; } |
2 Drawing Attributes
附录A,B 和 C 中总结了影响图形会值的主要属性。关于更多的属性,和属性更完整的描述,你可以参考Graphviz 网站
1 | www.graphviz.org/doc/info/attrs.html |
2.1 Node Shapes
默认情况下,节点绘制的 shape=ellipse, width=.75, height=.5, 并由节点名称标记。其他常见形状包括框(box)、圆圈(circle)、记录(record)和纯文本(plaintext)。在附录 H 中给出了主节点形状的列表。 节点形状纯文本特别令人感兴趣,因为它绘制的节点没有任何轮廓,这是某些图表中的重要约定。在图形结构是主要关注的情况下,特别是当图形是中等的 点形状较大,可减小节点以显示最小内容。绘制时,节点的实际大小是请求的大小及其文本标签所需的区域的较大,除非为 fixedsize=true,在这种情况下,将强制执行宽度和高度值。
节点形状分为两大类:基于面(polygon-based)和基于记录(record-based)。除 record 和 Mrecord 之外的所有节点形状都被视为多边形,并且按边数(椭圆和圆形为特殊情况)进行建模,以及其他几个几何属性。其中一些属性可以在图形中指定。如果为 regular=true,则节点将强制为常规节点。参数 peripheries 设置绘制的边界曲线数。例如,双圆具有 peripheries=2。方向属性指定以度为单位的多边形顺时针旋转。
形状多边形(polygon)公开所有多边形参数,可用于创建许多未预定义的形状。除了上面提到的参数 常规(regular)、外围(peripheries)和方向(orientation)外,多边形还按边数(sides)、偏斜(skew)和失真(distortion)进行参数化。偏斜是浮动的点数(通常在 +1.0 和 1.0 之间),通过从上到下倾斜形状来扭曲形状,正值将多边形的顶部向右移动。因此,倾斜可用于将框转换为平行四边形。失真将多边形从上到下缩小,负值导致底部大于顶部。变形将盒子变成梯形。图 6 和图 5 显示了各种多边形属性。
基于记录的节点构成其他类的节点形状。其中包括形状记录和 Mrecord。两者是相同的,除了后者有圆角。这些节点表示递归字段列表,这些字段绘制为交替的水平和垂直行的框。递归结构为由节点的标签确定,该标签具有以下架构:
1 2 3 | rlabel -> field('|' field)* field -> boxLabel| "rlabel" boxLabel -> ['<' string '>'][string] |
必须转义文字大括号、垂直条和角括号。空格被解释为令牌之间的分隔符,因此,如果要在文本中字面显示空格,则必须转义它们。boxLabel 中的第一个字符串为字段指定名称,并用作框的端口名称(参见第 3.1 节)。第二个字符串用作字段的标签;它可能包含与多行标签相同的转义序列(参见第2.2节)。图 7 和图 8 的示例说明了记录的使用和某些属性。
2.2 Labels
如上所述,默认节点标签是其名称。默认情况下,边缘未标记。可以使用标签属性显式设置节点和边标签,如图 4 所示。
尽管按名称标记节点可能很方便,但在有些时候必须显式设置标签(labels)。例如,在绘制文件目录树时,一个目录可能有多个名为 src 的目录,但每个目录都必须具有唯一的节点标识符。
1 2 3 4 5 6 7 8 | digraph G { a -> b -> c; b -> d; a [shape=polygon, sides=5, peripheries=3, color=lightblue, style=filled]; c [shape=polygon, sides=4, skew=.4, label="hello world"] d [shape=invtriangle]; e [shape=polygon, sides=4, distrotion=.7]; } |
1 2 3 4 5 6 7 8 | digraph structs {acyclic node [shape=record]; struct1 [shape=record, label="<f0> left|<f1> mid\ndle|<f2> right"]; struct2 [shape=record, label="<f0> one|<f1> two"]; struct3 [shape=record, label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"]; struct1 -> struct2; struct1 -> struct3; } |
inode 编号或完整路径名称是合适的唯一标识符。然后每个节点(node)的标签(label)可以设置为其目录中的文件名。
可以使用转义序列 \n, \l, \r 来终止居中或左或右对齐的行,从而创建多行标签。
图形和聚类子图也可能具有标签。默认情况下,图形标注位于图形下方。设置标签 loc=t 将标签居于图形上方。群集标签显示在右上角的封闭矩形内角落。值 labelloc=b 将标签移动到矩形的底部。设置标签 just=r 将标签向右移动。
默认字体为 14-point Times-Roman, black。可以使用属性字体名称(fontname)、字体大小(fontsize )和字体颜色(fontcolor)选择其他字体系列(font families)、大小(sizes)和颜色(colors)。字体名称应与目标解释器兼容。最好只使用标准字体系列 Times、Helvetica、Courier 或 Symbol,因为这些字体保证与任何目标图形语言配合使用。例如,Times-Italic、Times-Italic 和 Times-Bold 是便携式的;AvanteGardeDemiOblique 不是。
对于位图(bitmap)输出(如 GIF 或 JPG),点依赖于在布局期间提供这些字体。Graphviz 的大多数预编译安装都使用字体配置用于将字体名称与可用字体文件匹配的库。字体配置附带
用于显示匹配和安装字体的实用程序集。请参阅字体配置文档或外部 Graphviz FontFAQ 或更多详细信息。如果 Graphviz 是在没有字体配置的情况下构建的(这通常意味着您从 源代码,fontpath 属性可以指定应搜索字体文件的目录列表3。如果未设置,点将使用 DOTFONTPATH 环境变量,或者,如果未设置,GDFONTPATH 环境变量。如果未设置这些,dot 将使用内置列表。
Edge labels 位于边缘(edge)中心附近。通常,要注意防止边缘标签重叠的边缘(edges)和节点(nodes)。在复杂的图形中,要确定标签属于哪个边仍然很困难。如果装饰(decorate)属性设置为 true,则绘制一条线,将标签(label)连接到其边缘(edge)。有时避免边缘标签和边之间的冲突会强制绘图大于预期。如果 labelfloat=true,点不会尝试防止此类重叠,从而允许更紧凑的绘图。
边缘(edge)还可以使用头标签(headlabel)和尾号(taillabel)指定其他标签,这些标签放置在边缘末端附近。这些标签的特征使用属性标签字体名称(labelfontname)、标签字体大小(;abe;fontsize)和标签字体颜色(labelfontcolor)指定。这些标签放置在边缘(edge)和节点(node)的交点附近,因此可能会干扰它们。要调整图形,用户可以设置标注角度(labelangle)和标注距离(labeldistance)属性。前者设置角度(以度)旋转,标签从边缘(edge)与节点(node)发生事件的角度旋转。后者设置一个多倍缩放因子来调整标签(label)与节点(node)的距离。
####2.3 HTML-like Labels
为了允许在更精细的粒度下更丰富的属性集合,dot 使用 HTML 语法接受类似于 HTML 的标签。这些字符串使用由 <…> 分隔的字符串而不是双引号来指定。在这些分隔符中,字符串必须遵循 HTML 的词法、引用和句法约定。
通过使用
元素,这些标签可以被视为 shape=record 的扩展和替换。有了这些,人们可以在框级别更改颜色和字体,并包括图像。
元素的 PORT 属性为单元格提供端口名称(参见第 3.1 节)。 |
尽管类似 HTML 的标签只是一种特殊类型的标签属性,但人们经常使用它们,就好像它们是类似于 records 的新类型的节点形状一样。因此,当使用这些值时,人们通常会看到 shape=none 和 margin=0。也请注意,作为标签(label),这些标签(label)可用于边(edges)和图形(graphs)以及节点(nodes)。
图 9 和图 10 提供了使用类似 HTML 标签的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | digraph html { abc [shape=none, margin=0, label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4"> <TR> <TD ROWSPAN="3"><FONT COLOR="red">hello</FONT><BR/>world</TD> <TD ROWSPAN="3" BGCOLOR="lightgrey">g</TD> <TD ROWSPAN="3">h</TD> </TR> <TR> <TD>c</TD> <TD PORT="here">d</TD> <TD>e</TD> </TR> <TR> <TD COLSPAN="3">f</TD> </TR> </TABLE>>]; } |
2.4 Graphics Styles
节点和边可以指定颜色属性,默认值为黑色。这是用于绘制节点形状或边缘的颜色。颜色值可以是色相饱和-亮度三重(huesaturation-brightness triple)(在 0 和 1 之间三个浮点数字,用逗号分隔);附录 J 中列出的颜色名称之一(借用 X 窗口系统的某些版本);或红绿蓝 (RGB) 三重4
(在 00 和 FF 之间的三个十六进制数字,前面是字符"*")。因此,值"orchid",“0.8396,0.4862,0.8549"和”#DA70D6"是指定相同颜色的三种方法。对于自动生成颜色的脚本或工具,数字形式很方便。颜色名称查找不区分大小写,并且忽略非字母数字字符,因此 warmgrey 和 Warm=Grey 等效。
我们可以提供有关图形图形中颜色使用的一些提示。首先,避免使用太多明亮的颜色。"彩虹效应"令人困惑。最好选择较窄的颜色范围,或随色调而变化饱和度。其次,当节点充满深色或非常饱和的颜色时,标签似乎更可读 fontcolor=white 和 fontname=Helvetica。(我们还具有 PostScript functions 用于从普通字体(plain fonts)创建轮廓字体(outline fonts)。第三,在某些输出格式中,您可以定义自己的颜色空间。例如,如果使用 PostScript 进行输出,则可以重新定义节点颜色(nodecolor)、边缘颜色(edgecolor)、或图中图色(graphcolor)。因此,要使用 RGB 颜色, 将以下一行放在 lib.ps 文件中。
1 | /nodecolor {setrgbcolor} bind def |
使用 -l 命令行选项加载这个文件。
1 | dot -Tps -l lib.ps file.gv -o file.ps |
style 属性控制节点和边的杂项图形特征。此属性是具有可选参数列表的基元的逗号分隔列表。预定义的基元包括实心(solid)、虚线(dashed)、虚线(dotted)、粗体(bold)和 invis。前四条控制线绘制在节点边界和边上,具有明显的意义。值 invis 会导致节点或边未绘制。节点(node)的样式(style)还可以包括填充(filled)、对角线(diagonals)和圆角(rounded)。使用颜色fillcolor 填充节点内的阴影。如果未设置此选项,则使用 color 的值。如果这也是未设置,则使用浅灰色(light grey) 作为默认值。对角线样式(diagonals style)会导致短对角线在顶点附近的边对之间绘制。圆形样式(rounded style)对多边形角进行圆角。
用户定义的样式基元(primitives)可以作为自定义 PostScript 过程(procedures)实现。此类基元在图形(graph)、节点(node)、
或边(edge),在绘制其任何标记之前。参数列表已转换为 PostScript notation 表示法。例如,具有 style="setlinewidth(8)"的节点绘制时采用粗轮廓绘制。此处,setlinewidth 是内置的 PostScript,但用户定义的 PostScript 过程称为相同的方式。这些过程的定义可以在使用 -l 加载的库文件中给出,如上所示。
Edges 具有用于设置箭头的 dir 属性。dir 可以是 forward(默认值)、back(后退)、both(两者)或 none(无)。这仅指绘制箭头的位置,并且不会更改基础图形。例如,设置 dir=back 会导致箭头在尾部绘制,头部无箭头,但不会交换边缘的端点。属性 arrowhead(箭头)和 arrowtail(箭尾)(如果有) 的样式, 箭头用于边的头部和尾端。允许的值是 normal, inv, dot, invdot, odot, invodot 和 none(参见附录 I)。属性箭头大小(arrowsize)指定影响边缘上绘制的任何箭头大小的多倍因子。例如, arrowsize=2.0 使箭头长两倍,宽两倍。
在样式(style)和颜色(color)方面,群集(clusters)的作用有点像大型框形节点,因为群集边界是使用群集的颜色属性绘制的,在
一般,群集的外观会影响样式 style, color 和 fillcolor 属性。
如果 root graph 有指定 bgcolor 属性,则此颜色用作整个绘图的背景,并用作默认填充颜色。
2.5 Drawing Orientation,Size and Spacing
在确定 dot 绘图大小时起重要作用的两个属性是节点(nodesep)和列塞(ranksep)。第一个指定同一列(same rank)的两个相邻节点(two adjacent nodes)之间的最小距离(以英寸为单位)。第二个处理排列的间隔距离,即一个排名中节点底部和下一个排名中节点顶部之间的最小垂直空间。ranksep 属性设置排名分隔(以英寸为单位)。或者,一个可以有 ranksep=equally。这保证了所有等级的间距相等,从相邻列上的节点中心测量。在这种情况下,两个等级之间的排名分隔至少是默认的排名分隔。由于两种用途是独立的,因此可以同时设置两种用途。例如,ranksep="1.0 equally"会导致排名间距相等,最小等级分隔为 1 inch。
通常,使用默认节点大小和分离绘制的图形对于目标打印机或文档中图形允许的空间来说太大。有几种方法可以尝试解决这个问题。首先,我们将回顾 dot 如何计算最终布局大小。
布局最初使用默认设置在内部以"natural"大小进行(除非设置了 ratio=compress(ratio=compress),如下所述)。绘图的大小或纵横比没有绑定,因此,如果图形很大,则布局也很大。如果未指定大小或比率,则打印自然大小布局。
控制图形输出大小的最简单方法是在图形文件中(或使用 -G 在命令行上)size=“x,y”。这将确定最终布局。例如,无论初始布局有多大,size=“7.5,10” 都适合 8.5x11 页(假设默认页面方向)。
ratio 同样影响布局大小,如果绘图已适合给定大小,则不会发生任何操作。否则,绘图的减小程度足够均匀,以使关键尺寸适合。
Case 1 没有设置ratio, 如果绘图满足给定的 size 大小,不会发生什么,否则,绘图的减小程度足以使关键尺寸适合。
如果设置了 ratio, 有四个子案例。
Case 2a 如果 ratio=x, x 是一个浮点数,然后,在一个维度中放大绘图,以达到以绘图 height|width 表示的请求比率。
Case 2b 如果 ratio=fill, 并且设置了size=x,y, 然后,在一个维度中放大绘图,以达到 y/x 的比例。然后,缩放执行,如案例 1。效果是,按大小给出的所有边界框都已填充。
Case 2c 如果设置了 ratio=compress 和 size=x,y , 然后,初始布局被压缩,尝试以将其放入给定的边界框中。
Case 2d 如果 ratio=auto 和 page 属性豆设置了,并且图不能在一个页面里面画下,那么size属性将被忽略,并且 dot 会计算一个适合的尺寸,特别是,给定维度中的大小将是该维度中页面大小的最小整数倍数,该倍数至少是当前大小的一半。然后,两个维度将独立缩放到新大小。
如果设置了 rotate=90, 或者 orientation=landscape, 然后,绘图旋转 90 ° 进入横向模式。布局的 X 轴将沿每个页的 Y 轴。这不会影响 dot 对大小(size)、比率(ratio)或 页面(page)。
此时,如果未设置页面,则最终布局将生成为一个页面。
如果设置了 page=x,y, 然后,布局被打印为页面序列,可以平铺或组装成马赛克。常见设置是 page=“8.5,11” 或 page=“11,17”。这些值是指物理设备的完整大小;使用的实际面积将由边距设置减小。(对于打印机输出,默认值为 0.5 英寸;对于位图输出(bitmap-output),X 和 Y 边距分别为 10 和 2 点。对于平铺布局,设置较小的边距可能会有所帮助。这可以 使用边距属性完成。这可以采取单个数字,用于设置边距,或两个数字用逗号分隔,以分别设置 x 和 y 边距。像往常一样,单位是英寸。尽管可以设置 margin=0,但不幸的是,许多位图打印机(bitmap printers)的内部硬件裕量无法覆盖。
页面的打印顺序可以由 pagedir 属性控制。输出始终使用基于行或基于列的顺序完成,并且 pagedir 设置为两个字母的代码,指定主要方向和次要方向。例如,默认值为 BL,指定从下到上 (B) 主要顺序和从左到右 (L) 次要顺序。因此,首先从左到右发出页面的下排,然后向上发出第二行,从左到右,然后从左到右用上一行结束。从上到下的顺序由 T 表示,从右到左的顺序由 R 表示。
如果 center=true 和图形可以在一页上输出,如果使用默认页面大小 8.5 x 11 英寸(如果未设置页面),则图形将重新定位为
居于该页面。
一个常见的问题是,以小尺寸绘制的大图形会产生不可读的节点标签。要制作更大的标签,必须给予一些东西。限制可放入一页上的可读文本量。通常,在运行点之前,可以通过提取原始图形的有趣部分来绘制较小的图形。我们有一些工具,以帮助这一点。
sccmap 将图形分解为强连接的组件
tred 计算传递减少(去除传递性隐含的边缘)
gvpr 图形处理器选择节点或边,并收缩或删除图形的其余部分
unflatten 通过惊人的叶边长度改善树木的纵横比
考虑到这一点,下面是在给定图形上尝试的一些功能:
- 增加 node 的 fontsize.
- 使用更小的 ranksep 和 nodesep.
- 使用 ratio=auto.
- 使用 ratio=compress 和 给一个合理的 size.
- 无衬线字体(sans serif font)(如 Helvetica)在缩小时可能比 Times 更具可读性。
2.6 Node and Edge Placement
dot中的属性提供了许多方法来调整节点和边的大规模布局,以及微调绘图以满足用户的需求和口味。本节讨论这些属性。
有时,从左到右而不是从上到下使边指向是很自然的。如果顶层图形中的 rankdir=LR,则以这种方式旋转图形。TB(从上到下)是默认值。模式 rankdir=BT 可用于绘制向上定向图形。对于完整性,也可以具有 rankdir=RL。
在具有时间线的图形中,或在强调源节点和接收器节点的图形中,可能需要约束排名分配。子图(subgraph)的排名(rank)可以设置为相同(same)、最小(min)、源(source)、最大值(max)或接收器(sink)。值相同会导致子图中的所有节点都在同一排(rank)上发生。如果设置为最小(min),则子图中的所有节点都保证在排名上至少与布局中的任何其他节点一样小。这可以通过设置 rank=source 进行严格规定,这强制子图中的节点位于某些级别上,其级别严格小于任何其他节点的等级(min或source 子图也指定的节点除外)。值 max 或 sink 对最大排名具有类似的作用。请注意,这些约束会导致节点的等效类。如果一个子图将节点 A 和 B 强制位于同一级别上,而另一个子图强制节点 C 和 B 共享一个排名,则两个子图中的所有节点都必须在同一个排名上绘制。图 11 和图 12 使用子图进行控制排名分配。
在某些图形中,节点的从左到右排序非常重要。如果子图具有 ordering=out,则子图中具有相同尾部的边缘节点将按创建顺序从左到右扇出。(另请注意,涉及头节点的平面边缘可能会干扰其排序。)
有许多方法可以微调节点和边的布局。例如,如果边节点具有相同的 group 属性,则 dot 会尝试保持边笔直,并避免其他边穿过它。边的重量提供了另一种保持边直的方法。edge 的 weight 表明一定程度上衡量了边缘的重要性;因此,weight 越大,其 nodes 之间的距离越近。dot 使较重的边绘制得更短、更直。
当节点(nodes)被限制为相同的排(rank)时,边权重也会发挥作用。这些节点之间权重非零的边尽可能以相同的方向(从左到右,或旋转的图形中从上到下)沿排名进行。这一事实可能利用来调整节点排序,将不可见边(style=“invis”)在需要的地方。
可以使用 samehead 和 sametail 属性约束与同一节点相邻的边的端点。具体而言,具有相同头和相同头的相同值的所有边都受约束以在同一点。类似属性适用于尾节点 sametail。
在排名分配期间,边的头部节点被限制为比尾节点更高的等级。但是,如果边具有 constraint=false,则不强制执行此要求。
在某些情况下,用户可能希望边缘的端点永远不会太接近。这可以通过设置边的 minlen 属性来获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | digraph asde91 { ranksep=.75; size= "7.5, 7.5"; { node [shape=plaintext, fontsize=16]; /* the time-line graph */ past -> 1978 -> 1980 -> 1982 -> 1983 -> 1985 -> 1986 -> 1987 -> 1988 -> 1989 -> 1990 -> "future"; /* ancestor programs */ "Bourne sh"; "make"; "SCCS"; "yacc"; "cron"; "Reiser cpp"; "Cshell"; "emacs"; "build"; "vi"; "<curses>"; "RCS"; "C*"; } { rank=same; "Software IS"; "Configuration Mgt"; "Architecture & Libraries"; "Process"; }; node [shape=box]; { rank = same; "past"; "SCCS"; "make"; "Bourne sh"; "yacc"; "cron";} { rank = same; 1978; "Reiser cpp"; "Cshell"; } { rank = same; 1980; "build"; "emacs"; "vi";} { rank = same; 1982; "RCS"; "<curses>"; "IMX"; "SYNED";} { rank = same; 1983; "ksh"; "IFS"; "TTU"; } { rank = same; 1985; "nmake"; "Peggy"; } { rank = same; 1986; "C*"; "ncpp"; "ksh-i"; "<curses-i>"; "PG2";} { rank = same; 1987; "Ansi cpp"; "nmake 2.0"; "3D File System"; "fdelta"; "DAG"; "CSAS";} { rank = same; 1988; "CIA"; "SBCS"; "ksh-88"; "PEGASUS/PML"; "PAX"; "backtalk";} { rank = same; 1989; "CIA++"; "APP"; "SHIP"; "DataShare"; "ryacc"; "Mosaic";} { rank = same; 1990; "libft"; "CoShell"; "DIA"; "IFS-i"; "kyacc"; "sfio"; "yeast"; "ML-X"; "DOT";} { rank = same; "future"; "Adv. Software Technology";} "PEGASUS/PML" -> "ML-X"; "SCCS" -> "nmake"; "SCCS" -> "3D File System"; "SCCS" -> "RCS"; "make" -> "nmake"; "make" -> "build"; "Bourne sh" -> "Cshell"; "Bourne sh" -> "ksh"; "Reiser cpp" -> "ncpp"; "Cshell" -> "ksh"; "build" -> "nmake 2.0"; "vi" -> "<curses>"; "vi" -> "ksh"; "emacs" -> "ksh"; "RCS" -> "SBCS"; "RCS" -> "fdelta"; "<curses>" -> "<curse-i>"; "SYNED" -> "Peggy"; "ksh" -> "nmake"; "ksh" -> "ksh-i"; "ksh" -> "ksh-88" "IFS" -> "<curses-i>"; "IFS" -> "sfio"; "IFS" -> "IFS-i"; "nmake" -> "ncpp"; "nmake" -> "3D File System"; "nmake" -> "nmake 2.0"; "Peggy" -> "PEGASUS/PML"; .... } |
这定义了头部和尾部之间的最小差值。例如,如果 minlen=2,则头部和尾部之间始终至少有一个中间排列。请注意,这与两个节点之间的几何距离无关。
应谨慎对待微调。dot 可以制作布局时效果最佳,而不会在放置单个节点(nodes)和边(edges)时受到太大的"帮助"或干扰。布局可以通过增加某些边的weight,或者通过使用 style=invis 创建不可见的边(edges)或节点(nodes),有时甚至通过重新排列文件中的节点(nodes)和边(edges)的顺序来调整布局。但是,这可能适得其反,因为布局不一定与输入图中的更改有关。最后一次调整会使所有以前的更改无效,并使绘图非常糟糕。我们今后想到的项目是将 dot 的数学布局技术与允许用户定义的提示和约束的交互式前端相结合。
3 Advanced Features
3.1 Node Ports
节点端口是边可以附加到节点的点。(当边未附加到端口时,它针对节点的中心,边缘在节点的边界处剪切。
有两种类型的端口。端口基于 8 个指南针点 “n”,“ne”,“e”,“se”,“s”,“sw”,“w"或"nw"可以为任何节点指定。然后,边的末端将针对节点上的该位置。因此,如果指定 se 端口,边缘将连接到其东南"角”(southeast “corner”)处的节点。
此外,具有 record 形状的节点可以使用记录结构来定义端口,而具有表的类似 HTML 的标签可以使用 元素的 PORT 属性使任何单元格成为端口。如果记录框或表单元格定义端口名称,则边可以使用该端口名称来指示该端口名称应针对框的中心。(默认情况下,边缘被剪切到框的边界。)
还有两种指定端口的方法。一种方法是使用边缘的头端口(headport)和尾端口(tailport)属性,例如。
1 | a -> b [tailport = se] |
或者,端口名称(portname)可用于使用语法 node_name:port_name 修改节点名称作为边声明的一部分。因此,处理上述示例的另一种方法是
1 | a -> b:se |
由于记录框有自己的角,因此可以将指南针点端口添加到记录名称端口。因此,边缘将附加到记录节点 b 中的框的东南角,其端口名称为
f0.
1 | a -> b:f0:se |
图 13 说明了在记录节点中声明和使用端口名称, 如图 14 所示的结果图。
图 15 和 16 提供了使用记录节点和端口的另一个例子。这重复了图 7 和图 8 的示例,但现在使用端口作为边的连接器。请注意,如果记录的输入高度设置为小值,则记录有时看起来更好,因此文本标签主导了实际大小,如图 13 所示。否则,将假定默认节点大小 (.75 乘 .5),如图 16 所示。图 17 和图 18 的示例在哈希表的布局中使用从左到右的绘图。
3.2 Clusters
cluster 是放置在布局中自己不同矩形中的子图(subgraph)。当子图的名称具有前缀 cluster 时,该子图将识别为 cluster。(如果顶级图形具有 cluster=none,则关闭此特殊处理)。 标签(Labels)、字体特征(font characteristics)和 labelloc 属性可以设置为顶级图形,但默认情况下,标签(label)显示在图形上方。对于群集(cluster),默认情况下标签是左对齐的;对于群集,标签是左对齐的。如果标签只是"r",标签是正确的。颜色属性指定封闭矩形的颜色。此外,群集可能具有 style=“filled”,在这种情况下,矩形在绘制群集之前用 fillcolor 填充。(如果未指定填充颜色,则使用群集的颜色属性。)
群集由递归技术绘制,该技术计算群集内节点的排名分配和内部排序。图 19 到图 21 是群集布局和相应的图形文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | digraph g { node [shape = record,height=.1]; node0 [label = "<f0> |<f1> G|<f2> "]; node1 [label = "<f0> |<f1> E|<f2> "]; node2 [label = "<f0> |<f1> B|<f2> "]; node3 [label = "<f0> |<f1> F|<f2> "]; node4 [label = "<f0> |<f1> R|<f2> "]; node5 [label = "<f0> |<f1> H|<f2> "]; node6 [label = "<f0> |<f1> Y|<f2> "]; node7 [label = "<f0> |<f1> A|<f2> "]; node8 [label = "<f0> |<f1> C|<f2> "]; "node0":f2 -> "node4":f1; "node0":f0 -> "node1":f1; "node1":f0 -> "node2":f1; "node1":f2 -> "node3":f1; "node2":f2 -> "node8":f1; "node2":f0 -> "node7":f1; "node4":f2 -> "node6":f1; "node4":f0 -> "node5":f1; } |
1 2 3 4 5 6 7 8 | digraph structs { node [shape=record]; struct1 [shape=record,label="<f0> left|<f1> middle|<f2> right"]; struct2 [shape=record,label="<f0> one|<f1> two"]; struct3 [shape=record,label="hello\nworld |{ b |{c|<here> d|e}| f}| g | h"]; struct1:f1 -> struct2:f0; struct1:f2 -> struct3:here; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | digraph G { nodeseq=.05; rankdir=LR; node [shape=record,width=.1,height=.1]; node0 [label = "<f0> |<f1> |<f2> |<f3> |<f4> |<f5> |<f6> | ",height=2.5]; node [width = 1.5]; node1 [label = "{<n> n14 | 718 |<p> }"]; node2 [label = "{<n> a1 | 805 |<p> }"]; node3 [label = "{<n> i9 | 718 |<p> }"]; node4 [label = "{<n> e5 | 989 |<p> }"]; node5 [label = "{<n> t20 | 959 |<p> }"]; node6 [label = "{<n> o15 | 794 |<p> }"]; node7 [label = "{<n> s19 | 659 |<p> }"]; node0:f0 -> node1:n; node0:f1 -> node2:n; node0:f2 -> node3:n; node0:f5 -> node4:n; node0:f6 -> node5:n; node2:p -> node6:n; node4:p -> node7:n; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | digrah G { subgraph cluster0 { node [style=filled,color=white]; style=filled; color=lightgrey; a0 -> a1 -> a2 -> a3; label = "process #1"; } subgraph cluster1 { node [style=filled]; b0 -> b1 -> b2 -> b3; label = "process #2"; color=blue; } start -> a0; start -> b0; a1 -> b3; b2 -> a3; a3 -> a0; a3 -> end; b3 -> end; start [shape=Midamond]; end [shape=Msquare]; } |
如果顶层图形的 compound(复合) 属性设置为 true,则 dot 将允许 edges 连接 nodes 和 clusters。这可以通过定义 lhead 或 ltail 属性的 edge 来实现。这些属性的值必须分别为包含头节点(lhead)或尾节点(ltail)的群集的名称。在这种情况下,edge 在 cluster 边界处被剪切。所有其他边缘属性(如 arrowhead 或 dir)都应用于截断的边缘。例如,图 22 显示了使用 compound 属性和生成的关系图的图形。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | digraph G { compound=true; subgraph cluster0 { a -> b; a -> c; b -> d; c -> d; } subgraph clsuter1 { e -> g; e -> f; } b -> f [lhead=cluster1]; d -> e; c -> g [ltail=cluster0, lhead=cluster1]; c -> e [ltail=cluster0]; d -> h; } |
3.3 Concentrators (集中器)
在顶层图上设置 concentrate=true 可实现边缘合并技术,以减少密集布局中的杂乱。边在并行运行时合并,具有公共终结点,长度大于 1。固定大小的布局的一个有利副作用是,移除这些边通常允许更大、更可读的标签。而浓缩器在点看起来有点像纽贝里的,它们通过搜索布局中的边缘而不是通过检测基础图形中的完整双部分图形来找到。因此,dot 的方法运行得更快,但不像纽贝里的算法那样折叠。
4 Command Line Options
默认情况下,dot在筛选器模式下运行,从 stdin 读取图形,并在附加布局属性的 DOT 格式的 stdout 上书写图形。点支持各种命令行选项:
-Tformat 设置输出的格式。格式的允许值为:
bmp Windows bitmap 格式
canon 漂亮的打印输入;未完成布局。
dot 已归于 DOT。打印带有布局信息作为属性附加到的输入,参见附录 F。
fig FIG 输出
gd DG 格式。这是 GD 图形库(GD Graphics Library)使用的内部格式。备用格式为 gd2。
gif GIF 输出
imap 为服务器端图像映射生成地图文件。这可以与输出的图形形式结合使用,例如,在网页中使用 -Tgif 或 -Tjpg 来附加指向节点和边缘的链接。
cmapx 为客户端图像映射生成 HTML 地图文件。
pdf 通过开罗图书馆的 Adobe PDF。我们在嵌入到其他文档中时就看到了问题。而是使用 -Tps2,如下所述。
plain 简单、基于线的 ASCII 格式。附录 E 描述了此输出。备用格式为纯 ext,它提供边头和尾部节点上的端口名称。
png PNG(便携式网络图形)输出。
ps PostScript(EPSF) 输出。
ps2 带 PDF 注释的后期脚本 (EPSF) 输出。此输出应蒸馏到 PDF 中,例如 pdflatex,然后再包含在文档中。 (使用 ps2pdf;epstopdf 不处理 %%绑定Box:(尾)。
svg SVG 输出。备用窗体 svgz 生成压缩 SVG。
vrml VRML 输出
wbmp Wireless BitMap(WBMP) 格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | digraph G { size="8,6"; ratio=fill; node[fontsize=24]; ciafan->computefan; fan->increment; computefan->fan; stringdup->fatal; main->exit; main->interp_err; main->ciafan; main->fatal; main->malloc; main->strcpy; main->getopt; main->init_index; main->fatal; main->malloc; main->strcpy; main->getopt; main->init_index; main->strlen; fan->fatal; fan->ref; fan->interp_err; ciafan->def; fan->free; computefan->stdprintf; computefan->get_sym_fields; fan->exit; fan->malloc; increment->strcmp; computefan->malloc; fan->stdsprintf; fan->strlen; computefan->strcmp; computefan->realloc; computefan-strlen; debug->sfprintf; debug-strcat; stringdup->malloc; fatal->sfprintf; stringdup->strcpy; stringdup->strlen; fatal->exit; subgraph "cluster_error.h" { label="error.h"; interp_err;} subpraph "cluster_sfio.h" { label="sfip.h"; sfprintf;} subgraph "cluster_ciafan.c" { label="ciafan.c"; ciafan; computefan; increment;} subgraph "cluster_util.c" { label="util.c"; stringdup; fatal; debug; } subgraph "cluster_query.h" { label="query.h"; ref; def;} subgraph "cluster_field.h" { get_sym_fields;} subgraph "cluster_stdio.h" { label="stdio.h"; stdprintf; stdspirntf;} subgraph "cluster_<libc.a>" {getopt;} subgraph "cluster_stdlib.h" {label="stdlib.h"; exit; malloc; free; realloc;} subgraph "cluster_main.c" {main;} subgraph "cluster_index.h" {init_index;} subgraph "cluster_string.h" { label="string.h"; strcpy; strlen; strcmp; strcat;} } |
-Gname=value 设置图形属性默认值。通常,在命令行(而不是在图形文件中)设置大小、分值和相关值很方便。类似的标志 -N 或 -E 设置默认节点或边属性。请注意,文件内容重写命令行参数。
-llibfile 指定与设备相关的图形库文件。可以提供多个库。这些名称在输出开始时传递给代码生成器。
-ooutfile 将输出写到 outfile 文件中
-v 请求详细输出。在处请求详细输出。在处理大型布局时,详细消息可能会对dot的process进行一些估计。
-V 打印版本号和退出。
5 Miscellaneous
在顶层图形标题中,可以声明图形为严格二图或严格图形。这禁止创建多边,即在定向情况下,最多只能有一个带有给定尾节点和头节点的边。对于无方向图形,最多只能有一个边连接到同一两个节点。使用同一两个节点的后续边语句将标识边缘与以前定义的一个,并应用边缘语句中给出的任何属性。
节点(nodes)、边(edges)和图形(graphs)可能具有 URL 属性。在某些输出格式(ps2、imap、cmapx 或 svg 中),此信息集成在输出中,以便在使用适当的显示时节点(nodes)、边(edges)和群集(clusters)变为活动链接工具。通常,附加到顶级图形的 URL 用作基本 URL,支持组件上的相对 URL。当输出格式为 imap 或 cmapx 时,头URL 和 tailURL 属性也发生类似的处理。
对于某些格式(ps, fig or svg), comment 属性可用于在输出中嵌入人类可读符号。
6 Conclusions 结论
点产生令人愉悦的分层图形,可以应用于许多设置。由于点的基本算法工作良好,因此,我们有很好的基础来深入研究绘制大图和在线(动画)图形绘制的方法等问题。
7 Acknowledgments 感谢
我们感谢 Phong Vo 关于图形绘制算法和编程的建议。图形库使用 Phong 的 splay tree dictionary library。此外,用户 dag, dot 的前身,给了我们许多好的建议。盖伊·雅各布森(Guy Jacobson)和兰迪·哈克巴特(Randy Hackbarth)审阅了本手册的早期草稿,埃姆登(Emden)为当前的修订做出了重大贡献。约翰·埃尔森(John Ellson)写了广义多边形形状和花了相当大的努力,使其强大和高效。他还编写了 GIF 和 ISMAP 生成器和其他工具,将 Graphviz 带到 Web 上。