AntV/X6 复杂图形应用架构解析:工厂模式与管理器体系
在现代前端开发中,基于图形的编辑器(流程图、ER 图、概念模型等)往往涉及到大量的交互与状态管理。AntV/X6 提供了强大的底层能力,但如何在实际项目中优雅地组织和管理这些能力,是工程化的关键。
本文将介绍一种在大型项目中实践成熟的架构:以工厂模式(Factory Pattern)为核心的管理器(Manager)体系,配合 Vue3 在 @antv/x6-vue-shape 下的组件化节点渲染,实现清晰的模块边界与高可维护性。
效果预览
以概念建模画布为例:通过统一的工厂创建 Graph、节点/边、布局与事件管理器,在 Vue 组件中只需极少的调用即可完成初始化与数据渲染。
核心特性
- 模块解耦:Graph/Node/Edge/Layout/Event 职责清晰、独立演进
- Vue 集成:节点使用 Vue 组件渲染,支持 Teleport 容器挂载
- 可配置化:通过 options 控制只读模式、布局参数、节点大小等
- 工程化:统一初始化与销毁流程,便于测试与维护
架构总览
整体架构由一个总工厂 ManagerFactory 负责创建与组合各类管理器:
- GraphManager:X6 Graph 实例初始化与交互开关
- NodeManager:节点注册、创建与父子嵌套管理,Vue 节点渲染
- EdgeManager:连线创建与样式管理
- LayoutManager:自动布局与定位
- EventManager:统一事件订阅与回调分发
管理器工厂设计
工厂模式将多实例创建的复杂性收敛到统一入口,便于外部使用与测试。
javascript
export class ManagerFactory {
constructor(container, options = {}) {
this.container = container;
this.options = {
embedPadding: 40,
leafLevel: 3,
direction: ["TB", "LR", "LR"],
leafSpacing: 120,
nonLeafSpacing: 40,
rankSpacing: 80,
leafSize: { width: 160, height: 70 },
autoResize: true,
readonly: false,
...options,
};
}
initialize() {
// 1. Graph 实例
this.graphManager = new GraphManager(this.container, this.options);
// 2. 布局
this.layoutManager = new LayoutManager(
this.graphManager.graph,
this.options
);
// 3. 节点
this.nodeManager = new NodeManager(
this.graphManager.graph,
this.layoutManager,
this.options
);
// 4. 边
this.edgeManager = new EdgeManager(this.graphManager.graph);
// 5. 事件
this.eventManager = new EventManager(
this.graphManager.graph,
this.nodeManager,
this.edgeManager,
this.layoutManager,
this.options
);
return {
graph: this.graphManager.graph,
graphManager: this.graphManager,
nodeManager: this.nodeManager,
edgeManager: this.edgeManager,
layoutManager: this.layoutManager,
eventManager: this.eventManager,
};
}
}销毁流程集中,确保事件、布局、节点、边与 Graph 均被正确释放。
GraphManager:图形实例与交互
GraphManager 封装了 Graph 初始化细节与核心交互 API:
javascript
import { Graph } from "@antv/x6";
export class GraphManager {
constructor(container, options = {}) {
this.container = container;
this.options = options;
this.initialize();
}
initialize() {
this.graph = new Graph({
container: this.container,
autoResize: this.options.autoResize,
mousewheel: {
enabled: true,
modifiers: "ctrl",
minScale: 0.1,
maxScale: 5,
},
panning: { enabled: true, eventTypes: ["mouseWheel"] },
connecting: {
router: { name: "manhattan", args: { offset: "center" } },
connector: { name: "rounded", args: { radius: 8 } },
anchor: "center",
connectionPoint: "anchor",
allowBlank: false,
allowLoop: false,
allowNode: false,
allowEdge: false,
allowPort: true,
allowMulti: true,
highlight: true,
snap: { radius: 40 },
},
});
}
setNode(mode) {
if (mode == "select") {
this.graph.enableSelection();
this.disablePanning();
} else if (mode == "drag") {
this.graph.cleanSelection();
this.graph.clearTransformWidgets();
this.graph.disableSelection();
this.enablePanning();
}
}
enablePanning() {
this.graph.options.panning.eventTypes = ["leftMouseDown", "mouseWheel"];
this.graph.container.style.cursor = "grab";
}
disablePanning() {
this.graph.options.panning.eventTypes = ["mouseWheel"];
this.graph.container.style.cursor = "default";
}
}NodeManager 负责注册 Vue 组件节点形状(通过 @antv/x6-vue-shape),并建立父子层级嵌套关系:
javascript
import { register, getTeleport } from "@antv/x6-vue-shape";
export class NodeManager {
constructor(graph, layoutManager = null, options = {}) {
this.graph = graph;
this.layoutManager = layoutManager;
this.options = options;
this._registerNodeShapes();
}
_registerNodeShapes() {
this.shapeName = `custom-node=${Date.now()}`;
register({
shape: this.shapeName,
component: this.options.CustomNode,
ports: {
/* 四方向连接桩配置 */
},
});
}
getTeleportContainer() {
return getTeleport();
}
createNodes(nodesData) {
const leafLevel = this.options.leafLevel;
const nodesByLevel = {};
for (let level = 1; level <= leafLevel; level++) nodesByLevel[level] = [];
nodesData.forEach((nodeData) => {
const node = this.createNode(nodeData);
const level = nodeData.level;
if (level >= 1 && level <= leafLevel)
nodesByLevel[level].push({ node, data: nodeData });
});
for (let level = 1; level < leafLevel; level++) {
const parentNodes = nodesByLevel[level];
const childNodes = nodesByLevel[level + 1];
parentNodes.forEach(({ node: parentNode, data: parentData }) => {
const children = childNodes.filter(
({ data: childData }) => childData.parentId === parentData.id
);
children.forEach(({ node: childNode }) =>
parentNode.addChild(childNode)
);
});
}
}
}使用方法:在 Vue 组件中初始化
在实际 Vue 组件中,初始化非常简洁。
javascript
const initGraph = () => {
if (!containerRef.value) return;
try {
// 创建管理器工厂
managerFactory = new ManagerFactory(containerRef.value, {
leafLevel: 2,
leafSize: { width: 160, height: 70 },
readonly,
CustomNode,
});
// 初始化所有管理器
managers = managerFactory.initialize();
// 设置事件回调
setupEventCallbacks();
// 初始化自定义节点
TeleportContainer.value = managers.nodeManager.getTeleportContainer();
// 初始化数据
initializeData();
} catch (error) {
console.error("图形初始化失败:", error);
}
};数据渲染与布局
javascript
const initializeData = async () => {
if (!managers) return;
const { nodeManager, edgeManager } = managers;
managers.graphManager.graph.clearCells();
nodeManager.createNodes(graphData.value.nodes);
edgeManager.createEdges(graphData.value.edges);
managers.layoutManager.layout();
managers.graphManager.zoomTo100();
managers.graphManager.cleanHistory();
containerRef.value.focus();
};事件回调设置示例通过 EventManager.setCallback 统一分发。
资源释放与模式切换
- 统一销毁: ManagerFactory.destroy() 依次释放事件、布局、边、节点与 Graph。
- 模式切换:通过 GraphManager.setMode('select' | 'drag') 切换框选与拖拽模式。
设计原则与最佳实践
- 单一职责:每个 Manager 专注一个维度,避免巨型类
- 明确边界:外部只与工厂返回的对象交互,内部细节可演进
- 配置驱动:通过 options 控制行为(只读、布局、尺寸等)
- 与 Vue 融合:节点渲染组件化,Teleport 解耦渲染容器
总结
通过工厂模式与管理器体系,将 X6 的底层能力抽象为清晰的模块,显著降低了复杂度,提升了可维护性与扩展性。在实际业务中,可以继续:
- 扩展 EventManager,统一处理更多交互与快捷键
- 引入不同布局策略(层级、网格、流式)并在 LayoutManager 中配置切换
这套架构不仅适用于概念模型、逻辑模型,还适用于各类图编辑场景,值得在中大型前端项目中推广。