AntV X6源码简析

前端 | AntV X6源码探究简析.png

前言

AntV是蚂蚁金服全新一代数据可视化解决方案,其中X6主要用于解决图编辑领域相关的解决方案,其是一款图编辑引擎,内置了一下编辑器所需的功能及组件等,本文旨在通过简要分析x6源码来对图编辑领域的一些底层引擎进行一个大致了解,同时也为团队中需要进行基于X6编辑引擎进行构建的图编辑器提供一些侧面了解,在碰到问题时可以较快的找到问题点。

架构

图片
图片

X6整体是基于MVVM的架构进行设计的,对外整体暴露Graph的类,其中的Node、Edge、Port等都有对外暴露的方法,可以单独使用,其中提供了类Jquery的一些dom操作方法,整体的Graph基于了一个事件基类,对事件进行的整体的处理,其中使用了dispose来对实例进行显示判定。

[图片上传失败...(image-f783dc-1646573655232)]

图片

整体设计符合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等不同方案的选择也需要针对性考虑,以上。可视化领域深度与广度探索起来不仅仅局限于前端侧,希望能够在这方面能够系统的学习与实践,从而探索出在前端领域的一些机会,共勉!!!

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容