Graph

首先关注 x6/src/graph/ 目录,其中 graph.ts 是核心,它定义了画布,承载所有其他功能。其中的 base.ts 是此目录除 view.ts 外所有功能共同的父类。

为了方便理解,我们目前仅关心核心实现,那么此目录就只需要关注 graph.tsview.ts

对于 graph.ts,我们观察到 Graph 的大部分都是都其属性功能的封装,我们剔除这些部分后,得到有效代码如下:

export class Graph extends Basecoat<EventArgs> {
  private installedPlugins: Set<Graph.Plugin> = new Set()
  public model: Model

  public readonly options: GraphOptions.Definition
  public readonly css: Css
  public readonly view: GraphView
  public readonly grid: Grid
  public readonly defs: Defs
  public readonly coord: Coord
  public readonly renderer: ViewRenderer
  public readonly highlight: Highlight
  public readonly transform: Transform
  public readonly background: Background
  public readonly panning: Panning
  public readonly mousewheel: Wheel
  public readonly virtualRender: VirtualRender
  public readonly size: Size

  constructor(options: Partial<GraphOptions.Manual>) {
    super()
    this.options = GraphOptions.get(options)
    this.css = new Css(this)
    this.view = new GraphView(this)
    this.defs = new Defs(this)
    this.coord = new Coord(this)
    this.transform = new Transform(this)
    this.highlight = new Highlight(this)
    this.grid = new Grid(this)
    this.background = new Background(this)

    if (this.options.model) {
      this.model = this.options.model
    } else {
      this.model = new Model()
      this.model.graph = this
    }

    this.renderer = new ViewRenderer(this)
    this.panning = new Panning(this)
    this.mousewheel = new Wheel(this)
    this.virtualRender = new VirtualRender(this)
    this.size = new Size(this)
  }

  // #region plugin

  use(plugin: Graph.Plugin, ...options: any[]) {
    if (!this.installedPlugins.has(plugin)) {
      this.installedPlugins.add(plugin)
      plugin.init(this, ...options)
    }
    return this
  }

  getPlugin<T extends Graph.Plugin>(pluginName: string): T | undefined {
    return Array.from(this.installedPlugins).find(
      (plugin) => plugin.name === pluginName,
    ) as T
  }

  getPlugins<T extends Graph.Plugin[]>(pluginName: string[]): T | undefined {
    return Array.from(this.installedPlugins).filter((plugin) =>
      pluginName.includes(plugin.name),
    ) as T
  }

  enablePlugins(plugins: string[] | string) {
    let postPlugins = plugins
    if (!Array.isArray(postPlugins)) {
      postPlugins = [postPlugins]
    }
    const aboutToChangePlugins = this.getPlugins(postPlugins)
    aboutToChangePlugins?.forEach((plugin) => {
      plugin?.enable?.()
    })
    return this
  }

  disablePlugins(plugins: string[] | string) {
    let postPlugins = plugins
    if (!Array.isArray(postPlugins)) {
      postPlugins = [postPlugins]
    }
    const aboutToChangePlugins = this.getPlugins(postPlugins)
    aboutToChangePlugins?.forEach((plugin) => {
      plugin?.disable?.()
    })
    return this
  }

  isPluginEnabled(pluginName: string) {
    const pluginIns = this.getPlugin(pluginName)
    return pluginIns?.isEnabled?.()
  }

  disposePlugins(plugins: string[] | string) {
    let postPlugins = plugins
    if (!Array.isArray(postPlugins)) {
      postPlugins = [postPlugins]
    }
    const aboutToChangePlugins = this.getPlugins(postPlugins)
    aboutToChangePlugins?.forEach((plugin) => {
      plugin.dispose()
    })
    return this
  }

  // #endregion

  // #region dispose

  @Basecoat.dispose()
  dispose() {
    this.clearCells()
    this.off()

    this.css.dispose()
    this.defs.dispose()
    this.grid.dispose()
    this.coord.dispose()
    this.transform.dispose()
    this.highlight.dispose()
    this.background.dispose()
    this.mousewheel.dispose()
    this.panning.dispose()
    this.view.dispose()
    this.renderer.dispose()

    this.installedPlugins.forEach((plugin) => {
      plugin.dispose()
    })
  }

  // #endregion
}

GraphView

从上述代码中找到 GraphView,它是画布的视图。现在我们进入它的实现,位于 view.ts 中。

其中 constructor 方法如下,我们为其添加注释:

  constructor(protected readonly graph: Graph) {
    // 执行父级的 `constructor`,生成一个 cid 其值是 "v0"
    // 并且向 `View.views` 中增加了 {v0: this}
    super()

    // 解析自身的 markup 生成 DOM, 并在 selectors 中记录 `selector` 和对应的 DOM
    // 在编辑器中点击下面的 `markup` 就可以找到描述画布的结构对象
    const { selectors, fragment } = Markup.parseJSONMarkup(GraphView.markup)

    this.background = selectors.background as HTMLDivElement
    this.grid = selectors.grid as HTMLDivElement
    this.svg = selectors.svg as SVGSVGElement
    this.defs = selectors.defs as SVGDefsElement
    this.viewport = selectors.viewport as SVGGElement
    this.primer = selectors.primer as SVGGElement
    this.stage = selectors.stage as SVGGElement
    this.decorator = selectors.decorator as SVGGElement
    this.overlay = selectors.overlay as SVGGElement
    this.container = this.options.container

    // 记录一个快照,可以在调用 restore 时还原,前提是不能直接修改子节点
    this.restore = GraphView.snapshoot(this.container)

    // 给 container 添加 x6-graph 的 class
    Dom.addClass(this.container, this.prefixClassName('graph'))
    // 将生成的 DOM 元素挂载到 container 中
    Dom.append(this.container, fragment)

    // 委托事件,让事件和对应的处理方法进行绑定
    this.delegateEvents()
  }

经过初始化,画布被渲染到文档中,并且开始监听鼠标键盘事件。