antv-x6 源码解析(3) - Node 和 Edge 是如何被渲染的?

659 阅读4分钟

前言

目前为止,我们已经大致了解了模型是如何被渲染的,但是也许还有一些疑问,比如 NodeEdge 到底是如何渲染的,因此,我们现在以 NodeEdge 的新增和移除走一遍渲染流程。

Node 的新增

我们通过一个示例来说明,首先创建一个 Graph:

// 此处省略预先准备 container 的步骤
const graph = new Graph({
    container,
    width: 800,
    height: 600,
})

然后,我们创建两个节点并添加到画布:

const node1 = Node.create({
    shape: 'rect',
    x: 80,
    y: 80,
    width: 100,
    height: 100,
});
const node2 = Node.create({
    shape: 'rect',
    x: 280,
    y: 80,
    width: 100,
    height: 100,
})
graph.addNodes([node1, node2]);

根据我们前面对源码的分析,我们可以很容易的分析这整个过程:

  1. 由于我们为节点的属性设置了 shaperect 因此 Node.create 函数从注册表中寻找注册为 rect 的节点,此节点在 shape/rect.ts 中被注册,因此返回了被注册好的节点

  2. 被注册的节点在调用 graph.addNodes 方法时依次经过 graph.addNodes -> graph.addCell -> model.addCell -> collection.add。我们发现它最终会调用 model 上的 collectionadd 方法,我们截取其中的 added 部分:

          added.forEach((cell, i) => {
            const args = {
              cell,
              index: localIndex + i,
              options: localOptions,
            }
            this.trigger('added', args)
            if (!localOptions.dryrun) {
              cell.notify('added', { ...args })
            }
          })
    

    其中的第 7 行 this.trigger('added', args) 会触发 collection 上的 added 事件,在 modelsetup 中监听了该事件:

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

    它没有重新触发事件。

    我们再看第9行 cell.notify('added', { ...args }),它调用了 Cell 上的 notify 方法,如下:

    export class Cell<
      Properties extends Cell.Properties = Cell.Properties,
    > extends Basecoat<Cell.EventArgs> {
      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
      }
    }
    

    如果 cell 上存在 model,那么会触发 model 上的 cell:added 方法,那么此时 cell 上是否存在 model?

    答案是存在,我们注意到 model.addCell 中调用 collection.add 方法是,传递的参数是 this.prepareCell(cell, options),我们可以发现,在 prepareCell 方法中,为 cell 设置了 model 值,也就是 cell 上存在 model

  3. 在上一步中,model 上的 cell:added 方法被触发,在渲染器中的调度器中,它在初始化时监听了 cell:added 事件:

    this.model.on('cell:added', this.onCellAdded, this)
    

    onCellAdded 方法中,调用了 renderViews 方法,它获取 cell 对应的视图。

    当视图不存在时(刚被添加的 cell 当然不存在视图),会调用 createCellView 去创建视图,和之前在调度器那部分一样,在最后会调用视图上的 confirmUpdate 方法进行实际的渲染。

Node 的移除

继续补充上面的代码,我们移除其中一个节点:

graph.removeNode(node1)

我们观察调用链会发现,它依次经过 graph.removeNode -> model.removeCell -> collection.remove -> collection.removeCells 方法 collection.removeCells 方法如下:

  protected removeCells(cells: Cell[], options: Collection.RemoveOptions) {
    const removed = []

    for (let i = 0; i < cells.length; i += 1) {
      const cell = this.get(cells[i])
      if (cell == null) {
        continue
      }

      const index = this.cells.indexOf(cell)
      this.cells.splice(index, 1)
      this.length -= 1
      delete this.map[cell.id]
      removed.push(cell)
      this.unreference(cell)

      if (!options.dryrun) {
        cell.remove()
      }

      if (!options.silent) {
        this.trigger('removed', { cell, index, options })

        if (!options.dryrun) {
          cell.notify('removed', { cell, index, options })
        }
      }
    }

    return removed
  }

我们可以看到,在第22行,它触发了 collection 上的 removed 事件,第25行调用了 Cell 上的 notify 方法,和上面的新增一样,如果 cell 上存在 model,那么会触发 model 上的 cell:removed 方法,那么此时 cell 上是否存在 model?

答案是不存在,我们注意到,在第22行触发了 collection 上的 removed 事件,它在 model 中被监听:

    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 })
      }
    })

然后交给了 onCellRemoved 方法处理,在此方法的最后,它将 cell.model 设置为了 null,也就是说,cell 上不会触发 model 上的 cell:removed 方法,这也是上面监听 removed 事件的处理函数中增加了触发 cell:removed 事件的语句的原因。

model 上的 cell:removed 方法被触发,在渲染器中的调度器中,它在初始化时监听了 cell:removed 事件:

this.model.on('cell:removed', this.onCellRemoved, this)

onCellRemoved 方法中,调用了 removeViews 方法,它向任务队列增加了一个任务,回调函数会调用 removeView, removeView 中会调用视图上的 remove 方法,它会将 Dom 节点移除。

Edge 的新增

我们去掉移除节点的方法,加上新增 Edge 的方法:

graph.addEdge({
    source: node1,
    target: node2,
})

首先,graph.addEdge 会调用 model.addEdge ,由于传入的是元数据,他会调用 createEdge 创建 Edge,我们深入其中发现使用了 Edge.create,再深入,它从注册表中获取了名为 edge 的图形,它被定义在 x6/src/shape/edge.ts 中。回到 model.addEdge,使用默认定义创建边后,调用了 model.addCell,到这里就跟 Node 的新增一致了,不再赘述。

Edge 的移除

graph.removeEdge 直接调用了 model.removeCell 和移除节点一致,这里也不再赘述。