foreword
AntV is a new generation of data visualization solutions for Ant Financial. X6 is mainly used to solve solutions related to the field of graph editing. It is a graph editing engine with built-in functions and components required by the editor. This article It aims to gain a general understanding of some underlying engines in the field of graph editing by briefly analyzing the x6 source code, and also provide some side understanding for the graph editors in the team that need to build based on the X6 editing engine. Find the problem quickly.
Architecture
X6 as a whole is designed based on the MVVM architecture, and exposes Graph classes as a whole. Node, Edge, Port, etc. all have methods exposed to the outside world, which can be used alone. Some DOM operation methods like Jquery are provided. The overall Graph is based on an event base class, which handles the overall processing of events, and uses dispose to display and determine the instance.
The overall design conforms to the SOLID principle , provides an event mechanism for publish-subscribe decoupling, and provides a registration mechanism for the extensible structure to organize extensible plug-ins
content
The overall use of monorepo for source code warehouse management
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
source code
From the architecture level, it can be seen that the overall exposure is a large category such as Graph. Therefore, in the process of analyzing the source code call, we grasp the Graph to expand gradually, so as to grasp the overall design link and avoid falling into partial failure. Leave
Graph
The Graph class provides a summary of all structures as a whole, thus exposing it to the user
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
An intermediate decoupling layer that implements various types of methods, used to wrap properties, etc.
// 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
Provides processing methods for Node, Cell, Edge, Prot, etc.
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
Rendering Model-related data
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
A public storage repository for data, interacting with the 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
Aggregate EdgeView, CellView, etc., using jQuery's related DOM operations
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
Provides operation and processing of geometric figures, including Curve, Ellipse, Line, Point, PolyLine, Rectangle, Angle, etc.
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
provide a mechanism for a 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
Provide event monitoring (publish and subscribe) mechanism
class Events<EventArgs extends Events.EventArgs = any> {
private listeners: { [name: string]: any[] } = {}
on() {}
once() {}
off() {}
trigger() {}
emit() {}
}
Summarize
As a whole, we see that in order to implement a low-level graph editing engine, we need to do a good job in the overall architecture design and deconstruction, which is usually nothing more than a variant of the MVC structure. Therefore, we are in the process of selecting the Model layer, the View layer, and the Controller layer. It can be dealt with comprehensively considering different design schemes in software engineering, such as the design of the event system, the design of the plug-in mechanism, etc. In addition, in terms of underlying rendering, after all, as a front-end scheme in the field of graph visualization, SVG, HTML, Canvas The choice of different programs also needs to be considered, the above. The depth and breadth of the visualization field is not limited to the front-end side. I hope to learn and practice systematically in this area, so as to explore some opportunities in the front-end field. Let’s encourage each other! ! !
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。