@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
549 lines (474 loc) • 17.3 kB
text/typescript
import { ArrayExt } from '../util'
import { Basecoat } from '../common'
import { Cell } from './cell'
import { Node } from './node'
import { Edge } from './edge'
export class Collection extends Basecoat<Collection.EventArgs> {
public length = 0
public comparator: Collection.Comparator | null
private cells: Cell[]
private map: { [id: string]: Cell }
constructor(cells: Cell | Cell[], options: Collection.Options = {}) {
super()
this.comparator = options.comparator || 'zIndex'
this.clean()
if (cells) {
this.reset(cells, { silent: true })
}
}
toJSON() {
return this.cells.map((cell) => cell.toJSON())
}
add(cells: Cell | Cell[], options?: Collection.AddOptions): this
add(
cells: Cell | Cell[],
index: number,
options?: Collection.AddOptions,
): this
add(
cells: Cell | Cell[],
index?: number | Collection.AddOptions,
options?: Collection.AddOptions,
) {
let localIndex: number
let localOptions: Collection.AddOptions
if (typeof index === 'number') {
localIndex = index
localOptions = { merge: false, ...options }
} else {
localIndex = this.length
localOptions = { merge: false, ...index }
}
if (localIndex > this.length) {
localIndex = this.length
}
if (localIndex < 0) {
localIndex += this.length + 1
}
const entities = Array.isArray(cells) ? cells : [cells]
const sortable =
this.comparator &&
typeof index !== 'number' &&
localOptions.sort !== false
const sortAttr = this.comparator || null
let sort = false
const added: Cell[] = []
const merged: Cell[] = []
entities.forEach((cell) => {
const existing = this.get(cell)
if (existing) {
if (localOptions.merge && !cell.isSameStore(existing)) {
existing.setProp(cell.getProp(), options) // merge
merged.push(existing)
if (sortable && !sort) {
if (sortAttr == null || typeof sortAttr === 'function') {
sort = existing.hasChanged()
} else if (typeof sortAttr === 'string') {
sort = existing.hasChanged(sortAttr)
} else {
sort = sortAttr.some((key) => existing.hasChanged(key))
}
}
}
} else {
added.push(cell)
this.reference(cell)
}
})
if (added.length) {
if (sortable) {
sort = true
}
this.cells.splice(localIndex, 0, ...added)
this.length = this.cells.length
}
if (sort) {
this.sort({ silent: true })
}
if (!localOptions.silent) {
added.forEach((cell, i) => {
const args = {
cell,
index: localIndex + i,
options: localOptions,
}
this.trigger('added', args)
if (!localOptions.dryrun) {
cell.notify('added', { ...args })
}
})
if (sort) {
this.trigger('sorted')
}
if (added.length || merged.length) {
this.trigger('updated', {
added,
merged,
removed: [],
options: localOptions,
})
}
}
return this
}
remove(cell: Cell, options?: Collection.RemoveOptions): Cell
remove(cells: Cell[], options?: Collection.RemoveOptions): Cell[]
remove(cells: Cell | Cell[], options: Collection.RemoveOptions = {}) {
const arr = Array.isArray(cells) ? cells : [cells]
const removed = this.removeCells(arr, options)
if (!options.silent && removed.length > 0) {
this.trigger('updated', {
options,
removed,
added: [],
merged: [],
})
}
return Array.isArray(cells) ? removed : removed[0]
}
protected removeCells(cells: Cell[], options: Collection.RemoveOptions) {
const removed = []
for (let i = 0; i < cells.length; i += 1) {
const cell = this.get(cells[i])
if (cell == null) {
continue
}
const index = this.cells.indexOf(cell)
this.cells.splice(index, 1)
this.length -= 1
delete this.map[cell.id]
removed.push(cell)
this.unreference(cell)
if (!options.dryrun) {
cell.remove()
}
if (!options.silent) {
this.trigger('removed', { cell, index, options })
if (!options.dryrun) {
cell.notify('removed', { cell, index, options })
}
}
}
return removed
}
reset(cells: Cell | Cell[], options: Collection.SetOptions = {}) {
const previous = this.cells.slice()
previous.forEach((cell) => this.unreference(cell))
this.clean()
this.add(cells, { silent: true, ...options })
if (!options.silent) {
const current = this.cells.slice()
this.trigger('reseted', {
options,
previous,
current,
})
const added: Cell[] = []
const removed: Cell[] = []
current.forEach((a) => {
const exist = previous.some((b) => b.id === a.id)
if (!exist) {
added.push(a)
}
})
previous.forEach((a) => {
const exist = current.some((b) => b.id === a.id)
if (!exist) {
removed.push(a)
}
})
this.trigger('updated', { options, added, removed, merged: [] })
}
return this
}
push(cell: Cell, options?: Collection.SetOptions) {
return this.add(cell, this.length, options)
}
pop(options?: Collection.SetOptions) {
const cell = this.at(this.length - 1)!
return this.remove(cell, options)
}
unshift(cell: Cell, options?: Collection.SetOptions) {
return this.add(cell, 0, options)
}
shift(options?: Collection.SetOptions) {
const cell = this.at(0)!
return this.remove(cell, options)
}
get(cell?: string | number | Cell | null): Cell | null {
if (cell == null) {
return null
}
const id =
typeof cell === 'string' || typeof cell === 'number' ? cell : cell.id
return this.map[id] || null
}
has(cell: string | Cell): boolean {
return this.get(cell as any) != null
}
at(index: number): Cell | null {
if (index < 0) {
index += this.length // eslint-disable-line
}
return this.cells[index] || null
}
first() {
return this.at(0)
}
last() {
return this.at(-1)
}
indexOf(cell: Cell) {
return this.cells.indexOf(cell)
}
toArray() {
return this.cells.slice()
}
sort(options: Collection.SetOptions = {}) {
if (this.comparator != null) {
this.cells = ArrayExt.sortBy(this.cells, this.comparator)
if (!options.silent) {
this.trigger('sorted')
}
}
return this
}
clone() {
const constructor = this.constructor as any
return new constructor(this.cells.slice(), {
comparator: this.comparator,
}) as Collection
}
protected reference(cell: Cell) {
this.map[cell.id] = cell
cell.on('*', this.notifyCellEvent, this)
}
protected unreference(cell: Cell) {
cell.off('*', this.notifyCellEvent, this)
delete this.map[cell.id]
}
protected notifyCellEvent<K extends keyof Cell.EventArgs>(
name: K,
args: Cell.EventArgs[K],
) {
const cell = args.cell
this.trigger(`cell:${name}`, args)
if (cell) {
if (cell.isNode()) {
this.trigger(`node:${name}`, { ...args, node: cell })
} else if (cell.isEdge()) {
this.trigger(`edge:${name}`, { ...args, edge: cell })
}
}
}
protected clean() {
this.length = 0
this.cells = []
this.map = {}
}
}
export namespace Collection {
export type Comparator = string | string[] | ((cell: Cell) => number)
export interface Options {
comparator?: Comparator
}
export interface SetOptions extends Cell.SetOptions {}
export interface RemoveOptions extends Cell.SetOptions {
/**
* The default is to remove all the associated links.
* Set `disconnectEdges` option to `true` to disconnect edges
* when a cell is removed.
*/
disconnectEdges?: boolean
dryrun?: boolean
}
export interface AddOptions extends SetOptions {
sort?: boolean
merge?: boolean
dryrun?: boolean
}
}
export namespace Collection {
export interface EventArgs
extends CellEventArgs,
NodeEventArgs,
EdgeEventArgs {
sorted?: null
reseted: {
current: Cell[]
previous: Cell[]
options: SetOptions
}
updated: {
added: Cell[]
merged: Cell[]
removed: Cell[]
options: SetOptions
}
added: {
cell: Cell
index: number
options: AddOptions
}
removed: {
cell: Cell
index: number
options: RemoveOptions
}
}
interface NodeEventCommonArgs {
node: Node
}
interface EdgeEventCommonArgs {
edge: Edge
}
export interface CellEventArgs {
'cell:transition:start': Cell.EventArgs['transition:start']
'cell:transition:progress': Cell.EventArgs['transition:progress']
'cell:transition:complete': Cell.EventArgs['transition:complete']
'cell:transition:stop': Cell.EventArgs['transition:stop']
'cell:transition:finish': Cell.EventArgs['transition:finish']
'cell:changed': Cell.EventArgs['changed']
'cell:added': Cell.EventArgs['added']
'cell:removed': Cell.EventArgs['removed']
'cell:change:*': Cell.EventArgs['change:*']
'cell:change:attrs': Cell.EventArgs['change:attrs']
'cell:change:zIndex': Cell.EventArgs['change:zIndex']
'cell:change:markup': Cell.EventArgs['change:markup']
'cell:change:visible': Cell.EventArgs['change:visible']
'cell:change:parent': Cell.EventArgs['change:parent']
'cell:change:children': Cell.EventArgs['change:children']
'cell:change:tools': Cell.EventArgs['change:tools']
'cell:change:view': Cell.EventArgs['change:view']
'cell:change:data': Cell.EventArgs['change:data']
'cell:change:size': Cell.EventArgs['change:size']
'cell:change:angle': Cell.EventArgs['change:angle']
'cell:change:position': Cell.EventArgs['change:position']
'cell:change:ports': Cell.EventArgs['change:ports']
'cell:change:portMarkup': Cell.EventArgs['change:portMarkup']
'cell:change:portLabelMarkup': Cell.EventArgs['change:portLabelMarkup']
'cell:change:portContainerMarkup': Cell.EventArgs['change:portContainerMarkup']
'cell:ports:added': Cell.EventArgs['ports:added']
'cell:ports:removed': Cell.EventArgs['ports:removed']
'cell:change:source': Cell.EventArgs['change:source']
'cell:change:target': Cell.EventArgs['change:target']
'cell:change:router': Cell.EventArgs['change:router']
'cell:change:connector': Cell.EventArgs['change:connector']
'cell:change:vertices': Cell.EventArgs['change:vertices']
'cell:change:labels': Cell.EventArgs['change:labels']
'cell:change:defaultLabel': Cell.EventArgs['change:defaultLabel']
'cell:change:toolMarkup': Cell.EventArgs['change:toolMarkup']
'cell:change:doubleToolMarkup': Cell.EventArgs['change:doubleToolMarkup']
'cell:change:vertexMarkup': Cell.EventArgs['change:vertexMarkup']
'cell:change:arrowheadMarkup': Cell.EventArgs['change:arrowheadMarkup']
'cell:vertexs:added': Cell.EventArgs['vertexs:added']
'cell:vertexs:removed': Cell.EventArgs['vertexs:removed']
'cell:labels:added': Cell.EventArgs['labels:added']
'cell:labels:removed': Cell.EventArgs['labels:removed']
'cell:batch:start': Cell.EventArgs['batch:start']
'cell:batch:stop': Cell.EventArgs['batch:stop']
}
export interface NodeEventArgs {
'node:transition:start': NodeEventCommonArgs &
Cell.EventArgs['transition:start']
'node:transition:progress': NodeEventCommonArgs &
Cell.EventArgs['transition:progress']
'node:transition:complete': NodeEventCommonArgs &
Cell.EventArgs['transition:complete']
'node:transition:stop': NodeEventCommonArgs &
Cell.EventArgs['transition:stop']
'node:transition:finish': NodeEventCommonArgs &
Cell.EventArgs['transition:finish']
'node:changed': NodeEventCommonArgs & CellEventArgs['cell:changed']
'node:added': NodeEventCommonArgs & CellEventArgs['cell:added']
'node:removed': NodeEventCommonArgs & CellEventArgs['cell:removed']
'node:change:*': NodeEventCommonArgs & Cell.EventArgs['change:*']
'node:change:attrs': NodeEventCommonArgs & Cell.EventArgs['change:attrs']
'node:change:zIndex': NodeEventCommonArgs & Cell.EventArgs['change:zIndex']
'node:change:markup': NodeEventCommonArgs & Cell.EventArgs['change:markup']
'node:change:visible': NodeEventCommonArgs &
Cell.EventArgs['change:visible']
'node:change:parent': NodeEventCommonArgs & Cell.EventArgs['change:parent']
'node:change:children': NodeEventCommonArgs &
Cell.EventArgs['change:children']
'node:change:tools': NodeEventCommonArgs & Cell.EventArgs['change:tools']
'node:change:view': NodeEventCommonArgs & Cell.EventArgs['change:view']
'node:change:data': NodeEventCommonArgs & Cell.EventArgs['change:data']
'node:change:size': NodeEventCommonArgs & Cell.EventArgs['change:size']
'node:change:position': NodeEventCommonArgs &
Cell.EventArgs['change:position']
'node:change:angle': NodeEventCommonArgs & Cell.EventArgs['change:angle']
'node:change:ports': NodeEventCommonArgs & Cell.EventArgs['change:ports']
'node:change:portMarkup': NodeEventCommonArgs &
Cell.EventArgs['change:portMarkup']
'node:change:portLabelMarkup': NodeEventCommonArgs &
Cell.EventArgs['change:portLabelMarkup']
'node:change:portContainerMarkup': NodeEventCommonArgs &
Cell.EventArgs['change:portContainerMarkup']
'node:ports:added': NodeEventCommonArgs & Cell.EventArgs['ports:added']
'node:ports:removed': NodeEventCommonArgs & Cell.EventArgs['ports:removed']
'node:batch:start': NodeEventCommonArgs & Cell.EventArgs['batch:start']
'node:batch:stop': NodeEventCommonArgs & Cell.EventArgs['batch:stop']
// 'node:translate': NodeEventCommonArgs
// 'node:translating': NodeEventCommonArgs
// 'node:translated': NodeEventCommonArgs
// 'node:resize': NodeEventCommonArgs
// 'node:resizing': NodeEventCommonArgs
// 'node:resized': NodeEventCommonArgs
// 'node:rotate': NodeEventCommonArgs
// 'node:rotating': NodeEventCommonArgs
// 'node:rotated': NodeEventCommonArgs
}
export interface EdgeEventArgs {
'edge:transition:start': EdgeEventCommonArgs &
Cell.EventArgs['transition:start']
'edge:transition:progress': EdgeEventCommonArgs &
Cell.EventArgs['transition:progress']
'edge:transition:complete': EdgeEventCommonArgs &
Cell.EventArgs['transition:complete']
'edge:transition:stop': EdgeEventCommonArgs &
Cell.EventArgs['transition:stop']
'edge:transition:finish': EdgeEventCommonArgs &
Cell.EventArgs['transition:finish']
'edge:changed': EdgeEventCommonArgs & CellEventArgs['cell:changed']
'edge:added': EdgeEventCommonArgs & CellEventArgs['cell:added']
'edge:removed': EdgeEventCommonArgs & CellEventArgs['cell:removed']
'edge:change:*': EdgeEventCommonArgs & Cell.EventArgs['change:*']
'edge:change:attrs': EdgeEventCommonArgs & Cell.EventArgs['change:attrs']
'edge:change:zIndex': EdgeEventCommonArgs & Cell.EventArgs['change:zIndex']
'edge:change:markup': EdgeEventCommonArgs & Cell.EventArgs['change:markup']
'edge:change:visible': EdgeEventCommonArgs &
Cell.EventArgs['change:visible']
'edge:change:parent': EdgeEventCommonArgs & Cell.EventArgs['change:parent']
'edge:change:children': EdgeEventCommonArgs &
Cell.EventArgs['change:children']
'edge:change:tools': EdgeEventCommonArgs & Cell.EventArgs['change:tools']
'edge:change:data': EdgeEventCommonArgs & Cell.EventArgs['change:data']
'edge:change:source': EdgeEventCommonArgs & Cell.EventArgs['change:source']
'edge:change:target': EdgeEventCommonArgs & Cell.EventArgs['change:target']
'edge:change:router': EdgeEventCommonArgs & Cell.EventArgs['change:router']
'edge:change:connector': EdgeEventCommonArgs &
Cell.EventArgs['change:connector']
'edge:change:vertices': EdgeEventCommonArgs &
Cell.EventArgs['change:vertices']
'edge:change:labels': EdgeEventCommonArgs & Cell.EventArgs['change:labels']
'edge:change:defaultLabel': EdgeEventCommonArgs &
Cell.EventArgs['change:defaultLabel']
'edge:change:toolMarkup': EdgeEventCommonArgs &
Cell.EventArgs['change:toolMarkup']
'edge:change:doubleToolMarkup': EdgeEventCommonArgs &
Cell.EventArgs['change:doubleToolMarkup']
'edge:change:vertexMarkup': EdgeEventCommonArgs &
Cell.EventArgs['change:vertexMarkup']
'edge:change:arrowheadMarkup': EdgeEventCommonArgs &
Cell.EventArgs['change:arrowheadMarkup']
'edge:vertexs:added': EdgeEventCommonArgs & Cell.EventArgs['vertexs:added']
'edge:vertexs:removed': EdgeEventCommonArgs &
Cell.EventArgs['vertexs:removed']
'edge:labels:added': EdgeEventCommonArgs & Cell.EventArgs['labels:added']
'edge:labels:removed': EdgeEventCommonArgs &
Cell.EventArgs['labels:removed']
'edge:batch:start': EdgeEventCommonArgs & Cell.EventArgs['batch:start']
'edge:batch:stop': EdgeEventCommonArgs & Cell.EventArgs['batch:stop']
}
}