AntV X6原始碼簡析

野林發表於2022-03-06

前言

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等不同方案的選擇也需要針對性考慮,以上。視覺化領域深度與廣度探索起來不僅僅侷限於前端側,希望能夠在這方面能夠系統的學習與實踐,從而探索出在前端領域的一些機會,共勉!!!

參考

相關文章