Skip to content

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 中配置切换

这套架构不仅适用于概念模型、逻辑模型,还适用于各类图编辑场景,值得在中大型前端项目中推广。

Released under the MIT License.