@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
486 lines (430 loc) • 12.3 kB
text/typescript
import type { Dom, Nilable } from '../common'
import { ObjectExt } from '../common'
import { Config } from '../config'
import type { RectangleLike } from '../geometry'
import type { Graph } from '../graph'
import type {
Cell,
Edge,
EdgeLabel,
Model,
Node,
TerminalData,
TerminalType,
} from '../model'
import type { Port } from '../model/port'
import type {
ConnectionPointManualItem,
ConnectionPointNativeItem,
ConnectorManualItem,
ConnectorNativeItem,
EdgeAnchorManualItem,
EdgeAnchorNativeItem,
NodeAnchorManualItem,
NodeAnchorNativeItem,
RouterManualItem,
RouterNativeItem,
} from '../registry'
import { Edge as StandardEdge } from '../shape'
import type { CellView, EdgeView, NodeView } from '../view'
import type { CellViewInteracting } from '../view/cell/type'
import type { MarkupSelectors } from '../view/markup'
import type { BackgroundManagerOptions } from './background'
import type { GridCommonOptions, GridDrawOptions, GridOptions } from './grid'
import type { HighlightManagerOptions } from './highlight'
import type { MouseWheelOptions } from './mousewheel'
import type { PanningOptions } from './panning'
export interface VirtualOptions {
enabled?: boolean
margin?: number
}
interface Common {
container: HTMLElement
model?: Model
x: number
y: number
width: number
height: number
autoResize?: boolean | Element | Document
background?: false | BackgroundManagerOptions
scaling: {
min?: number
max?: number
}
moveThreshold: number
clickThreshold: number
magnetThreshold: number | 'onleave'
preventDefaultDblClick: boolean
preventDefaultContextMenu:
| boolean
| ((this: Graph, { view }: { view: CellView | null }) => boolean)
preventDefaultMouseDown: boolean
preventDefaultBlankAction: boolean
interacting: CellViewInteracting
async?: boolean
virtual?: boolean | VirtualOptions
guard: (e: Dom.EventObject, view?: CellView | null) => boolean
onPortRendered?: (args: OnPortRenderedArgs) => void
onEdgeLabelRendered?: (
args: OnEdgeLabelRenderedArgs,
) => void | ((args: OnEdgeLabelRenderedArgs) => void)
createCellView?: (
this: Graph,
cell: Cell,
) => typeof CellView | (new (...args: any[]) => CellView) | null | undefined
}
export interface ManualBooleans {
panning: boolean | Partial<PanningOptions>
mousewheel: boolean | Partial<MouseWheelOptions>
embedding: boolean | Partial<Embedding>
}
export interface GraphManual extends Partial<Common>, Partial<ManualBooleans> {
grid?: boolean | number | (Partial<GridCommonOptions> & GridDrawOptions)
connecting?: Partial<Connecting>
translating?: Partial<Translating>
highlighting?: Partial<Highlighting>
}
export interface GraphDefinition extends Common {
grid: GridOptions
panning: PanningOptions
mousewheel: MouseWheelOptions
embedding: Embedding
connecting: Connecting
translating: Translating
highlighting: Highlighting
}
type OptionItem<T, S> = S | ((this: Graph, arg: T) => S)
type NodeAnchorOptions = string | NodeAnchorNativeItem | NodeAnchorManualItem
type EdgeAnchorOptions = string | EdgeAnchorNativeItem | EdgeAnchorManualItem
type ConnectionPointOptions =
| string
| ConnectionPointNativeItem
| ConnectionPointManualItem
export interface Connecting {
/**
* Snap edge to the closest node/port in the given radius on dragging.
*/
snap: boolean | { radius: number; anchor?: 'center' | 'bbox' }
/**
* Specify whether connect to point on the graph is allowed.
*/
allowBlank: boolean | ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* When set to `false`, edges can not be connected to the same node,
* meaning the source and target of the edge can not be the same node.
*/
allowLoop: boolean | ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* Specify whether connect to node(not the port on the node) is allowed.
*/
allowNode: boolean | ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* Specify whether connect to edge is allowed.
*/
allowEdge: boolean | ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* Specify whether connect to port is allowed.
*/
allowPort: boolean | ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* Specify whether more than one edge connected to the same source and
* target node is allowed.
*/
allowMulti:
| boolean
| 'withPort'
| ((this: Graph, args: ValidateConnectionArgs) => boolean)
/**
* Highlights all the available magnets or nodes when a edge is
* dragging(reconnecting). This gives a hint to the user to what
* other nodes/ports this edge can be connected. What magnets/cells
* are available is determined by the `validateConnection` function.
*/
highlight: boolean
anchor: NodeAnchorOptions
sourceAnchor?: NodeAnchorOptions
targetAnchor?: NodeAnchorOptions
edgeAnchor: EdgeAnchorOptions
sourceEdgeAnchor?: EdgeAnchorOptions
targetEdgeAnchor?: EdgeAnchorOptions
connectionPoint: ConnectionPointOptions
sourceConnectionPoint?: ConnectionPointOptions
targetConnectionPoint?: ConnectionPointOptions
router: string | RouterNativeItem | RouterManualItem
connector: string | ConnectorNativeItem | ConnectorManualItem
createEdge?: (
this: Graph,
args: {
sourceCell: Cell
sourceView: CellView
sourceMagnet: Element
},
) => Nilable<Edge> | void
/**
* Check whether to add a new edge to the graph when user clicks
* on an a magnet.
*/
validateMagnet?: (
this: Graph,
args: {
cell: Cell
view: CellView
magnet: Element
e: Dom.MouseDownEvent | Dom.MouseEnterEvent
},
) => boolean
/**
* Custom validation on stop draggin the edge arrowhead(source/target).
* If the function returns `false`, the edge is either removed(edges
* which are created during the interaction) or reverted to the state
* before the interaction.
*/
validateEdge?: (
this: Graph,
args: {
edge: Edge
type: TerminalType
previous: TerminalData
},
) => boolean
/**
* Check whether to allow or disallow the edge connection while an
* arrowhead end (source/target) being changed.
*/
validateConnection: (this: Graph, args: ValidateConnectionArgs) => boolean
}
export interface ValidateConnectionArgs {
type?: TerminalType | null
edge?: Edge | null
edgeView?: EdgeView | null
sourceCell?: Cell | null
targetCell?: Cell | null
sourceView?: CellView | null
targetView?: CellView | null
sourcePort?: string | null
targetPort?: string | null
sourceMagnet?: Element | null
targetMagnet?: Element | null
}
export interface Translating {
/**
* Restrict the translation (movement) of nodes by a given bounding box.
* If set to `true`, the user will not be able to move nodes outside the
* boundary of the graph area.
*/
restrict: boolean | OptionItem<CellView | null, RectangleLike | number | null>
/**
* After a node is moved, if it overlaps with other nodes, it will be
* automatically offset (by default, no offset occurs).
*/
autoOffset?: boolean
}
export interface Embedding {
enabled?: boolean
/**
* Determines the way how a cell finds a suitable parent when it's dragged
* over the graph. The cell with the highest z-index (visually on the top)
* will be chosen.
*/
findParent?:
| 'bbox'
| 'center'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight'
| ((this: Graph, args: { node: Node; view: NodeView }) => Cell[])
/**
* If enabled only the node on the very front is taken into account for the
* embedding. If disabled the nodes under the dragged view are tested one by
* one (from front to back) until a valid parent found.
*/
frontOnly?: boolean
/**
* Check whether to allow or disallow the node embedding while it's being
* translated. By default, all nodes can be embedded into all other nodes.
*/
validate: (
this: Graph,
args: {
child: Node
parent: Node
childView: CellView
parentView: CellView
},
) => boolean
}
/**
* Configure which highlighter to use (and with which options) for
* each type of interaction.
*/
export interface Highlighting {
/**
* The default highlighter to use (and options) when none is specified
*/
default: HighlightManagerOptions
/**
* When a cell is dragged over another cell in embedding mode.
*/
embedding?: HighlightManagerOptions | null
/**
* When showing all nodes to which a valid connection can be made.
*/
nodeAvailable?: HighlightManagerOptions | null
/**
* When showing all magnets to which a valid connection can be made.
*/
magnetAvailable?: HighlightManagerOptions | null
/**
* When a valid edge connection can be made to an node.
*/
magnetAdsorbed?: HighlightManagerOptions | null
}
export function getOptions(options: Partial<GraphManual>) {
const { grid, panning, mousewheel, embedding, ...others } = options
// size
// ----
const container = options.container
if (container != null) {
if (others.width == null) {
others.width = container.clientWidth
}
if (others.height == null) {
others.height = container.clientHeight
}
} else {
throw new Error(`Ensure the container of the graph is specified and valid`)
}
const result = ObjectExt.merge({}, defaults, others) as GraphDefinition
// grid
// ----
const defaultGrid: GridCommonOptions = { size: 10, visible: false }
if (typeof grid === 'number') {
result.grid = { size: grid, visible: false }
} else if (typeof grid === 'boolean') {
result.grid = { ...defaultGrid, visible: grid }
} else {
result.grid = { ...defaultGrid, ...grid }
}
// booleas
// -------
const booleas: (keyof ManualBooleans)[] = [
'panning',
'mousewheel',
'embedding',
]
booleas.forEach((key) => {
const val = options[key]
if (typeof val === 'boolean') {
result[key].enabled = val
} else if (val != null) {
result[key] = {
...result[key],
...(val as any),
}
}
})
return result
}
export interface OnPortRenderedArgs {
node: Node
port: Port
container: Element
selectors?: MarkupSelectors
labelContainer?: Element
labelSelectors?: MarkupSelectors | null
contentContainer: Element
contentSelectors?: MarkupSelectors
}
export interface OnEdgeLabelRenderedArgs {
edge: Edge
label: EdgeLabel
container: Element
selectors: MarkupSelectors
}
export const defaults: Partial<GraphDefinition> = {
x: 0,
y: 0,
scaling: {
min: 0.01,
max: 16,
},
grid: {
size: 10,
visible: false,
},
background: false,
panning: {
enabled: true,
eventTypes: ['leftMouseDown'],
},
mousewheel: {
enabled: false,
factor: 1.2,
zoomAtMousePosition: true,
},
highlighting: {
default: {
name: 'stroke',
args: {
padding: 3,
},
},
nodeAvailable: {
name: 'className',
args: {
className: Config.prefix('available-node'),
},
},
magnetAvailable: {
name: 'className',
args: {
className: Config.prefix('available-magnet'),
},
},
},
connecting: {
snap: false,
allowLoop: true,
allowNode: true,
allowEdge: false,
allowPort: true,
allowBlank: true,
allowMulti: true,
highlight: false,
anchor: 'center',
edgeAnchor: 'ratio',
connectionPoint: 'boundary',
router: 'normal',
connector: 'normal',
validateConnection(this: Graph, { type, sourceView, targetView }) {
const view = type === 'target' ? targetView : sourceView
return view != null
},
createEdge() {
return new StandardEdge()
},
},
translating: {
restrict: false,
},
embedding: {
enabled: false,
findParent: 'bbox',
frontOnly: true,
validate: () => true,
},
moveThreshold: 0,
clickThreshold: 0,
magnetThreshold: 0,
preventDefaultDblClick: true,
preventDefaultMouseDown: false,
preventDefaultContextMenu: true,
preventDefaultBlankAction: true,
interacting: {
edgeLabelMovable: false,
},
async: true,
virtual: false,
guard: () => false,
}