model
模型是画布内图像的抽象表示。
Model
Model 是由画布内的元素构成的抽象模型,其基本单位是 Cell。
省略部分方法及其实现,简化后的 Model 定义如下:
export class Model extends Basecoat<Model.EventArgs> {
// 所有的 cell 存储在其中,并提供一些便捷的操作方法
public readonly collection: Collection
// 批处理计数器
protected readonly batches: KeyValue<number> = {}
// 批量新增时,正在被添加的节点
protected readonly addings: WeakMap<Cell, boolean> = new WeakMap()
public graph: Graph
// 模型中的节点 id
protected nodes: KeyValue<boolean> = {}
// 模型中的边 id
protected edges: KeyValue<boolean> = {}
// 从指定节点出发的边的 id 列表
protected outgoings: KeyValue<string[]> = {}
// 指向指定节点的边的 id 列表
protected incomings: KeyValue<string[]> = {}
constructor(cells: Cell[] = []) {
super()
this.collection = new Collection(cells)
this.setup()
}
/** 发出通知,触发 Model 上的事件以及触发 Graph 上的事件 */
notify<Key extends keyof Model.EventArgs>(
name: Key,
args: Model.EventArgs[Key],
) {...}
/** 监听 collection 属性上的事件,进行进一步的事件分发或触发对应处理方法 */
protected setup() {...}
// 其他增删查改方法
}
在 Model 中,nodes, edges, outgoings 和 incomings 抽象的描述了此模型。所有的增删改都需要维持模型的稳定,例如 outgoings 属性和 incomings 属性,在节点或边增加或删除后,仍然有效。
在 Model 模型中只有两种元素,即 Node 和 Edge,它们都是基于 Cell 的扩展。下面我们来看 Cell。
Cell
Cell 是所有元素的基类。它定义了画布中的元素的基本属性。
同样的,我们看一个简化后的 Cell 定义:
export class Cell<
Properties extends Cell.Properties = Cell.Properties,
> extends Basecoat<Cell.EventArgs> {
protected static markup: Markup
protected static defaults: Cell.Defaults = {}
protected static attrHooks: Attr.Definitions = {}
protected static propHooks: Cell.PropHook[] = []
// 配置 markup, defaults, attrHooks 和 propHooks
public static config<C extends Cell.Config = Cell.Config>(presets: C) {...}
public readonly id: string
// 存储处理后的 metadata, 包括 props, attr 等,以及提供便捷的增删改查方法
protected readonly store: Store<Cell.Properties>
// 动画相关
protected readonly animation: Animation
constructor(metadata: Cell.Metadata = {}) {
super()
const ctor = this.constructor as typeof Cell
const defaults = ctor.getDefaults(true)
const props = ObjectExt.merge(
{},
this.preprocess(defaults),
this.preprocess(metadata),
)
this.id = props.id || StringExt.uuid()
this.store = new Store(props)
this.animation = new Animation(this)
this.setup()
this.init()
this.postprocess(metadata)
}
/** 预留的初始化方法 */
init() {}
/** 预处理方法,对 metadata 应用 propHooks,增加 id 属性 */
protected preprocess(
metadata: Cell.Metadata,
ignoreIdCheck?: boolean,
): Properties {
const id = metadata.id
const ctor = this.constructor as typeof Cell
const props = ctor.applyPropHooks(this, metadata)
if (id == null && ignoreIdCheck !== true) {
props.id = StringExt.uuid()
}
return props as Properties
}
/** 预留的后处理方法 */
protected postprocess(metadata: Cell.Metadata) {} // eslint-disable-line
/** 监听 store 上的 change 和 changed 事件并进行分发,触发实例的事件 */
protected setup() {...}
/** 发出通知,触发模型上的事件 */
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 和 animation 提供操作的方法
}
Cell 中静态属性 markup,只能通过静态方法 config 进行设置。因此,Cell 定义了: 不能直接使用 new 进行创建,在创建前必须先调用 config 进行配置。
对 Cell 的操作就是对其上存储的 store 中的数据进行操作,所有操作都会发出事件,以便后续渲染器对操作进行渲染。
Node
Node 是模型中的节点,继承自 Cell,它相比 Cell 增加了 角度(angle),位置(position),尺寸(size),连接桩(port)属性,以及与这些属性相关的方法,下面,我们看它简化后的定义:
export class Node<
Properties extends Node.Properties = Node.Properties,
> extends Cell<Properties> {
protected static defaults: Node.Defaults = {
angle: 0,
position: { x: 0, y: 0 },
size: { width: 1, height: 1 },
}
protected readonly store: Store<Node.Properties>
protected port: PortManager
constructor(metadata: Node.Metadata = {}) {
super(metadata)
this.initPorts()
}
/** 对 metadata 中的 x,y,width,height,统一为 position 和 size */
protected preprocess(
metadata: Node.Metadata,
ignoreIdCheck?: boolean,
): Properties {...}
// 对属性的操作方法
}
与 Cell 相同,所有数据都存储在 store 中,Node 提供的所有操作方法本质上都是对 store 中的数据进行增删查改的封装。
Edge
Edge 是模型中用于连接 Node 的有向线条,被称为边。Edge 也是继承自 Cell,它相比 Cell 增加了 source 和 target 属性以及与这些属性相关的方法,下面,我们看它简化后的定义:
export class Edge<
Properties extends Edge.Properties = Edge.Properties,
> extends Cell<Properties> {
protected static defaults: Edge.Defaults = {}
protected readonly store: Store<Edge.Properties>
constructor(metadata: Edge.Metadata = {}) {
super(metadata)
}
/** 对 metadata 中的 source 和 target 的不同表示统一为同一种表示 */
protected preprocess(metadata: Edge.Metadata, ignoreIdCheck?: boolean) {...}
protected setup() {
super.setup()
this.on('change:labels', (args) => this.onLabelsChanged(args))
this.on('change:vertices', (args) => this.onVertexsChanged(args))
}
// 对属性的操作方法
}
Edge 的 source 和 target 分别表示边的两个端点,也是存储在 store 中,Edge 主要提供了修改这两个属性的方法。