4
头图

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! ! !

refer to


维李设论
1.1k 声望4k 粉丝

专注大前端领域发展