Skip to content

AntV X6 布局管理器深度解析:迭代布局与父子嵌套

图编辑应用的核心在于“看得清、看得准”。在多层级的概念模型中,节点大小与位置需要动态适配父子关系与连线结构。本文解析一个基于 dagre 的迭代式布局管理器,如何在 AntV X6 中实现稳定、可控的布局流程。

设计目标

  • 根据层级自动设置节点大小
  • 父节点自适应包裹子节点边界
  • 根节点与子树分别布局,整体收敛稳定
  • 提供方向与间距配置,适配不同业务场景

初始化与配置

初始化尺寸

javascript
setNodeSize() {
  for (let level = 1; level <= this.options.leafLevel; level++) {
    this.options.nodeSizes.push({
      width: this.options.leafSize.width + this.options.embedPadding * (this.options.leafLevel - level) * 2,
      height: this.options.leafSize.height + this.options.embedPadding * (this.options.leafLevel - level) * 2,
    })
  }
}

布局主流程

入口: layout()

javascript
layout() {
  const { nodes, edges } = this._getGraphElements()
  if (nodes.length == 0) return
  this._executeLayoutSteps(nodes, edges)
}

步骤拆解:

  • 节点分类: _categoryizeNodesByLevel()
  • 迭代布局: _performIterativeLayout()
    • 计算所有节点大小(自底向上):_calculateOptimalSizes()
    • 计算所有节点位置(自顶向下):_calculateOptimalPositions()

大小计算(父子关系)

父节点大小由子节点边界与内边距计算得到:__calculateParentNodeSizes()

设置父节点尺寸:__setParentNodeSize()

javascript
_setParentNodeSize(parentNode, bounds) {
  if (bounds.isValid) {
    const parentWidth = bounds.maxX - bounds.minX + 2 * this.options.embedPadding
    const parentHeight = bounds.maxY - bounds.minY + 2 * this.options.embedPadding
    parentNode.setSize(parentWidth, parentHeight)
  }
}

叶子节点默认大小:_setLeafNodeSizes() (LayoutManager.js:157-164)

位置计算(根与子树)

根节点布局(使用当前实际尺寸):__layoutRootNodesWithCurrentSizes()

javascript
const rootGraph = this._createDagreGraph(1);
rootNodes.forEach((node) => {
  const currentSize = node.getSize();
  rootGraph.setNode(node.id, {
    width: currentSize.width,
    height: currentSize.height,
  });
});
dagre.layout(rootGraph);
this._applyNodePositions(rootGraph);

子节点相对父节点重新定位:_repositionChildrenForParent()

关键偏移量计算

javascript
const offsetX = parentPos.x + this.options.embedPadding - bounds.minX;
const offsetY = parentPos.y + this.options.embedPadding - bounds.minY;
// 根据偏移与当前大小设置最终位置
childNode.position(finalX, finalY, { skipParentHandler: true });

方向与间距

布局方向由层级决定:_getDirectionForLevel()

javascript
_getDirectionForLevel(level) {
  return this.options.direction[level - 1]
}
  • nodesep(同层间距)与 ranksep(层级间距)在 dagre.setGraph 中配置。

连线参与布局

在根与子图中,连线用于辅助 dagre 优化节点的相对位置:

  • 根层连线:_addEdgesBetweenNodes()
  • 子层连线:_addChildEdgesToGraph()

在某些场景可进一步使用 _adjustEdgePaths() 优化连线路径(L 型、跳线等)。

使用示例与建议

  • 在 Vue 组件数据渲染后执行布局
javascript
nodeManager.createNodes(graphData.value.nodes);
edgeManager.createEdges(graphData.value.edges);
layoutManager.layout();

总结

该布局管理器通过“自底向上尺寸计算 + 自顶向下位置应用”的迭代策略,解决了父子节点嵌套与 dagre 全局布局的兼容问题。结合 X6 的节点/边 API,可在复杂建模场景中保持良好的可读性与稳定性。

Released under the MIT License.