renderer
渲染器是用于渲染 Cell 的。渲染器提供了 requestViewUpdate 方法用于外部调用,以请求视图更新,并且提供了视图的获取方法。
渲染器分为三个部分:
renderer.ts-Renderer渲染器scheduler.ts-Scheduler调度器queueJob.ts-JobQueue任务队列,向任务队列添加任务,任务队列会在页面空闲时执行任务中的回调函数
Renderer
Renderer 仅仅是一个入口,是对调度器的封装,其上的 requestViewUpdate 方法是调用的调度器的 requestViewUpdate 方法,外部可以调用此方法请求视图更新。
Render 的代码非常简单,这里不做过多讲解。
Scheduler
调度器是渲染器的核心,其在内部监听模型上的 cell 增删事件,然后请求视图更新。
Scheduler 简化后定义如下:
export class Scheduler extends Disposable {
public views: KeyValue<Scheduler.View> = {}
public willRemoveViews: KeyValue<Scheduler.View> = {}
protected zPivots: KeyValue<Comment>
private graph: Graph
private renderArea?: Rectangle
private queue: JobQueue
constructor(graph: Graph) {
super()
this.queue = new JobQueue()
this.graph = graph
this.init()
}
protected init() {
this.startListening()
this.renderViews(this.model.getCells())
}
protected startListening() {
this.model.on('reseted', this.onModelReseted, this)
this.model.on('cell:added', this.onCellAdded, this)
this.model.on('cell:removed', this.onCellRemoved, this)
this.model.on('cell:change:zIndex', this.onCellZIndexChanged, this)
this.model.on('cell:change:visible', this.onCellVisibleChanged, this)
}
protected stopListening() {
this.model.off('reseted', this.onModelReseted, this)
this.model.off('cell:added', this.onCellAdded, this)
this.model.off('cell:removed', this.onCellRemoved, this)
this.model.off('cell:change:zIndex', this.onCellZIndexChanged, this)
this.model.off('cell:change:visible', this.onCellVisibleChanged, this)
}
// cell 新增时会调用此方法
protected renderViews(cells: Cell[], options: any = {}) {
cells.sort((c1, c2) => {
if (c1.isNode() && c2.isEdge()) {
return -1
}
return 0
})
cells.forEach((cell) => {
const id = cell.id
const views = this.views
let flag = 0
let viewItem = views[id]
if (viewItem) {
flag = Scheduler.FLAG_INSERT
} else {
// 此处创建 cell 对应的视图
const cellView = this.createCellView(cell)
if (cellView) {
cellView.graph = this.graph
flag = Scheduler.FLAG_INSERT | cellView.getBootstrapFlag()
viewItem = {
view: cellView,
flag,
options,
state: Scheduler.ViewState.CREATED,
}
this.views[id] = viewItem
}
}
if (viewItem) {
// 调用请求视图更新
this.requestViewUpdate(
viewItem.view,
flag,
options,
this.getRenderPriority(viewItem.view),
false,
)
}
})
this.flush()
}
// 最重要的方法,请求视图更新,对模型上的事件的监听函数(例如 `onModelReseted`)最终都是调用此函数
requestViewUpdate(
view: CellView,
flag: number,
options: any = {},
priority: JOB_PRIORITY = JOB_PRIORITY.Update,
flush = true,
) {
const id = view.cell.id
const viewItem = this.views[id]
if (!viewItem) {
return
}
viewItem.flag = flag
viewItem.options = options
const priorAction = view.hasAction(flag, ['translate', 'resize', 'rotate'])
if (view.isNodeView() && priorAction) {
priority = JOB_PRIORITY.PRIOR // eslint-disable-line
flush = false // eslint-disable-line
}
// 向任务队列增加任务,任务队列会在空闲的时候执行 cb 回调
this.queue.queueJob({
id,
priority,
cb: () => {
this.renderViewInArea(view, flag, options)
},
})
// 受影响的边递归调用 requestViewUpdate
const effectedEdges = this.getEffectedEdges(view)
effectedEdges.forEach((edge) => {
this.requestViewUpdate(edge.view, edge.flag, options, priority, false)
})
if (flush) {
this.flush()
}
}
// 渲染处于渲染范围内的视图
protected renderViewInArea(view: CellView, flag: number, options: any = {}) {
const cell = view.cell
const id = cell.id
const viewItem = this.views[id]
if (!viewItem) {
return
}
let result = 0
// 判断是否处于渲染范围内
if (this.isUpdateable(view)) {
// 更新视图
result = this.updateView(view, flag, options)
viewItem.flag = result
} else {
// 已经被渲染了,那么也需要更新
if (viewItem.state === Scheduler.ViewState.MOUNTED) {
result = this.updateView(view, flag, options)
viewItem.flag = result
} else {
// 等待进入渲染范围
viewItem.state = Scheduler.ViewState.WAITTING
}
}
if (result) {
if (
cell.isEdge() &&
(result & view.getFlag(['source', 'target'])) === 0
) {
this.queue.queueJob({
id,
priority: JOB_PRIORITY.RenderEdge,
cb: () => {
this.updateView(view, flag, options)
},
})
}
}
}
// 更新视图
protected updateView(view: View, flag: number, options: any = {}) {
if (view == null) {
return 0
}
if (CellView.isCellView(view)) {
if (flag & Scheduler.FLAG_REMOVE) {
this.removeView(view.cell as any)
return 0
}
if (flag & Scheduler.FLAG_INSERT) {
this.insertView(view)
flag ^= Scheduler.FLAG_INSERT // eslint-disable-line
}
}
if (!flag) {
return 0
}
// 调用视图上的 `confirmUpdate` 进行实际的渲染
return view.confirmUpdate(flag, options)
}
}
Scheduler 在 model 上监听的事件最终是调用 requestViewUpdate 方法。
首先我们关注 renderViews 方法,在节点新增时,此方法会被调用。我们可以看到当 cell.id 对应的视图不存在于缓存中时,会调用 createCellView 创建视图,此方法按照以下优先级创建对应的视图:
options.createCellView如果提供了此选项,那么使用此选项创建cell.view如果在指定了view的名称,那么从CellView.registry注册表中获取- 如果是
node,默认为NodeView - 如果是
edge,默认为EdgeView
创建视图后,最终调用 requestViewUpdate 方法,将创建的视图作为参数传入。
requestViewUpdate 方法向任务队列增加了一个任务,在回调中调用了 renderViewInArea 方法。
renderViewInArea 方法是用于渲染处于渲染范围内的视图,如果处于渲染范围内,它会调用 updateView 方法。
updateView 是更新视图的方法。如果视图被标记删除,那么会执行删除操作;如果视图需要插入,那么会执行插入操作。最后调用视图上的 confirmUpdate 方法进行实际的渲染。