前言
AntV是螞蟻金服全新一代資料視覺化解決方案,其中X6主要用於解決圖編輯領域相關的解決方案,其是一款圖編輯引擎,內建了一下編輯器所需的功能及元件等,本文旨在通過簡要分析x6原始碼來對圖編輯領域的一些底層引擎進行一個大致瞭解,同時也為團隊中需要進行基於X6編輯引擎進行構建的圖編輯器提供一些側面瞭解,在碰到問題時可以較快的找到問題點。
架構
X6整體是基於MVVM的架構進行設計的,對外整體暴露Graph的類,其中的Node、Edge、Port等都有對外暴露的方法,可以單獨使用,其中提供了類Jquery的一些dom操作方法,整體的Graph基於了一個事件基類,對事件進行的整體的處理,其中使用了dispose來對例項進行顯示判定。
整體設計符合SOLID原則,提供事件機制進行釋出訂閱解耦,對於擴充套件性結構則提供序號產生器制,進行擴充套件性外掛組織
目錄
整體採用monorepo進行原始碼的倉庫管理
packages
x6
- addon
- common
- geometry
- global
- graph
- layout
- model
- registry
- shape
- style
- types
- util
- view
- x6-angular-shape
x6-geometry
- angle
- curve
- ellipse
- line
- point
- polyline
- rectangle
- x6-react
- x6-react-components
- x6-react-shape
- x6-vector
- x6-vue-shape
原始碼
從架構層次可以看出,整體對外暴露的就是Graph這麼一個大類,因而在分析原始碼呼叫過程中,我們抓住Graph進行逐步的往外擴充,從而把握整體的一個設計鏈路,避免陷入區域性無法抽離
Graph
Graph類提供了整體所有結構的彙總,從而暴露給使用者
class Graph extends Basecoat<EventArgs> {
public readonly options: GraphOptions.Definition
public readonly css: CSSManager
public readonly model: Model
public readonly view: GraphView
public readonly hook: HookManager
public readonly grid: Grid
public readonly defs: Defs
public readonly knob: Knob
public readonly coord: Coord
public readonly renderer: ViewRenderer
public readonly snapline: Snapline
public readonly highlight: Highlight
public readonly transform: Transform
public readonly clipboard: Clipboard
public readonly selection: Selection
public readonly background: Background
public readonly history: History
public readonly scroller: Scroller
public readonly minimap: MiniMap
public readonly keyboard: Shortcut
public readonly mousewheel: Wheel
public readonly panning: Panning
public readonly print: Print
public readonly format: Format
public readonly size: SizeManager
// 拿到需要載入的container
public get container() {
return this.view.container
}
protected get [Symbol.toStringTag]() {
return Graph.toStringTag
}
constructor(options: Partial<GraphOptions.Manual>) {
super()
this.options = GraphOptions.get(options)
this.css = new CSSManager(this)
this.hook = new HookManager(this)
this.view = this.hook.createView()
this.defs = this.hook.createDefsManager()
this.coord = this.hook.createCoordManager()
this.transform = this.hook.createTransformManager()
this.knob = this.hook.createKnobManager()
this.highlight = this.hook.createHighlightManager()
this.grid = this.hook.createGridManager()
this.background = this.hook.createBackgroundManager()
this.model = this.hook.createModel()
this.renderer = this.hook.createRenderer()
this.clipboard = this.hook.createClipboardManager()
this.snapline = this.hook.createSnaplineManager()
this.selection = this.hook.createSelectionManager()
this.history = this.hook.createHistoryManager()
this.scroller = this.hook.createScrollerManager()
this.minimap = this.hook.createMiniMapManager()
this.keyboard = this.hook.createKeyboard()
this.mousewheel = this.hook.createMouseWheel()
this.print = this.hook.createPrintManager()
this.format = this.hook.createFormatManager()
this.panning = this.hook.createPanningManager()
this.size = this.hook.createSizeManager()
}
}
Shape
實現各種型別方法的中間解耦層,用於包裹屬性等
// shape的基類,標記shape的各種屬性,如標籤等
class Base<
Properties extends Node.Properties = Node.Properties,
> extends Node<Properties> {
get label() {
return this.getLabel()
}
set label(val: string | undefined | null) {
this.setLabel(val)
}
getLabel() {
return this.getAttrByPath<string>('text/text')
}
setLabel(label?: string | null, options?: Node.SetOptions) {
if (label == null) {
this.removeLabel()
} else {
this.setAttrByPath('text/text', label, options)
}
return this
}
removeLabel() {
this.removeAttrByPath('text/text')
return this
}
}
// 建立shape的方法
function createShape(
shape: string,
config: Node.Config,
options: {
noText?: boolean
ignoreMarkup?: boolean
parent?: Node.Definition | typeof Base
} = {},
) {
const name = getName(shape)
const defaults: Node.Config = {
constructorName: name,
attrs: {
'.': {
fill: '#ffffff',
stroke: 'none',
},
[shape]: {
fill: '#ffffff',
stroke: '#000000',
},
},
}
if (!options.ignoreMarkup) {
defaults.markup = getMarkup(shape, options.noText === true)
}
const base = options.parent || Base
return base.define(
ObjectExt.merge(defaults, config, { shape: name }),
) as typeof Base
}
Model
提供了Node、Cell、Edge、Prot等的處理方法
class Model extends Basecoat<Model.EventArgs> {
public readonly collection: Collection
protected readonly batches: KeyValue<number> = {}
protected readonly addings: WeakMap<Cell, boolean> = new WeakMap()
public graph: Graph | null
protected nodes: KeyValue<boolean> = {}
protected edges: KeyValue<boolean> = {}
protected outgoings: KeyValue<string[]> = {}
protected incomings: KeyValue<string[]> = {}
protected get [Symbol.toStringTag]() {
return Model.toStringTag
}
constructor(cells: Cell[] = []) {
super()
this.collection = new Collection(cells)
this.setup()
}
}
Renderer
渲染Model相關的資料
class Renderer extends Base {
protected views: KeyValue<CellView>
protected zPivots: KeyValue<Comment>
protected updates: Renderer.Updates
protected init() {}
protected startListening() {}
protected stopListening() {}
protected resetUpdates() {}
protected onSortModel() {}
protected onModelReseted() {}
protected onBatchStop() {}
protected onCellAdded() {}
protected onCellRemove() {}
protected onCellZIndexChanged() {}
protected onCellVisibleChanged() {}
protected processEdgeOnTerminalVisibleChanged() {}
protected isEdgeTerminalVisible() {}
}
Store
資料的公共儲存倉庫,與renderer進行互動
class Store<D> extends Basecoat<Store.EventArgs<D>>{
protected data: D
protected previous: D
protected changed: Partial<D>
protected pending = false
protected changing = false
protected pendingOptions: Store.MutateOptions | null
protected mutate<K extends keyof D>() {}
constructor(data: Partial<D> = {}) {
super()
this.data = {} as D
this.mutate(ObjectExt.cloneDeep(data))
this.changed = {}
}
get() {}
set() {}
remove() {}
clone() {}
}
View
聚合EdgeView、CellView等,使用了jQuery的相關DOM操作
abstract class View<EventArgs = any> extends Basecoat<EventArgs> {
public readonly cid: string
public container: Element
protected selectors: Markup.Selectors
public get priority() {
return 2
}
constructor() {
super()
this.cid = Private.uniqueId()
View.views[this.cid] = this
}
}
Geometry
提供幾何圖形的操作處理,包括Curve、Ellipse、Line、Point、PolyLine、Rectangle、Angle等
abstract class Geometry {
abstract scale(
sx: number,
sy: number,
origin?: Point.PointLike | Point.PointData,
): this
abstract rotate(
angle: number,
origin?: Point.PointLike | Point.PointData,
): this
abstract translate(tx: number, ty: number): this
abstract translate(p: Point.PointLike | Point.PointData): this
abstract equals(g: any): boolean
abstract clone(): Geometry
abstract toJSON(): JSONObject | JSONArray
abstract serialize(): string
valueOf() {
return this.toJSON()
}
toString() {
return JSON.stringify(this.toJSON())
}
}
Registry
提供註冊中心的機制,
class Registry<
Entity,
Presets = KeyValue<Entity>,
OptionalType = never,
> {
public readonly data: KeyValue<Entity>
public readonly options: Registry.Options<Entity | OptionalType>
constructor(options: Registry.Options<Entity | OptionalType>) {
this.options = { ...options }
this.data = (this.options.data as KeyValue<Entity>) || {}
this.register = this.register.bind(this)
this.unregister = this.unregister.bind(this)
}
get names() {
return Object.keys(this.data)
}
register() {}
unregister() {}
get() {}
exist() {}
}
Events
提供事件的監聽(釋出訂閱)機制
class Events<EventArgs extends Events.EventArgs = any> {
private listeners: { [name: string]: any[] } = {}
on() {}
once() {}
off() {}
trigger() {}
emit() {}
}
總結
整體我們看到,要想實現一款底層的圖編輯引擎,需要做好整體的架構設計及解構,通常不外乎MVC的結構的變種,因而我們在選擇Model層、View層、Controller層的過程中,可以綜合考慮軟體工程中不同的設計方案來處理,比如對事件系統的設計、外掛機制的設計等等,另外在底層渲染方面,畢竟作為圖視覺化領域的前端方案,對SVG、HTML、Canvas等不同方案的選擇也需要針對性考慮,以上。視覺化領域深度與廣度探索起來不僅僅侷限於前端側,希望能夠在這方面能夠系統的學習與實踐,從而探索出在前端領域的一些機會,共勉!!!