preface
AntV is the ant gold suit new generation data visualization solutions, including in the fields of X6 is mainly used to solve the graph editor solution, it is a graph editor engine, the built-in editor functions and components, etc., required for the purpose of this paper is to through the brief analysis of X6 source to figure editing some underlying engine for a general idea, At the same time, it also provides some side understanding for the graph editor that needs to be built based on X6 editing engine in the team, so that problems can be found quickly when they encounter problems.
architecture
X6 overall is based on the architecture of MVVM design, external overall exposed Graph class, which Node, Edge, Port and so on have external exposed methods, can be used alone, which provides some dom manipulation methods like Jquery, the overall Graph based on an event base class, Dispose is used to dispose the whole event. Dispose is used to display the instance.
The overall design conforms to the SOLID principle, provides the event mechanism to decouple the publication and subscription, and provides the registration mechanism for the extensibility structure to organize extensibility plug-ins
directory
The overall use of MonorePO 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
- x6
The source code
It can be seen from the architecture level that the overall external exposure is Graph such a big category, so in the process of analyzing the source code call, we seize Graph to gradually expand outward, so as to grasp the whole of a design link, to avoid falling into local can not be removed
Graph
The Graph class provides a summary of all the structures of the whole to expose 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
// Get the container to be loaded
public get container() {
return this.view.container
}
protected get [Symbol.toStringTag]() {
return Graph.toStringTag
}
constructor(options: Partial<GraphOptions.Manual>) {
super(a)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()
}
}
Copy the code
Shape
Implements an intermediate decoupling layer for various types of methods, used to wrap properties, etc
// Shape's base class marks various attributes of shape, such as tags, etc
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}}Copy the code
// Create the shape method
function createShape(
shape: string, config: Node.Config, options: { noText? :booleanignoreMarkup? :booleanparent? : 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
}
Copy the code
Model
Provides Node, Cell, Edge, and Prot processing methods
class Model extends Basecoat<Model.EventArgs> {
public readonly collection: Collection
protected readonly batches: KeyValue<number> = {}
protected readonly addings: WeakMap<Cell, boolean> = new WeakMap(a)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(a)this.collection = new Collection(cells)
this.setup()
}
}
Copy the code
Renderer
Render the data associated with the 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(){}}Copy the code
Store
A common repository for data that interacts 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(a)this.data = {} as D
this.mutate(ObjectExt.cloneDeep(data))
this.changed = {}
}
get() {}
set() {}
remove() {}
clone(){}}Copy the code
View
EdgeView, CellView, etc., using jQuery 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(a)this.cid = Private.uniqueId()
View.views[this.cid] = this}}Copy the code
Geometry
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())
}
}
Copy the code
Registry
Providing 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(){}}Copy the code
Events
Provides a listening (publish subscribe) mechanism for events
class Events<EventArgs extends Events.EventArgs = any> {
private listeners: { [name: string] :any[]} = {}on() {}
once() {}
off() {}
trigger() {}
emit(){}}Copy the code
conclusion
On the whole, we can see that to realize 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 the variation of MVC structure. Therefore, we can comprehensively consider different design schemes in software engineering to deal with the selection of Model layer, View layer and Controller layer. For example, the design of event system and plug-in mechanism, etc. In addition, in the aspect of low-level rendering, after all, as the front-end scheme in the field of graph visualization, the selection of different schemes such as SVG, HTML and Canvas also needs to be targeted. The depth and breadth of visualization field exploration is not limited to the front end, hope to be able to systematically study and practice in this area, so as to explore some opportunities in the front end field, mutual encouragement!!
reference
- X6 website
- Antv/X6< graph editing engine >
- X6 source
- Antvis G6 vs X6 vs Topology source code statistical analysis
- X6 1.0 Sorry to be late
- XFlow 1.0: Professional graph editing application solution
- X6: Deep grinding, improving
- AntV map editing engine X6