Graphviz 能否明确约束边的走向,使其尽量接近指定方向?
Graphviz 没有内置的通用约束,可以为每条边指定 E、NE、N、NW、W、SW、S、SE 中的一个方向,再自动调整节点位置,让边尽量接近这个方向。
它支持指定边连接节点的方位,也能使用预先计算好的节点坐标。绘制恒星系统地图时,如果坐标可控,可以继续使用 Graphviz;如果还要自动优化方向、减少交叉,并生成地铁图式的折线,就需要额外的布局或布线算法。
为什么 circo 和 twopi 的布局会失去方向感
circo 使用圆形布局;twopi 按节点到根节点的图距离,把节点放在不同的同心圆上。两者的布局规则都无法表达“B 应位于 A 的东北方”这样的逐边要求。circo 文档、twopi 文档
小规模示例中,第一个节点可能恰好出现在东侧,但这个位置没有保证。增加节点或修改连接后,原来的排列就可能改变。
不可见占位节点也是布局输入的一部分,会影响图的结构。它们能间接改变排列,却没有建立明确的方向约束,因此无法可靠地防止翻转和交叉。
只需要控制连接方位时,使用端口
Graphviz 支持用 compass point 指定边连接节点的哪个方位。例如:
digraph Routes {
graph [splines=spline];
node [shape=circle];
A:e -> B:w;
A:ne -> C:sw;
}
其中:
A:e -> B:w指定边连接 A 的东侧和 B 的西侧。A:ne -> C:sw指定边连接 A 的东北侧和 C 的西南侧。
也可以用等价的边属性:
A -> B [tailport=e, headport=w];
方位值使用小写:n、ne、e、se、s、sw、w、nw。端口方位文档
这些设置只约束连接位置,不能保证 B 位于 A 的东侧,也不能保证整条边沿指定方向延伸。布局引擎仍可能把目标节点放在其他位置,让边绕行。
rankdir=LR 则控制 dot 的整体分层方向,无法为每条边分别指定八个方位。属性文档
节点方位必须稳定时,先计算坐标
如果游戏已经保存了恒星坐标,或者你能自行生成示意坐标,就可以让 Graphviz 按这些坐标绘制地图。
例如,将以下内容保存为 map.dot:
graph StarMap {
graph [splines=line];
node [shape=circle, fixedsize=true, width=0.5];
Center [pos="0,0"];
E [pos="144,0"];
NE [pos="144,144"];
N [pos="0,144"];
NW [pos="-144,144"];
W [pos="-144,0"];
SW [pos="-144,-144"];
S [pos="0,-144"];
SE [pos="144,-144"];
Center -- E;
Center -- NE;
Center -- N;
Center -- NW;
Center -- W;
Center -- SW;
Center -- S;
Center -- SE;
}
运行:
neato -n2 -Tsvg map.dot -o map.svg
-n2 使用给定的节点位置,不执行节点重叠消除。如果没有提供边路径,它仍会根据 splines 计算连线。命令行文档
使用时有几个限制:
- 每个节点都必须有
pos。 - 在
neato -n2下,输入坐标的单位是 point,72 point = 1 inch;普通neato、fdp默认把输入坐标解释为 inch。pos 文档 - 这个示例直接指定了节点位置,没有执行方向优化,也不会自动解决交叉、标签遮挡或直线穿过其他节点的问题。
如果只想固定少数节点,让其他节点参与布局,可以在普通 neato 或 fdp 中使用 pin=true,也可以写成 pos="x,y!"。不过,节点重叠消除或宽高比调整仍可能改变坐标。固定锚点也不会自动产生逐边的角度约束。pin 文档
想让引擎自动微调,需要把方向写进优化目标
这里有两个不同的目标:
- B 相对于 A 大致位于东北方。
- A 到 B 的折线路径只包含水平、垂直和 45° 斜线段。
前者属于节点布局,后者属于八方向布线,通常称为 octilinear routing。一条折线即使每一段都符合八方向规则,终点也未必位于指定方位。
要自行实现“微调”,可以按以下流程处理:
- 从恒星原始坐标或已有地图生成初始位置。
- 为每个节点对记录目标方向,例如 A 到 B 为 NE。
- 优化节点坐标,对偏离目标方向的程度施加惩罚,同时约束节点间距。
- 固定部分锚点,或限制节点偏离原始位置的距离,减少整张地图旋转、翻转或大幅重排。
- 根据最终坐标执行布线,再处理标签和线路间距。
以 x 向右、y 向上的坐标系为例,“B 严格位于 A 的东北 45° 方向”可以写成:
x_B - x_A = y_B - y_A
x_B - x_A >= d_min
其中,d_min > 0 用来避免两个节点重合。如果只要求“大致朝东北”,可以允许两个坐标差存在一定偏差,或把这种偏差加入优化目标。
这属于自定义布局模型,Graphviz 不会直接解析这些公式。求出坐标后,可以用前面的 neato -n2 输出图形。
方向要求之间也可能存在冲突。例如,A 到 B、B 到 C、C 到 A 不可能同时严格朝东。自动布局必须允许放宽部分方向要求,或报告无解。
地铁图式布线可以考虑什么工具
如果允许使用商业库,可以评估 yFiles。它的 EdgeRouter 支持 octilinear routing,能生成由水平、垂直和 45° 斜线段组成的路径,并提供距离、网格和连接位置等配置。yFiles 布线文档
EdgeRouter 不会改变节点位置,所以“B 应在 A 的东北方”仍要在节点布局阶段处理,之后再调用它计算线路。可以先通过官方演示判断线路样式是否符合需求。
Graphviz 的以下两个属性也不能直接实现八方向布局:
splines=polyline生成折线,不限制折线段角度。splines=ortho生成水平和垂直线段,不包含 45° 斜线;官方文档还注明其端口处理存在限制,在dot中也不处理边标签。splines 文档
对于这类恒星地图,可以先根据方向要求求出节点坐标,再执行八方向布线,让布局和线路更可控。坐标确定后,Graphviz 可以负责绘制。
备注:内容仅供参考。
