(一)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:统一事件订阅与回调分发
管理器工厂设计
工厂模式将多实例创建的复杂性收敛到统一入口,便于外部使用与测试。
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:
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),并建立父子层级嵌套关系:
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 组件中,初始化非常简洁。
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)
}
}
数据渲染与布局
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 中配置切换
这套架构不仅适用于概念模型、逻辑模型,还适用于各类图编辑场景,值得在中大型前端项目中推广。
更多推荐
所有评论(0)