事件驱动

x6 是通过事件进行驱动的,事件分为以下几类:

  • 鼠标键盘事件

  • 模型变更事件

  • 自定义事件

鼠标键盘事件

鼠标键盘事件的入口在 GraphView

GraphView.events 定义了事件和绑定的方法。它在 GraphView 初始化时作为 delegateEvents 的参数传递给了 View,然后将其绑定在了 graph.container 上。

模型变更事件

Cell 级的事件

首先观察 Cellsetup 方法以及 notify 方法,如下:

export class Cell<
  Properties extends Cell.Properties = Cell.Properties,
> extends Basecoat<Cell.EventArgs> {
  protected setup() {
    this.store.on('change:*', (metadata) => {
      const { key, current, previous, options } = metadata

      this.notify('change:*', {
        key,
        options,
        current,
        previous,
        cell: this,
      })

      this.notify(`change:${key}` as keyof Cell.EventArgs, {
        options,
        current,
        previous,
        cell: this,
      })

      const type = key as Edge.TerminalType
      if (type === 'source' || type === 'target') {
        this.notify(`change:terminal`, {
          type,
          current,
          previous,
          options,
          cell: this,
        })
      }
    })

    this.store.on('changed', ({ options }) =>
      this.notify('changed', { options, cell: this }),
    )
  }

  notify<Key extends keyof Cell.EventArgs>(
    name: Key,
    args: Cell.EventArgs[Key],
  ): this
  notify(name: Exclude<string, keyof Cell.EventArgs>, args: any): this
  notify<Key extends keyof Cell.EventArgs>(
    name: Key,
    args: Cell.EventArgs[Key],
  ) {
    this.trigger(name, args)
    const model = this.model
    if (model) {
      model.notify(`cell:${name}`, args)
      if (this.isNode()) {
        model.notify(`node:${name}`, { ...args, node: this })
      } else if (this.isEdge()) {
        model.notify(`edge:${name}`, { ...args, edge: this })
      }
    }
    return this
  }
}

store 中存储的是 Cell 的 props, attrs 等元数据,当元数据发生变化时,首先会触发 change:* 事件,所有更改完成后会触发 changed 事件。Cell 监听了 storechange:*changed 事件,并将其重新分发为 Cell 上的事件和 Model 上的事件。

Cell 有两种类型:NodeEdge

Node 只在 updatePortData 增加了 ports:addedports:removed 事件

EdgeonLabelsChanged 增加了 labels:addedlabels:removed 事件,在 onVertexsChanged 增加了 vertexs:addedvertexs:removed 事件

Model 级的事件

现在,我们观察 Modelsetup 方法及 notify 方法,如下:

export class Model extends Basecoat<Model.EventArgs> {
  protected setup() {
    const collection = this.collection

    collection.on('sorted', () => this.notify('sorted', null))
    collection.on('updated', (args) => this.notify('updated', args))
    collection.on('cell:change:zIndex', () => this.sortOnChangeZ())

    collection.on('added', ({ cell }) => {
      this.onCellAdded(cell)
    })

    collection.on('removed', (args) => {
      const cell = args.cell
      this.onCellRemoved(cell, args.options)

      // Should trigger remove-event manually after cell was removed.
      this.notify('cell:removed', args)
      if (cell.isNode()) {
        this.notify('node:removed', { ...args, node: cell })
      } else if (cell.isEdge()) {
        this.notify('edge:removed', { ...args, edge: cell })
      }
    })

    collection.on('reseted', (args) => {
      this.onReset(args.current)
      this.notify('reseted', args)
    })

    collection.on('edge:change:source', ({ edge }) =>
      this.onEdgeTerminalChanged(edge, 'source'),
    )

    collection.on('edge:change:target', ({ edge }) => {
      this.onEdgeTerminalChanged(edge, 'target')
    })
  }

  notify<Key extends keyof Model.EventArgs>(
    name: Key,
    args: Model.EventArgs[Key],
  ): this
  notify(name: Exclude<string, keyof Model.EventArgs>, args: any): this
  notify<Key extends keyof Model.EventArgs>(
    name: Key,
    args: Model.EventArgs[Key],
  ) {
    this.trigger(name, args)
    const graph = this.graph
    if (graph) {
      if (name === 'sorted' || name === 'reseted' || name === 'updated') {
        graph.trigger(`model:${name}`, args)
      } else {
        graph.trigger(name, args)
      }
    }
    return this
  }
}

Model 上的 collection 是存储 Cell 的容器,当 collection 中的 Cell 发生变更时(例如,增加,删除,排序等),它会触发事件,Model 监听这些事件并进行重新分发为 Model 上的事件和 Graph 上的事件。

注意,Cell 也调用了 Modelnotify,因此,所有 Cell 上的事件加上前缀后同样也会在 ModelGraph 上出现。

监听事件

  1. Graph 上的功能组件通过监听事件来进行变更

    • BackgroundManager 监听 scaletranslate 事件
    • GridManager 监听 scaletranslate 事件
    • HighlightManager 监听 cell:highlightcell:unhighlight 事件
    • PanningManager 监听 blank:mousedown, node:unhandled:mousedownedge:unhandled:mousedown 事件
    • VirtualRenderManager 监听 translate, scaleresize 事件
  2. 注册表中的内置工具通过监听事件来进行变更,例如 jumpover, stroke, CellEditor, SegmentsVertices

  3. CellView 视图中,观察 CellViewsetup,如下:

    export class CellView<Entity extends Cell = Cell, Options extends CellView.Options = CellView.Options> extends View<CellView.EventArgs> {
        protected setup() {
            this.cell.on('changed', ({ options }) => this.onAttrsChange(options))
        }
        protected onAttrsChange(options: Cell.MutateOptions) {
            let flag = this.flag.getChangedFlag()
            if (options.updated || !flag) {
                return
            }
    
            if (options.dirty && this.hasAction(flag, 'update')) {
                flag |= this.getFlag('render') // eslint-disable-line no-bitwise
            }
    
            // tool changes should be sync render
            if (options.toolId) {
                options.async = false
            }
    
            if (this.graph != null) {
                this.graph.renderer.requestViewUpdate(this, flag, options)
            }
        }
    }
    

    当监听到 Cell 上的 changed 事件时,请求视图更新。

  4. 渲染器的调度器中监听了 reseted, cell:added, cell:removed, cell:change:zIndex, cell:change:visible 事件,然后请求视图更新。