UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

486 lines (430 loc) 12.3 kB
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, }