view
视图负责对抽象模型的实际渲染。
View
View 是视图的抽象类,定义于 x6/src/view/view.ts。
我们省略一些用于便捷操作的方法,只关注核心部分,如下:
export abstract class View<A extends EventArgs = any> extends Basecoat<A> {
// 视图的唯一 id
public readonly cid: string
// 视图渲染的容器
public container: Element
// 包含 DOM 元素的选择器
protected selectors: Markup.Selectors
// 渲染优先级,值越大越高
public get priority() {
return 2
}
constructor() {
super()
this.cid = Private.uniqueId()
View.views[this.cid] = this
}
// 确认更新,由调度器调用,是渲染的入口,flag 是表示渲染的类型
confirmUpdate(flag: number, options: any): number {
return 0
}
}
View 定义了渲染的入口 confirmUpdate, 继承 View 时需要重写此方法来进行实际的渲染。
CellView
我们首先来看 CellView, CellView 是 Cell 对应的视图。
CellView 定义了 Cell 如何渲染,但实际上 Cell 并不会直接渲染,所以,它只是定义了一个渲染的框架,由实际继承 Cell 的元素实现一个对应的继承自 CellView 的视图来负责渲染。
下面我们对 CellView 进行简化,如下:
export class CellView<
Entity extends Cell = Cell,
Options extends CellView.Options = CellView.Options,
> extends View<CellView.EventArgs> {
protected static defaults: Partial<CellView.Options> = {
isSvgElement: true,
rootSelector: 'root',
priority: 0,
bootstrap: [],
actions: {},
}
public static getDefaults() {
return this.defaults
}
public static config<T extends CellView.Options = CellView.Options>(
options: Partial<T>,
) {
this.defaults = this.getOptions(options)
}
public graph: Graph
// 视图对应的模型
public cell: Entity
// 包含实际 DOM 元素的选择器
protected selectors: Markup.Selectors
// 选项
protected readonly options: Options
// 标记管理器,flag 决定了渲染的类型和渲染的方式
protected readonly flag: FlagManager
// 属性管理器,提供操作 DOM 属性的方法
protected readonly attr: AttrManager
// 缓存
protected readonly cache: Cache
constructor(cell: Entity, options: Partial<Options> = {}) {
super()
this.cell = cell
this.options = this.ensureOptions(options)
this.graph = this.options.graph
this.attr = new AttrManager(this)
this.flag = new FlagManager(
this,
this.options.actions,
this.options.bootstrap,
)
this.cache = new Cache(this)
this.setContainer(this.ensureContainer())
this.setup()
this.init()
}
protected init() {}
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) {
// 请求渲染器调用当前视图的 confirmUpdate 方法进行视图更新
this.graph.renderer.requestViewUpdate(this, flag, options)
}
}
}
CellView 中定义了属性,并且当接收到属性变化完成事件后,请求渲染器进行视图更新,渲染器会在适合的时机调用当前视图的 confirmUpdate 方法,CellView 并没有实现 confirmUpdate 方法,因为需要继承 CellView 的视图根据实际模型渲染不同的结果。
NodeView
NodeView 是 Node 对应的视图,它继承自 CellView。
下面我们对 NodeView 进行简化,如下:
export class NodeView<
Entity extends Node = Node,
Options extends NodeView.Options = NodeView.Options,
> extends CellView<Entity, Options> {
protected portsCache: { [id: string]: NodeView.PortCache } = {}
// 渲染的入口,flag 代表了不同的渲染方式
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
}
// 全量渲染
render() {
// 清空容器
this.empty()
// 首先渲染骨架
this.renderMarkup()
// 设置尺寸
this.resize()
// 设置位置
this.updateTransform()
if (!Config.useCSSSelector) {
// 渲染连接桩
this.renderPorts()
}
// 渲染工具
this.renderTools()
return this
}
...
}
NodeView 实现了 confirmUpdate,这是视图渲染的入口,无论是初始化渲染还是更新都是从这里开始的,flag 参数的值代表了渲染的类型,根据不同的类型调用对应的渲染方法。
这里省略了很多对属性的操作方法,他们最终都会修改 DOM,这里的所说的属性也就是 DOM 元素的属性。
EdgeView
EdgeView 是 Edge 对应的视图,它继承自 CellView。
下面我们对 EdgeView 进行简化,如下:
export class EdgeView<
Entity extends Edge = Edge,
Options extends EdgeView.Options = EdgeView.Options,
> extends CellView<Entity, Options> {
protected readonly POINT_ROUNDING = 2
// 边的路径
public path: Path
// 路由点
public routePoints: Point[]
// 源锚点
public sourceAnchor: Point
// 目标锚点
public targetAnchor: Point
// 源点实际所在的点
public sourcePoint: Point
// 目标点实际所在的点
public targetPoint: Point
// 源点箭头所在的点
public sourceMarkerPoint: Point
// 目标箭头所在的点
public targetMarkerPoint: Point
// 源视图,如果边的源是视图
public sourceView: CellView | null
// 目标视图,如果边的目标是视图
public targetView: CellView | null
// 源 Magnet,通常指连接桩
public sourceMagnet: Element | null
// 目标 Magnet,通常指连接桩
public targetMagnet: Element | null
// 标签容器
protected labelContainer: Element | null
protected labelCache: { [index: number]: Element }
protected labelSelectors: { [index: number]: Markup.Selectors }
// 渲染的入口,flag 代表了不同的渲染方式
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
}
// 全量渲染
render() {
// 清空容器
this.empty()
// 渲染骨架
this.renderMarkup()
// 渲染标签
this.labelContainer = null
this.renderLabels()
// 更新
this.update()
// 渲染工具
this.renderTools()
return this
}
// 更新
update(options: any = {}) {
// 清空缓存
this.cleanCache()
// 更新连接
this.updateConnection(options)
// 更新属性
const attrs = this.cell.getAttrs()
if (attrs != null) {
this.updateAttrs(this.container, attrs, {
selectors: this.selectors,
})
}
// 更新标签位置
this.updateLabelPositions()
// 更新工具
this.updateTools(options)
return this
}
...
}
EdgeView 实现了 confirmUpdate,这是视图渲染的入口,无论是初始化渲染还是更新都是从这里开始的,flag 参数的值代表了渲染的类型,根据不同的类型调用对应的渲染方法。