Skip to content

核心架构与设计原理

架构概览

diagram-js 采用模块化的架构设计,通过依赖注入和事件驱动的方式实现高度可扩展的图表编辑器框架。整个系统围绕以下核心理念构建:

  • 模块化设计:所有功能都封装为独立的模块,便于组合和定制
  • 依赖注入:使用 didi 实现松耦合的组件依赖管理
  • 事件驱动:通过事件总线实现组件间的通信
  • 命令模式:所有修改操作都通过命令栈执行,自动支持撤销/重做

架构全景图

核心组件详解

Canvas - 画布核心

Canvas 是 diagram-js 的核心组件,负责管理整个图表的渲染和视图层次。

主要职责:

  • 管理 SVG 根元素和图层结构
  • 提供视图变换(缩放、平移)
  • 管理根元素(Root Elements)
  • 计算和维护视图框(ViewBox)

关键 API:

typescript
interface Canvas {
  // 获取根元素
  getRootElement(): Shape;

  // 设置根元素
  setRootElement(root: Shape): void;

  // 添加形状到画布
  addShape(shape: Shape, parent?: Shape): SVGElement;

  // 添加连接线到画布
  addConnection(connection: Connection, parent?: Shape): SVGElement;

  // 获取 SVG 容器
  getContainer(): HTMLElement;

  // 视图框操作
  viewbox(box?: Rect): Rect;
  zoom(newScale: number | "fit-viewport", center?: Point): number;
  scroll(delta: Point): void;
}

图层管理:

ElementRegistry - 元素注册表

ElementRegistry 维护所有图表元素的索引,提供快速查询和访问能力。

主要职责:

  • 存储和索引所有元素(Shape、Connection、Label)
  • 提供元素的快速查找
  • 管理元素与 SVG DOM 节点的映射关系

关键 API:

typescript
interface ElementRegistry {
  // 通过 ID 获取元素
  get(id: string): Element;

  // 获取所有元素
  getAll(): Element[];

  // 过滤元素
  filter(fn: (element: Element) => boolean): Element[];

  // 添加元素
  add(element: Element, gfx: SVGElement): void;

  // 移除元素
  remove(element: Element): void;

  // 更新元素 ID
  updateId(element: Element, newId: string): void;

  // 获取元素对应的图形节点
  getGraphics(element: Element): SVGElement;
}

EventBus - 事件总线

EventBus 是 diagram-js 的神经中枢,所有组件间的通信都通过事件完成。

事件优先级机制:

事件监听器可以指定优先级,数字越大优先级越高:

typescript
// 高优先级监听器(优先执行)
eventBus.on("element.click", 1500, function (event) {
  console.log("高优先级处理");
});

// 默认优先级 (1000)
eventBus.on("element.click", function (event) {
  console.log("默认优先级处理");
});

// 低优先级监听器
eventBus.on("element.click", 500, function (event) {
  console.log("低优先级处理");
});

事件生命周期:

常用事件模式:

typescript
// 1. 简单监听
eventBus.on('element.changed', function(event) {
  const { element } = event;
  console.log('元素已更改:', element);
});

// 2. 一次性监听
eventBus.once('diagram.init', function() {
  console.log('图表初始化完成');
});

// 3. 事件拦截和修改
eventBus.on('commandStack.execute', function(event) {
  // 可以修改事件数据
  event.context.customData = 'modified';
});

// 4. 事件取消
eventBus.on('element.move', function(event) {
  if (/* 某些条件 */) {
    return false; // 阻止事件继续传播
  }
});

GraphicsFactory - 图形工厂

GraphicsFactory 负责创建和更新 SVG 图形元素。

主要职责:

  • 根据模型数据创建 SVG 元素
  • 更新现有 SVG 元素
  • 管理渲染器(Renderer)

渲染器链:

关键概念:

typescript
// 自定义渲染器示例
class CustomRenderer extends BaseRenderer {
  constructor(eventBus: EventBus) {
    super(eventBus, 1500); // 优先级 1500
  }

  canRender(element: Element): boolean {
    // 判断是否可以渲染此元素
    return element.type === "custom:Shape";
  }

  drawShape(visuals: SVGElement, element: Shape): SVGElement {
    const rect = svgCreate("rect");
    svgAttr(rect, {
      x: 0,
      y: 0,
      width: element.width,
      height: element.height,
      fill: "#3498db",
      stroke: "#2c3e50",
      "stroke-width": 2,
    });
    svgAppend(visuals, rect);
    return rect;
  }

  drawConnection(visuals: SVGElement, connection: Connection): SVGElement {
    // 绘制连接线
    return svgCreate("path");
  }
}

ElementFactory - 元素工厂

ElementFactory 负责创建符合 diagram-js 规范的元素对象。

元素类型:

typescript
// Shape - 形状元素
interface Shape extends Element {
  x: number;
  y: number;
  width: number;
  height: number;
  parent?: Shape;
  children?: Element[];
  host?: Shape; // 附加到的宿主元素
  attachers?: Shape[]; // 附加的元素列表
}

// Connection - 连接线
interface Connection extends Element {
  waypoints: Point[];
  source: Shape;
  target: Shape;
  parent?: Shape;
}

// Label - 标签
interface Label extends Shape {
  labelTarget: Element;
}

// Root - 根元素
interface Root extends Shape {
  // 根元素特有属性
}

创建元素示例:

typescript
// 创建形状
const shape = elementFactory.createShape({
  id: "Shape_1",
  type: "custom:Task",
  x: 100,
  y: 100,
  width: 100,
  height: 80,
});

// 创建连接线
const connection = elementFactory.createConnection({
  id: "Connection_1",
  type: "custom:SequenceFlow",
  waypoints: [
    { x: 200, y: 140 },
    { x: 300, y: 140 },
  ],
  source: sourceShape,
  target: targetShape,
});

CommandStack - 命令栈

CommandStack 实现命令模式,提供撤销/重做功能。

命令执行流程:

实现自定义命令:

typescript
class MoveShapeHandler implements CommandHandler {
  execute(context: any) {
    const { shape, delta } = context;

    // 保存旧位置以便撤销
    context.oldPosition = {
      x: shape.x,
      y: shape.y,
    };

    // 执行移动
    shape.x += delta.x;
    shape.y += delta.y;

    return shape;
  }

  revert(context: any) {
    const { shape, oldPosition } = context;

    // 恢复到旧位置
    shape.x = oldPosition.x;
    shape.y = oldPosition.y;

    return shape;
  }
}

// 注册命令
class CustomModule {
  static $inject = ["commandStack"];

  constructor(commandStack: CommandStack) {
    commandStack.registerHandler("shape.move", MoveShapeHandler);
  }
}

依赖注入系统

diagram-js 使用 didi 作为依赖注入容器。

模块声明

typescript
// 定义模块
export default {
  __init__: ["customService"], // 自动初始化的服务
  __depends__: [CoreModule, SelectionModule], // 依赖的模块
  customService: ["type", CustomService], // 服务声明
  customRenderer: ["type", CustomRenderer],
};

服务注入

typescript
class CustomService {
  // 声明依赖
  static $inject = ["eventBus", "canvas", "elementRegistry"];

  constructor(
    eventBus: EventBus,
    canvas: Canvas,
    elementRegistry: ElementRegistry,
  ) {
    this.eventBus = eventBus;
    this.canvas = canvas;
    this.elementRegistry = elementRegistry;

    // 初始化逻辑
    this.init();
  }

  init() {
    this.eventBus.on("element.click", (event) => {
      console.log("Element clicked:", event.element);
    });
  }
}

服务类型

  • type: 标准服务,每次注入都创建新实例(默认)
  • factory: 工厂函数,返回值作为服务
  • value: 直接使用提供的值
typescript
export default {
  // 类型服务
  myService: ["type", MyService],

  // 工厂服务
  config: [
    "factory",
    function () {
      return {
        apiUrl: "https://api.example.com",
        timeout: 5000,
      };
    },
  ],

  // 值服务
  version: ["value", "1.0.0"],
};

SVG 渲染引擎

diagram-js 使用 SVG 作为渲染技术,提供高质量的矢量图形渲染。

SVG 层次结构

xml
<svg class="djs-container">
  <g class="viewport">
    <g class="layer-base">
      <g class="djs-group" data-element-id="Shape_1">
        <!-- 形状图形 -->
      </g>
      <g class="djs-group" data-element-id="Connection_1">
        <!-- 连接线图形 -->
      </g>
    </g>
    <g class="layer-overlays">
      <!-- 覆盖层内容 -->
    </g>
  </g>
</svg>

渲染优化

  • 分层渲染:不同类型的元素渲染在不同的图层
  • 增量更新:只更新变化的元素
  • 虚拟化:大型图表的性能优化(可选)
  • 事件委托:使用 SVG 的事件冒泡机制

性能考虑

事件处理优化

typescript
// 避免在高频事件中执行昂贵操作
eventBus.on(
  "element.hover",
  500,
  debounce(function (event) {
    // 防抖处理
    updateTooltip(event.element);
  }, 100),
);

大规模图表优化

typescript
// 批量操作
commandStack.execute("elements.move", {
  elements: largeElementArray, // 批量移动
  delta: { x: 100, y: 0 },
});

// 暂停渲染
canvas.suspendRendering();
// ... 执行多个操作
canvas.resumeRendering();

下一步

Released under the MIT License.