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

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