Web编程技术营地
研究、演示、创新

画布布局

撰写时间:2023-07-10

修订时间:2023-07-16

理解rankdir布局属性

对象之间是否有连接关系,会影响到布局效果。

rankdir=TB

digraph { rankdir=TB; /* default setting */ a, b; }

默认情况下,rankdir=TB。此时,如果声明两个没有连接关系的节点,则它们在水平方向上排列。

而如果这两个节点有连接关系,则它们的布局方向发生反转,改为在竖直方向上排列。

digraph { rankdir=TB; /* default setting */ a -> b; }

可见,rankdir指的是有连接关系的节点的排列方向。

rankdir=LR

当不存在连接关系时,各节点按声明顺序从下到上排列。就好像rankdir=TB时逆时针旋转一样。

digraph { rankdir=LR; a b c d; }

如果各节点都有连线,则依序从左到右排列。

digraph { rankdir=LR; a -> b -> c; }

各节点如果既有连线,也有没有连线的节点,则有连线的节点排成一行后,按声明的顺序从下到上排列。

digraph { rankdir=LR; a -> b; c, d; }

如果对这种布局感到别扭、不习惯,可使用cluster,具体详见cluster受rankdir的影响。

将指定节点排在同行或同列

现在,先假设仍在默认的竖直布局下。

digraph { bgcolor=lightgray; rankdir=TB; /* default setting */ {rank=same; b, c, e}; a -> b -> c -> d -> e; }

如上所述,在默认的竖直布局下,各个有连接关系的节点都会从上到下依序排列。而如果设置rank=same并指定排在同一等级上的各个节点,则这些节点就会优先排在同一行上。

我们可以总结为:对于rankdir=TB,rank=same指定哪些节点排在同一行上。

再看ranldir=LR的情况。

digraph { bgcolor=lightgray; rankdir=LR; {rank=same; b, c, e}; a -> b -> c -> d -> e; }

同理,对于rankdir=LR,rank=same指定哪些节点排在同一列上。

设置rank的另一种写法

除了{rank=same; a, b, c}的写法外,我们还可以在该代码中加入连接关系。

digraph { bgcolor=lightgray; rankdir=TB; {rank=same; a -> b -> c}; {rank=same; d -> f -> g}; {rank=same; h -> i -> j}; c -> d; }

这种写法尤其适合具有同行中多个节点都有连接关系这种特征的情况,并且代码很清晰。

第3组链条由于未与第1组或第2组链条产生联系,于是dot引擎见缝插针,将其作为一个整体排在第一行的右边,从而节省了空间。这是使用GraphViz这种技术的一大优势所在,它将较为复杂的有关排版的算法、细节都隐藏起来,让使用者只关心最核心的业务逻辑即可。

如果需要将第2行往回排、并连接第3组链条呢?

digraph { bgcolor=lightgray; rankdir=TB; {rank=same; a -> b -> c}; {rank=same; g -> f -> d [dir=back]}; {rank=same; h -> i -> j}; c -> d; g -> h; }

节点出现的先后顺序很重要。正如上个图表所示,如果第二行中按顺序书写d, f, g, 则这3个节点会在第二行中依序往右排列。若想要此链接向左折返,首先,先按它们在画板中第二行从左到右的顺序写出g, f, d,这样dot引擎会将第一行与第二行对齐;接着在该行上设置属性dir=back,反转箭头即可。

至于此时要与第3组链条连接,就相对比较简单了,g -> h即可。

现在是可以体验dot引擎的强大之处的时候了。随意连接上面的各个节点,你会发现,渲染结果都会满足你的各种奇异要求。老实说,只要我们精准地设定好关键的约束条件,dot引擎足够成熟,可以胜任工作。如果出现意料之外的事情,不要抱怨GraphViz,更不应因此而放弃使用GraphViz,而应从我们自身找原因。多做各类练习,玩转GraphViz。

将节点排在等级最低或等级最高的位置上

rank=min表示哪些节点应排在等级最低的位置上。

对于rankdir=TB的默认布局,等级最低的位置意味着第一行。

digraph { bgcolor=lightgray; rankdir=TB; {rank=min; b, c, e}; a -> b -> c -> d -> e; }

而对于rankdir=LR的布局,等级最低的位置意味着第一列。

digraph { bgcolor=lightgray; rankdir=LR; {rank=min; b, c, e}; a -> b -> c -> d -> e; }

理解了上面的内容,就不难理解rank=max的意思了,它表示哪些节点应排在等级最高的位置上。对于rankdir=TB的默认布局,等级最高的位置意味着最后一行;而对于rankdir=LR的布局,等级最高的位置意味着最后一列。读者可以修改LiveEditor的内容试试看。

rank=same及rank=min的区别

对于相同的代码,只是rank=same及rank=min不同,看看它们的区别。

rank=same:

digraph { bgcolor=lightgray; rankdir=TB; /* default setting */ {rank=same; b, c, e}; a -> b -> c -> d -> e; }

rank=min:

digraph { bgcolor=lightgray; rankdir=TB; {rank=min; b, c, e}; a -> b -> c -> d -> e; }

相同的地方是:b, c, e这3个节点都处于同一行上。

不同的地方在于确定哪一行是同行的机制不同。对于rank=min,先将这些节点排在最低阶即第一行的位置上,然后再排其他节点,这就导致了最终的布局与原来所指定的从上而下的rankdir=TB布局相矛盾。

例如,对于上面第2个图表,b, c, e这3个节点先依序从左到右排在第一行,此时a节点虽是链条上的第一个节点,也只能排在第二行的第一个位置上(与b节点对齐)。对于d节点,由于我们未指定它是否应与a节点同处一行,从整洁的角度,dot引擎将其与a节点排在同一行上。至于d节点所在的列,则依箭头顺序定位在c与e节点的中间。

而对于rank=same,则先将在箭头链条上第一个出现的节点排在最低的位置,然后再在下一行排第二个出现的节点。在此过程中,如果有哪个节点首次出现在rank=same所指定的同一行中,则以该节点所在的行作为对齐的标准。

例如,对于上面第1个图表,先在第一行上排链条上的第一个节点a,再在第二行排节点b。由于要求b, c, e这3个节点都处于同一行上,因此它们都确定下来排在第二行上。对于d节点,则根据rankdir=TB的要求排在下一行即第3行。导致所有节点占据了3行。

rank=source

利用constraint布局

digraph { graph [ label="drawArrays" rankdir=TB; ] node [colorscheme=pastel23 shape=circle]; f -> a [constraint=false minlen=5]; f -> b [constraint=false style=dashed color=gray]; f -> c [constraint=false style=dashed color=gray]; a -> b; a -> c; b -> c [constraint=false minlen=3]; }

当设置constraint=false时,连线不会导致rank的变化。

在例子中,如果没有constraint=false,b -> c将导致c往下降一级。使用constraint=false后,c不会降级。

同理,设置了constraint=false后,f节点连接a节点也不会导致a节点降级。利用此特性,f可自由连接任意一个节点而不会破坏原来的布局。

subgraph在布局中的作用

subgraph可用于集中设置相关节点及连线的新式。

受rankdir的影响

普通的subgraph受rankdir的影响,不管其值是TB还是LR,都与没有subgraph时一样。

digraph { rankdir=TB; subgraph { a -> b; c d; } }

注意,rankdir只适用于graph,不适用于其他对象,包括subgraph。

而cluster正好与没有subgraph时相反。当rankdir=TB时:

digraph { rankdir=TB; subgraph clusterA { a -> b; c d; } }

当rankdir=LR时:

digraph { rankdir=LR; subgraph clusterA { a -> b; c d; } }

因此,当rankdir=LR时,如果希望节点按声明的顺序出现,使用cluster即可。

rank对cluster的影响

默认情况下,cluster内的节点是一个整体,但如果使用rank=same来对齐分别来自不同的cluster的两个节点,将导致这两个节点脱离各自的cluster。

digraph { subgraph clusterA { a -> b -> c; } subgraph clusterB { f -> g; } {rank=same; b, f;} }

参考资源

  1. Graphviz.org