@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
1,359 lines (1,147 loc) • 32.5 kB
text/typescript
import { Size, KeyValue } from '../types'
import { ObjectExt, StringExt } from '../util'
import { Point, Polyline } from '../geometry'
import {
Registry,
Attr,
Router,
Connector,
EdgeAnchor,
NodeAnchor,
ConnectionPoint,
ConnectionStrategy,
} from '../registry'
import { Markup } from '../view/markup'
import { ShareRegistry } from './registry'
import { Store } from './store'
import { Cell } from './cell'
import { Node } from './node'
export class Edge<
Properties extends Edge.Properties = Edge.Properties,
> extends Cell<Properties> {
protected static defaults: Edge.Defaults = {}
protected readonly store: Store<Edge.Properties>
protected get [Symbol.toStringTag]() {
return Edge.toStringTag
}
constructor(metadata: Edge.Metadata = {}) {
super(metadata)
}
protected preprocess(metadata: Edge.Metadata, ignoreIdCheck?: boolean) {
const {
source,
sourceCell,
sourcePort,
sourcePoint,
target,
targetCell,
targetPort,
targetPoint,
...others
} = metadata
const data = others as Edge.BaseOptions
const isValidId = (val: any): val is string =>
typeof val === 'string' || typeof val === 'number'
if (source != null) {
if (Cell.isCell(source)) {
data.source = { cell: source.id }
} else if (isValidId(source)) {
data.source = { cell: source }
} else if (Point.isPoint(source)) {
data.source = source.toJSON()
} else if (Array.isArray(source)) {
data.source = { x: source[0], y: source[1] }
} else {
const cell = (source as Edge.TerminalCellLooseData).cell
if (Cell.isCell(cell)) {
data.source = {
...source,
cell: cell.id,
}
} else {
data.source = source as Edge.TerminalCellData
}
}
}
if (sourceCell != null || sourcePort != null) {
let terminal = data.source as Edge.TerminalCellData
if (sourceCell != null) {
const id = isValidId(sourceCell) ? sourceCell : sourceCell.id
if (terminal) {
terminal.cell = id
} else {
terminal = data.source = { cell: id }
}
}
if (sourcePort != null && terminal) {
terminal.port = sourcePort
}
} else if (sourcePoint != null) {
data.source = Point.create(sourcePoint).toJSON()
}
if (target != null) {
if (Cell.isCell(target)) {
data.target = { cell: target.id }
} else if (isValidId(target)) {
data.target = { cell: target }
} else if (Point.isPoint(target)) {
data.target = target.toJSON()
} else if (Array.isArray(target)) {
data.target = { x: target[0], y: target[1] }
} else {
const cell = (target as Edge.TerminalCellLooseData).cell
if (Cell.isCell(cell)) {
data.target = {
...target,
cell: cell.id,
}
} else {
data.target = target as Edge.TerminalCellData
}
}
}
if (targetCell != null || targetPort != null) {
let terminal = data.target as Edge.TerminalCellData
if (targetCell != null) {
const id = isValidId(targetCell) ? targetCell : targetCell.id
if (terminal) {
terminal.cell = id
} else {
terminal = data.target = { cell: id }
}
}
if (targetPort != null && terminal) {
terminal.port = targetPort
}
} else if (targetPoint != null) {
data.target = Point.create(targetPoint).toJSON()
}
return super.preprocess(data, ignoreIdCheck)
}
protected setup() {
super.setup()
this.on('change:labels', (args) => this.onLabelsChanged(args))
this.on('change:vertices', (args) => this.onVertexsChanged(args))
}
isEdge(): this is Edge {
return true
}
// #region terminal
disconnect(options: Edge.SetOptions = {}) {
this.store.set(
{
source: { x: 0, y: 0 },
target: { x: 0, y: 0 },
},
options,
)
return this
}
get source() {
return this.getSource()
}
set source(data: Edge.TerminalData) {
this.setSource(data)
}
getSource() {
return this.getTerminal('source')
}
getSourceCellId() {
return (this.source as Edge.TerminalCellData).cell
}
getSourcePortId() {
return (this.source as Edge.TerminalCellData).port
}
setSource(
node: Node,
args?: Edge.SetCellTerminalArgs,
options?: Edge.SetOptions,
): this
setSource(
edge: Edge,
args?: Edge.SetEdgeTerminalArgs,
options?: Edge.SetOptions,
): this
setSource(
point: Point | Point.PointLike,
args?: Edge.SetTerminalCommonArgs,
options?: Edge.SetOptions,
): this
setSource(args: Edge.TerminalData, options?: Edge.SetOptions): this
setSource(
source: Node | Edge | Point | Point.PointLike | Edge.TerminalData,
args?: Edge.SetTerminalCommonArgs | Edge.SetOptions,
options: Edge.SetOptions = {},
) {
return this.setTerminal('source', source, args, options)
}
get target() {
return this.getTarget()
}
set target(data: Edge.TerminalData) {
this.setTarget(data)
}
getTarget() {
return this.getTerminal('target')
}
getTargetCellId() {
return (this.target as Edge.TerminalCellData).cell
}
getTargetPortId() {
return (this.target as Edge.TerminalCellData).port
}
setTarget(
edge: Node,
args?: Edge.SetCellTerminalArgs,
options?: Edge.SetOptions,
): this
setTarget(
edge: Edge,
args?: Edge.SetEdgeTerminalArgs,
options?: Edge.SetOptions,
): this
setTarget(
point: Point | Point.PointLike,
args?: Edge.SetTerminalCommonArgs,
options?: Edge.SetOptions,
): this
setTarget(args: Edge.TerminalData, options?: Edge.SetOptions): this
setTarget(
target: Node | Edge | Point | Point.PointLike | Edge.TerminalData,
args?: Edge.SetTerminalCommonArgs | Edge.SetOptions,
options: Edge.SetOptions = {},
) {
return this.setTerminal('target', target, args, options)
}
getTerminal(type: Edge.TerminalType) {
return { ...this.store.get(type) } as Edge.TerminalData
}
setTerminal(
type: Edge.TerminalType,
terminal: Node | Edge | Point | Point.PointLike | Edge.TerminalData,
args?: Edge.SetTerminalCommonArgs | Edge.SetOptions,
options: Edge.SetOptions = {},
): this {
// `terminal` is a cell
if (Cell.isCell(terminal)) {
this.store.set(
type,
ObjectExt.merge({}, args, { cell: terminal.id }),
options,
)
return this
}
// `terminal` is a point-like object
const p = terminal as Point.PointLike
if (Point.isPoint(terminal) || (p.x != null && p.y != null)) {
this.store.set(
type,
ObjectExt.merge({}, args, { x: p.x, y: p.y }),
options,
)
return this
}
// `terminal` is an object
this.store.set(
type,
ObjectExt.cloneDeep(terminal as Edge.TerminalData),
options,
)
return this
}
getSourcePoint() {
return this.getTerminalPoint('source')
}
getTargetPoint() {
return this.getTerminalPoint('target')
}
protected getTerminalPoint(type: Edge.TerminalType): Point {
const terminal = this[type]
if (Point.isPointLike(terminal)) {
return Point.create(terminal)
}
const cell = this.getTerminalCell(type)
if (cell) {
return cell.getConnectionPoint(this, type)
}
return new Point()
}
getSourceCell() {
return this.getTerminalCell('source')
}
getTargetCell() {
return this.getTerminalCell('target')
}
protected getTerminalCell(type: Edge.TerminalType) {
if (this.model) {
const cellId =
type === 'source' ? this.getSourceCellId() : this.getTargetCellId()
if (cellId) {
return this.model.getCell(cellId)
}
}
return null
}
getSourceNode() {
return this.getTerminalNode('source')
}
getTargetNode() {
return this.getTerminalNode('target')
}
protected getTerminalNode(type: Edge.TerminalType): Node | null {
let cell: Cell | null = this // eslint-disable-line
const visited: { [id: string]: boolean } = {}
while (cell && cell.isEdge()) {
if (visited[cell.id]) {
return null
}
visited[cell.id] = true
cell = cell.getTerminalCell(type)
}
return cell && cell.isNode() ? cell : null
}
// #endregion
// #region router
get router() {
return this.getRouter()
}
set router(data: Edge.RouterData | undefined) {
if (data == null) {
this.removeRouter()
} else {
this.setRouter(data)
}
}
getRouter() {
return this.store.get<Edge.RouterData>('router')
}
setRouter(name: string, args?: KeyValue, options?: Edge.SetOptions): this
setRouter(router: Edge.RouterData, options?: Edge.SetOptions): this
setRouter(
name?: string | Edge.RouterData,
args?: KeyValue,
options?: Edge.SetOptions,
) {
if (typeof name === 'object') {
this.store.set('router', name, args)
} else {
this.store.set('router', { name, args }, options)
}
return this
}
removeRouter(options: Edge.SetOptions = {}) {
this.store.remove('router', options)
return this
}
// #endregion
// #region connector
get connector() {
return this.getConnector()
}
set connector(data: Edge.ConnectorData | undefined) {
if (data == null) {
this.removeConnector()
} else {
this.setConnector(data)
}
}
getConnector() {
return this.store.get('connector')
}
setConnector(name: string, args?: KeyValue, options?: Edge.SetOptions): this
setConnector(connector: Edge.ConnectorData, options?: Edge.SetOptions): this
setConnector(
name?: string | Edge.ConnectorData,
args?: KeyValue | Edge.SetOptions,
options?: Edge.SetOptions,
) {
if (typeof name === 'object') {
this.store.set('connector', name, args)
} else {
this.store.set('connector', { name, args }, options)
}
return this
}
removeConnector(options: Edge.SetOptions = {}) {
return this.store.remove('connector', options)
}
// #endregion
// #region strategy
get strategy() {
return this.getStrategy()
}
set strategy(data: Edge.StrategyData | undefined) {
if (data == null) {
this.removeStrategy()
} else {
this.setStrategy(data)
}
}
getStrategy() {
return this.store.get('strategy')
}
setStrategy(name: string, args?: KeyValue, options?: Edge.SetOptions): this
setStrategy(strategy: Edge.StrategyData, options?: Edge.SetOptions): this
setStrategy(
name?: string | Edge.StrategyData,
args?: KeyValue | Edge.SetOptions,
options?: Edge.SetOptions,
) {
if (typeof name === 'object') {
this.store.set('strategy', name, args)
} else {
this.store.set('strategy', { name, args }, options)
}
return this
}
removeStrategy(options: Edge.SetOptions = {}) {
return this.store.remove('strategy', options)
}
// #endregion
// #region labels
getDefaultLabel(): Edge.Label {
const ctor = this.constructor as Edge.Definition
const defaults = this.store.get('defaultLabel') || ctor.defaultLabel || {}
return ObjectExt.cloneDeep(defaults)
}
get labels() {
return this.getLabels()
}
set labels(labels: Edge.Label[]) {
this.setLabels(labels)
}
getLabels(): Edge.Label[] {
return [...this.store.get('labels', [])].map((item) =>
this.parseLabel(item),
)
}
setLabels(
labels: Edge.Label | Edge.Label[] | string | string[],
options: Edge.SetOptions = {},
) {
this.store.set('labels', Array.isArray(labels) ? labels : [labels], options)
return this
}
insertLabel(
label: Edge.Label | string,
index?: number,
options: Edge.SetOptions = {},
) {
const labels = this.getLabels()
const len = labels.length
let idx = index != null && Number.isFinite(index) ? index : len
if (idx < 0) {
idx = len + idx + 1
}
labels.splice(idx, 0, this.parseLabel(label))
return this.setLabels(labels, options)
}
appendLabel(label: Edge.Label | string, options: Edge.SetOptions = {}) {
return this.insertLabel(label, -1, options)
}
getLabelAt(index: number) {
const labels = this.getLabels()
if (index != null && Number.isFinite(index)) {
return this.parseLabel(labels[index])
}
return null
}
setLabelAt(
index: number,
label: Edge.Label | string,
options: Edge.SetOptions = {},
) {
if (index != null && Number.isFinite(index)) {
const labels = this.getLabels()
labels[index] = this.parseLabel(label)
this.setLabels(labels, options)
}
return this
}
removeLabelAt(index: number, options: Edge.SetOptions = {}) {
const labels = this.getLabels()
const idx = index != null && Number.isFinite(index) ? index : -1
const removed = labels.splice(idx, 1)
this.setLabels(labels, options)
return removed.length ? removed[0] : null
}
protected parseLabel(label: string | Edge.Label) {
if (typeof label === 'string') {
const ctor = this.constructor as Edge.Definition
return ctor.parseStringLabel(label)
}
return label
}
protected onLabelsChanged({
previous,
current,
}: Cell.ChangeArgs<Edge.Label[]>) {
const added =
previous && current
? current.filter((label1) => {
if (
!previous.find(
(label2) =>
label1 === label2 || ObjectExt.isEqual(label1, label2),
)
) {
return label1
}
return null
})
: current
? [...current]
: []
const removed =
previous && current
? previous.filter((label1) => {
if (
!current.find(
(label2) =>
label1 === label2 || ObjectExt.isEqual(label1, label2),
)
) {
return label1
}
return null
})
: previous
? [...previous]
: []
if (added.length > 0) {
this.notify('labels:added', { added, cell: this, edge: this })
}
if (removed.length > 0) {
this.notify('labels:removed', { removed, cell: this, edge: this })
}
}
// #endregion
// #region vertices
get vertexMarkup() {
return this.getVertexMarkup()
}
set vertexMarkup(markup: Markup) {
this.setVertexMarkup(markup)
}
getDefaultVertexMarkup() {
return this.store.get('defaultVertexMarkup') || Markup.getEdgeVertexMarkup()
}
getVertexMarkup() {
return this.store.get('vertexMarkup') || this.getDefaultVertexMarkup()
}
setVertexMarkup(markup?: Markup, options: Edge.SetOptions = {}) {
this.store.set('vertexMarkup', Markup.clone(markup), options)
return this
}
get vertices() {
return this.getVertices()
}
set vertices(vertices: Point.PointLike | Point.PointLike[]) {
this.setVertices(vertices)
}
getVertices() {
return [...this.store.get('vertices', [])]
}
setVertices(
vertices: Point.PointLike | Point.PointLike[],
options: Edge.SetOptions = {},
) {
const points = Array.isArray(vertices) ? vertices : [vertices]
this.store.set(
'vertices',
points.map((p) => Point.toJSON(p)),
options,
)
return this
}
insertVertex(
vertice: Point.PointLike,
index?: number,
options: Edge.SetOptions = {},
) {
const vertices = this.getVertices()
const len = vertices.length
let idx = index != null && Number.isFinite(index) ? index : len
if (idx < 0) {
idx = len + idx + 1
}
vertices.splice(idx, 0, Point.toJSON(vertice))
return this.setVertices(vertices, options)
}
appendVertex(vertex: Point.PointLike, options: Edge.SetOptions = {}) {
return this.insertVertex(vertex, -1, options)
}
getVertexAt(index: number) {
if (index != null && Number.isFinite(index)) {
const vertices = this.getVertices()
return vertices[index]
}
return null
}
setVertexAt(
index: number,
vertice: Point.PointLike,
options: Edge.SetOptions = {},
) {
if (index != null && Number.isFinite(index)) {
const vertices = this.getVertices()
vertices[index] = vertice
this.setVertices(vertices, options)
}
return this
}
removeVertexAt(index: number, options: Edge.SetOptions = {}) {
const vertices = this.getVertices()
const idx = index != null && Number.isFinite(index) ? index : -1
vertices.splice(idx, 1)
return this.setVertices(vertices, options)
}
protected onVertexsChanged({
previous,
current,
}: Cell.ChangeArgs<Point.PointLike[]>) {
const added =
previous && current
? current.filter((p1) => {
if (!previous.find((p2) => Point.equals(p1, p2))) {
return p1
}
return null
})
: current
? [...current]
: []
const removed =
previous && current
? previous.filter((p1) => {
if (!current.find((p2) => Point.equals(p1, p2))) {
return p1
}
return null
})
: previous
? [...previous]
: []
if (added.length > 0) {
this.notify('vertexs:added', { added, cell: this, edge: this })
}
if (removed.length > 0) {
this.notify('vertexs:removed', { removed, cell: this, edge: this })
}
}
// #endregion
// #region markup
getDefaultMarkup() {
return this.store.get('defaultMarkup') || Markup.getEdgeMarkup()
}
getMarkup() {
return super.getMarkup() || this.getDefaultMarkup()
}
// #endregion
// #region toolMarkup
get toolMarkup() {
return this.getToolMarkup()
}
set toolMarkup(markup: Markup) {
this.setToolMarkup(markup)
}
getDefaultToolMarkup() {
return this.store.get('defaultToolMarkup') || Markup.getEdgeToolMarkup()
}
getToolMarkup() {
return this.store.get('toolMarkup') || this.getDefaultToolMarkup()
}
setToolMarkup(markup?: Markup, options: Edge.SetOptions = {}) {
this.store.set('toolMarkup', markup, options)
return this
}
get doubleToolMarkup() {
return this.getDoubleToolMarkup()
}
set doubleToolMarkup(markup: Markup | undefined) {
this.setDoubleToolMarkup(markup)
}
getDefaultDoubleToolMarkup() {
return this.store.get('defaultDoubleToolMarkup')
}
getDoubleToolMarkup() {
return (
this.store.get('doubleToolMarkup') || this.getDefaultDoubleToolMarkup()
)
}
setDoubleToolMarkup(markup?: Markup, options: Edge.SetOptions = {}) {
this.store.set('doubleToolMarkup', markup, options)
return this
}
// #endregion
// #region arrowheadMarkup
get arrowheadMarkup() {
return this.getArrowheadMarkup()
}
set arrowheadMarkup(markup: Markup) {
this.setArrowheadMarkup(markup)
}
getDefaultArrowheadMarkup() {
return (
this.store.get('defaultArrowheadMarkup') ||
Markup.getEdgeArrowheadMarkup()
)
}
getArrowheadMarkup() {
return this.store.get('arrowheadMarkup') || this.getDefaultArrowheadMarkup()
}
setArrowheadMarkup(markup?: Markup, options: Edge.SetOptions = {}) {
this.store.set('arrowheadMarkup', markup, options)
return this
}
// #endregion
// #region transform
/**
* Translate the edge vertices (and source and target if they are points)
* by `tx` pixels in the x-axis and `ty` pixels in the y-axis.
*/
translate(tx: number, ty: number, options: Cell.TranslateOptions = {}) {
options.translateBy = options.translateBy || this.id
options.tx = tx
options.ty = ty
return this.applyToPoints(
(p) => ({
x: (p.x || 0) + tx,
y: (p.y || 0) + ty,
}),
options,
)
}
/**
* Scales the edge's points (vertices) relative to the given origin.
*/
scale(
sx: number,
sy: number,
origin?: Point | Point.PointLike,
options: Edge.SetOptions = {},
) {
return this.applyToPoints((p) => {
return Point.create(p).scale(sx, sy, origin).toJSON()
}, options)
}
protected applyToPoints(
worker: (p: Point.PointLike) => Point.PointLike,
options: Edge.SetOptions = {},
) {
const attrs: {
source?: Edge.TerminalPointData
target?: Edge.TerminalPointData
vertices?: Point.PointLike[]
} = {}
const source = this.getSource()
const target = this.getTarget()
if (Point.isPointLike(source)) {
attrs.source = worker(source)
}
if (Point.isPointLike(target)) {
attrs.target = worker(target)
}
const vertices = this.getVertices()
if (vertices.length > 0) {
attrs.vertices = vertices.map(worker)
}
this.store.set(attrs, options)
return this
}
// #endregion
// #region common
getBBox() {
return this.getPolyline().bbox()
}
getConnectionPoint() {
return this.getPolyline().pointAt(0.5)!
}
getPolyline() {
const points = [this.getSourcePoint(), this.getTargetPoint()]
const vertices = this.getVertices()
vertices.forEach((p) => points.push(Point.create(p)))
return new Polyline(points)
}
updateParent(options?: Edge.SetOptions) {
let newParent: Cell | null = null
const source = this.getSourceCell()
const target = this.getTargetCell()
const prevParent = this.getParent()
if (source && target) {
if (source === target || source.isDescendantOf(target)) {
newParent = target
} else if (target.isDescendantOf(source)) {
newParent = source
} else {
newParent = Cell.getCommonAncestor(source, target)
}
}
// Unembeds the edge if source and target has no common
// ancestor or common ancestor changed
if (prevParent && (!newParent || newParent.id !== prevParent.id)) {
prevParent.unembed(this, options)
}
if (newParent) {
newParent.embed(this, options)
}
return newParent
}
hasLoop(options: { deep?: boolean } = {}) {
const source = this.getSource() as Edge.TerminalCellData
const target = this.getTarget() as Edge.TerminalCellData
const sourceId = source.cell
const targetId = target.cell
if (!sourceId || !targetId) {
return false
}
let loop = sourceId === targetId
// Note that there in the deep mode a edge can have a loop,
// even if it connects only a parent and its embed.
// A loop "target equals source" is valid in both shallow and deep mode.
// eslint-disable-next-line
if (!loop && options.deep && this._model) {
const sourceCell = this.getSourceCell()
const targetCell = this.getTargetCell()
if (sourceCell && targetCell) {
loop =
sourceCell.isAncestorOf(targetCell, options) ||
targetCell.isAncestorOf(sourceCell, options)
}
}
return loop
}
getFragmentAncestor(): Cell | null {
const cells = [this, this.getSourceNode(), this.getTargetNode()].filter(
(item) => item != null,
)
return this.getCommonAncestor(...cells)
}
isFragmentDescendantOf(cell: Cell) {
const ancestor = this.getFragmentAncestor()
return (
!!ancestor && (ancestor.id === cell.id || ancestor.isDescendantOf(cell))
)
}
// #endregion
}
export namespace Edge {
export type RouterData = Router.NativeItem | Router.ManaualItem
export type ConnectorData = Connector.NativeItem | Connector.ManaualItem
export type StrategyData =
| ConnectionStrategy.NativeItem
| ConnectionStrategy.ManaualItem
}
export namespace Edge {
interface Common extends Cell.Common {
source?: TerminalData
target?: TerminalData
router?: RouterData
connector?: ConnectorData
strategy?: StrategyData
labels?: Label[] | string[]
defaultLabel?: Label
vertices?: (Point.PointLike | Point.PointData)[]
toolMarkup?: Markup
doubleToolMarkup?: Markup
vertexMarkup?: Markup
arrowheadMarkup?: Markup
defaultMarkup?: Markup
defaultToolMarkup?: Markup
defaultDoubleToolMarkup?: Markup
defaultVertexMarkup?: Markup
defaultArrowheadMarkup?: Markup
}
interface TerminalOptions {
sourceCell?: Cell | string
sourcePort?: string
sourcePoint?: Point.PointLike | Point.PointData
targetCell?: Cell | string
targetPort?: string
targetPoint?: Point.PointLike | Point.PointData
source?:
| string
| Cell
| Point.PointLike
| Point.PointData
| TerminalPointData
| TerminalCellLooseData
target?:
| string
| Cell
| Point.PointLike
| Point.PointData
| TerminalPointData
| TerminalCellLooseData
}
export interface BaseOptions extends Common, Cell.Metadata {}
export interface Metadata
extends Omit<BaseOptions, TerminalType>,
TerminalOptions {}
export interface Defaults extends Common, Cell.Defaults {}
export interface Properties
extends Cell.Properties,
Omit<BaseOptions, 'tools'> {}
export interface Config
extends Omit<Defaults, TerminalType>,
TerminalOptions,
Cell.Config<Metadata, Edge> {}
}
export namespace Edge {
export interface SetOptions extends Cell.SetOptions {}
export type TerminalType = 'source' | 'target'
export interface SetTerminalCommonArgs {
selector?: string
magnet?: string
connectionPoint?:
| string
| ConnectionPoint.NativeItem
| ConnectionPoint.ManaualItem
}
export interface SetCellTerminalArgs extends SetTerminalCommonArgs {
port?: string
priority?: boolean
anchor?: string | NodeAnchor.NativeItem | NodeAnchor.ManaualItem
}
export interface SetEdgeTerminalArgs extends SetTerminalCommonArgs {
anchor?: string | EdgeAnchor.NativeItem | EdgeAnchor.ManaualItem
}
export interface TerminalPointData
extends SetTerminalCommonArgs,
Point.PointLike {}
export interface TerminalCellData extends SetCellTerminalArgs {
cell: string
port?: string
}
export interface TerminalCellLooseData extends SetCellTerminalArgs {
cell: string | Cell
port?: string
}
export type TerminalData = TerminalPointData | TerminalCellData
export function equalTerminals(a: TerminalData, b: TerminalData) {
const a1 = a as TerminalCellData
const b1 = b as TerminalCellData
if (a1.cell === b1.cell) {
return a1.port === b1.port || (a1.port == null && b1.port == null)
}
return false
}
}
export namespace Edge {
export interface Label extends KeyValue {
markup?: Markup
attrs?: Attr.CellAttrs
/**
* If the distance is in the `[0,1]` range (inclusive), then the position
* of the label is defined as a percentage of the total length of the edge
* (the normalized length). For example, passing the number `0.5` positions
* the label to the middle of the edge.
*
* If the distance is larger than `1` (exclusive), the label will be
* positioned distance pixels away from the beginning of the path along
* the edge.
*
* If the distance is a negative number, the label will be positioned
* distance pixels away from the end of the path along the edge.
*/
position?: LabelPosition
size?: Size
}
export interface LabelPositionOptions {
/**
* Forces absolute coordinates for distance.
*/
absoluteDistance?: boolean
/**
* Forces reverse absolute coordinates (if absoluteDistance = true)
*/
reverseDistance?: boolean
/**
* Forces absolute coordinates for offset.
*/
absoluteOffset?: boolean
/**
* Auto-adjusts the angle of the label to match path gradient at position.
*/
keepGradient?: boolean
/**
* Whether rotates labels so they are never upside-down.
*/
ensureLegibility?: boolean
}
export interface LabelPositionObject {
distance: number
offset?:
| number
| {
x?: number
y?: number
}
angle?: number
options?: LabelPositionOptions
}
export type LabelPosition = number | LabelPositionObject
export const defaultLabel: Label = {
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
text: {
fill: '#000',
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none',
},
rect: {
ref: 'label',
fill: '#fff',
rx: 3,
ry: 3,
refWidth: 1,
refHeight: 1,
refX: 0,
refY: 0,
},
},
position: {
distance: 0.5,
},
}
export function parseStringLabel(text: string): Label {
return {
attrs: { label: { text } },
}
}
}
export namespace Edge {
export const toStringTag = `X6.${Edge.name}`
export function isEdge(instance: any): instance is Edge {
if (instance == null) {
return false
}
if (instance instanceof Edge) {
return true
}
const tag = instance[Symbol.toStringTag]
const edge = instance as Edge
if (
(tag == null || tag === toStringTag) &&
typeof edge.isNode === 'function' &&
typeof edge.isEdge === 'function' &&
typeof edge.prop === 'function' &&
typeof edge.attr === 'function' &&
typeof edge.disconnect === 'function' &&
typeof edge.getSource === 'function' &&
typeof edge.getTarget === 'function'
) {
return true
}
return false
}
}
export namespace Edge {
export const registry = Registry.create<
Definition,
never,
Config & { inherit?: string | Definition }
>({
type: 'edge',
process(shape, options) {
if (ShareRegistry.exist(shape, false)) {
throw new Error(
`Edge with name '${shape}' was registered by anthor Node`,
)
}
if (typeof options === 'function') {
options.config({ shape })
return options
}
let parent = Edge
// default inherit from 'dege'
const { inherit = 'edge', ...others } = options
if (typeof inherit === 'string') {
const base = this.get(inherit || 'edge')
if (base == null && inherit) {
this.onNotFound(inherit, 'inherited')
} else {
parent = base
}
} else {
parent = inherit
}
if (others.constructorName == null) {
others.constructorName = shape
}
const ctor: Definition = parent.define.call(parent, others)
ctor.config({ shape })
return ctor as any
},
})
ShareRegistry.setEdgeRegistry(registry)
}
export namespace Edge {
type EdgeClass = typeof Edge
export interface Definition extends EdgeClass {
new <T extends Properties = Properties>(metadata: T): Edge
}
let counter = 0
function getClassName(name?: string) {
if (name) {
return StringExt.pascalCase(name)
}
counter += 1
return `CustomEdge${counter}`
}
export function define(config: Config) {
const { constructorName, overwrite, ...others } = config
const ctor = ObjectExt.createClass<Definition>(
getClassName(constructorName || others.shape),
this as Definition,
)
ctor.config(others)
if (others.shape) {
registry.register(others.shape, ctor, overwrite)
}
return ctor
}
export function create(options: Metadata) {
const shape = options.shape || 'edge'
const Ctor = registry.get(shape)
if (Ctor) {
return new Ctor(options)
}
return registry.onNotFound(shape)
}
}
export namespace Edge {
const shape = 'basic.edge'
Edge.config({
shape,
propHooks(metadata: Properties) {
const { label, vertices, ...others } = metadata
if (label) {
if (others.labels == null) {
others.labels = []
}
const formated =
typeof label === 'string' ? parseStringLabel(label) : label
others.labels.push(formated)
}
if (vertices) {
if (Array.isArray(vertices)) {
others.vertices = vertices.map((item) => Point.create(item).toJSON())
}
}
return others
},
})
registry.register(shape, Edge)
}