- 作者:老汪软件技巧
- 发表时间:2024-08-24 00:03
- 浏览量:
FlagManager
标记管理器是 CellView 的一个重要属性。它用于在 CellView 渲染时,指定渲染的方式。
import { KeyValue } from '@antv/x6-common'
import { CellView } from './cell'
export class FlagManager {
// attr 表示属性,值为活动列表,被以二进制格式存储,每一个二进制位表示一个活动
protected attrs: { [attr: string]: number }
// name 表示活动名,值为活动对应的值,将其转换为二进制后,只有一位是 1 ,即表示此位的活动
protected flags: { [name: string]: number }
// 初次渲染时的活动列表
protected bootstrap: FlagManager.Actions
protected get cell() {
return this.view.cell
}
// actions 是属性名和对应的活动列表,被解析为 attrs ,同时产生 flags
// bootstrap 是启动时的活动
constructor(
protected view: CellView,
actions: KeyValue,
bootstrap: FlagManager.Actions = [],
) {
const flags: { [name: string]: number } = {}
const attrs: { [attr: string]: number } = {}
let shift = 0
Object.keys(actions).forEach((attr) => {
let labels = actions[attr]
if (!Array.isArray(labels)) {
labels = [labels]
}
labels.forEach((label) => {
let flag = flags[label]
if (!flag) {
shift += 1
flag = flags[label] = 1 << shift
}
attrs[attr] |= flag
})
})
let labels = bootstrap
if (!Array.isArray(labels)) {
labels = [labels]
}
labels.forEach((label) => {
if (!flags[label]) {
shift += 1
flags[label] = 1 << shift
}
})
// 26 - 30 are reserved for paper flags
// 31+ overflows maximal number
if (shift > 25) {
throw new Error('Maximum number of flags exceeded.')
}
this.flags = flags
this.attrs = attrs
this.bootstrap = bootstrap
}
// 获取 label 表示的活动的二进制表示即 flag
getFlag(label: FlagManager.Actions) {
const flags = this.flags
if (flags == null) {
return 0
}
if (Array.isArray(label)) {
return label.reduce((memo, key) => memo | flags[key], 0)
}
return flags[label] | 0
}
// 判断 flag 中是否存在 label 对应的活动
hasAction(flag: number, label: FlagManager.Actions) {
return flag & this.getFlag(label)
}
// 移除 flag 中的 label 对应的活动
removeAction(flag: number, label: FlagManager.Actions) {
return flag ^ (flag & this.getFlag(label))
}
// 获取启动活动对应的 flag
getBootstrapFlag() {
return this.getFlag(this.bootstrap)
}
// 获取 cell 中变化的属性对应的 flag
getChangedFlag() {
let flag = 0
if (!this.attrs) {
return flag
}
Object.keys(this.attrs).forEach((attr) => {
if (this.cell.hasChanged(attr)) {
flag |= this.attrs[attr]
}
})
return flag
}
}
export namespace FlagManager {
// 所有可能的活动
export type Action =
| 'render'
| 'update'
| 'resize'
| 'scale'
| 'rotate'
| 'translate'
| 'ports'
| 'tools'
| 'source'
| 'target'
| 'vertices'
| 'labels'
export type Actions = Action | Action[]
}
attrs 表示属性对应的活动,它的值是数字,将其转换为二进制后,每一位表示一个活动,具体每位表示哪个活动,由 flags 决定,flags 的 key 是活动,值是活动对应的值。bootstrap 是启动时的活动。
直接理解可能较为困难,我们结合一个示例来理解,在 CellView 的 constructor 中,flag 被初始化,如下:
this.flag = new FlagManager(
this,
this.options.actions,
this.options.bootstrap,
)
如果其中的 options.actions 为:
{
"view": ["render"],
"markup": ["render"],
"attrs": ["update"],
"size": ["resize", "ports", "tools"],
"angle": ["rotate", "tools"],
"position": ["translate", "tools"],
"ports": ["ports"],
"tools": ["tools"]
}
那么,经过初始化后,flag.attrs 为:
{
"view": 2,
"markup": 2,
"attrs": 4,
"size": 56, // 8 + 16 + 32
"angle": 96, // 64 + 32
"position": 160, // 128 + 32
"ports": 16,
"tools": 32
}
flag.flags 为:
{
"render": 2,
"update": 4,
"resize": 8,
"ports": 16,
"tools": 32,
"rotate": 64,
"translate": 128
}
作用
在视图章节,我们知道了 confirmUpdate 是视图渲染的入口函数。它的第一个参数就是 flag, 视图根据 flag 中的活动来判断到底如何渲染。
NodeView
例如,在 NodeView 中,confirmUpdate 定义如下:
confirmUpdate(flag: number, options: any = {}) {
let ret = flag
if (this.hasAction(ret, 'ports')) {
this.removePorts()
this.cleanPortsCache()
}
if (this.hasAction(ret, 'render')) {
this.render()
ret = this.removeAction(ret, [
'render',
'update',
'resize',
'translate',
'rotate',
'ports',
'tools',
])
} else {
ret = this.handleAction(
ret,
'resize',
() => this.resize(),
'update', // Resize method is calling `update()` internally
)
ret = this.handleAction(
ret,
'update',
() => this.update(),
// `update()` will render ports when useCSSSelectors are enabled
Config.useCSSSelector ? 'ports' : null,
)
ret = this.handleAction(ret, 'translate', () => this.translate())
ret = this.handleAction(ret, 'rotate', () => this.rotate())
ret = this.handleAction(ret, 'ports', () => this.renderPorts())
ret = this.handleAction(ret, 'tools', () => {
if (this.getFlag('tools') === flag) {
this.renderTools()
} else {
this.updateTools(options)
}
})
}
return ret
}
flag 中存在 ports 时,先移除所有 ports,然后根据 flag 中是否存在 render 来决定是调用 render 函数全量渲染,还是调用各种对应的函数来更新。
EdgeView
EdgeView 中的 confirmUpdate 定义如下:
confirmUpdate(flag: number, options: any = {}) {
let ref = flag
if (this.hasAction(ref, 'source')) {
if (!this.updateTerminalProperties('source')) {
return ref
}
ref = this.removeAction(ref, 'source')
}
if (this.hasAction(ref, 'target')) {
if (!this.updateTerminalProperties('target')) {
return ref
}
ref = this.removeAction(ref, 'target')
}
const graph = this.graph
const sourceView = this.sourceView
const targetView = this.targetView
if (
graph &&
((sourceView && !graph.renderer.isViewMounted(sourceView)) ||
(targetView && !graph.renderer.isViewMounted(targetView)))
) {
// Wait for the sourceView and targetView to be rendered.
return ref
}
if (this.hasAction(ref, 'render')) {
this.render()
ref = this.removeAction(ref, ['render', 'update', 'labels', 'tools'])
return ref
}
ref = this.handleAction(ref, 'update', () => this.update(options))
ref = this.handleAction(ref, 'labels', () => this.onLabelsChange(options))
ref = this.handleAction(ref, 'tools', () => this.renderTools())
return ref
}
首先 flag 中存在 source 时更新 source,flag 中存在 target 时更新 target,然后同样是根据 flag 中是否存在 render 来决定是调用 render 函数全量渲染,还是调用各种对应的函数来更新。