Graph
首先关注 x6/src/graph/ 目录,其中 graph.ts 是核心,它定义了画布,承载所有其他功能。其中的 base.ts 是此目录除 view.ts 外所有功能共同的父类。
为了方便理解,我们目前仅关心核心实现,那么此目录就只需要关注 graph.ts 和 view.ts。
对于 graph.ts,我们观察到 Graph 的大部分都是都其属性功能的封装,我们剔除这些部分后,得到有效代码如下:
export class Graph extends Basecoat<EventArgs> {
private installedPlugins: Set<Graph.Plugin> = new Set()
public model: Model
public readonly options: GraphOptions.Definition
public readonly css: Css
public readonly view: GraphView
public readonly grid: Grid
public readonly defs: Defs
public readonly coord: Coord
public readonly renderer: ViewRenderer
public readonly highlight: Highlight
public readonly transform: Transform
public readonly background: Background
public readonly panning: Panning
public readonly mousewheel: Wheel
public readonly virtualRender: VirtualRender
public readonly size: Size
constructor(options: Partial<GraphOptions.Manual>) {
super()
this.options = GraphOptions.get(options)
this.css = new Css(this)
this.view = new GraphView(this)
this.defs = new Defs(this)
this.coord = new Coord(this)
this.transform = new Transform(this)
this.highlight = new Highlight(this)
this.grid = new Grid(this)
this.background = new Background(this)
if (this.options.model) {
this.model = this.options.model
} else {
this.model = new Model()
this.model.graph = this
}
this.renderer = new ViewRenderer(this)
this.panning = new Panning(this)
this.mousewheel = new Wheel(this)
this.virtualRender = new VirtualRender(this)
this.size = new Size(this)
}
// #region plugin
use(plugin: Graph.Plugin, ...options: any[]) {
if (!this.installedPlugins.has(plugin)) {
this.installedPlugins.add(plugin)
plugin.init(this, ...options)
}
return this
}
getPlugin<T extends Graph.Plugin>(pluginName: string): T | undefined {
return Array.from(this.installedPlugins).find(
(plugin) => plugin.name === pluginName,
) as T
}
getPlugins<T extends Graph.Plugin[]>(pluginName: string[]): T | undefined {
return Array.from(this.installedPlugins).filter((plugin) =>
pluginName.includes(plugin.name),
) as T
}
enablePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin?.enable?.()
})
return this
}
disablePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin?.disable?.()
})
return this
}
isPluginEnabled(pluginName: string) {
const pluginIns = this.getPlugin(pluginName)
return pluginIns?.isEnabled?.()
}
disposePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin.dispose()
})
return this
}
// #endregion
// #region dispose
@Basecoat.dispose()
dispose() {
this.clearCells()
this.off()
this.css.dispose()
this.defs.dispose()
this.grid.dispose()
this.coord.dispose()
this.transform.dispose()
this.highlight.dispose()
this.background.dispose()
this.mousewheel.dispose()
this.panning.dispose()
this.view.dispose()
this.renderer.dispose()
this.installedPlugins.forEach((plugin) => {
plugin.dispose()
})
}
// #endregion
}
GraphView
从上述代码中找到 GraphView,它是画布的视图。现在我们进入它的实现,位于 view.ts 中。
其中 constructor 方法如下,我们为其添加注释:
constructor(protected readonly graph: Graph) {
// 执行父级的 `constructor`,生成一个 cid 其值是 "v0"
// 并且向 `View.views` 中增加了 {v0: this}
super()
// 解析自身的 markup 生成 DOM, 并在 selectors 中记录 `selector` 和对应的 DOM
// 在编辑器中点击下面的 `markup` 就可以找到描述画布的结构对象
const { selectors, fragment } = Markup.parseJSONMarkup(GraphView.markup)
this.background = selectors.background as HTMLDivElement
this.grid = selectors.grid as HTMLDivElement
this.svg = selectors.svg as SVGSVGElement
this.defs = selectors.defs as SVGDefsElement
this.viewport = selectors.viewport as SVGGElement
this.primer = selectors.primer as SVGGElement
this.stage = selectors.stage as SVGGElement
this.decorator = selectors.decorator as SVGGElement
this.overlay = selectors.overlay as SVGGElement
this.container = this.options.container
// 记录一个快照,可以在调用 restore 时还原,前提是不能直接修改子节点
this.restore = GraphView.snapshoot(this.container)
// 给 container 添加 x6-graph 的 class
Dom.addClass(this.container, this.prefixClassName('graph'))
// 将生成的 DOM 元素挂载到 container 中
Dom.append(this.container, fragment)
// 委托事件,让事件和对应的处理方法进行绑定
this.delegateEvents()
}
经过初始化,画布被渲染到文档中,并且开始监听鼠标键盘事件。