@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
1,424 lines (1,230 loc) • 37.1 kB
text/typescript
import type { Dom, KeyValue, NumberExt } from '../common'
import { Basecoat, disposable } from '../common'
import { Point, Rectangle, type RectangleLike } from '../geometry'
import type { PointLike } from '../geometry/point'
import type {
CellGetCellsBBoxOptions,
CellRemoveOptions,
CellSetOptions,
CollectionRemoveOptions,
CollectionSetOptions,
EdgeMetadata,
EdgeSetOptions,
NodeMetadata,
} from '../model'
import { Cell, Edge, Model, Node } from '../model'
import type {
AddOptions,
GetPredecessorsOptions,
ToJSONOptions,
FromJSONData,
FromJSONOptions,
GetConnectedEdgesOptions,
GetNeighborsOptions,
GetSubgraphOptions,
GetCellsInAreaOptions,
SearchIterator,
GetShortestPathOptions,
BatchName,
SearchOptions,
} from '../model'
import type { BackgroundOptions } from '../registry'
import {
attrRegistry,
backgroundRegistry,
connectionPointRegistry,
connectorRegistry,
edgeAnchorRegistry,
edgeToolRegistry,
filterRegistry,
gridRegistry,
highlighterRegistry,
markerRegistry,
nodeAnchorRegistry,
nodeToolRegistry,
portLabelLayoutRegistry,
portLayoutRegistry,
routerRegistry,
} from '../registry'
import { Renderer as ViewRenderer } from '../renderer'
import { CellView } from '../view'
import { BackgroundManager as Background } from './background'
import { CoordManager as Coord } from './coord'
import { CSSManager as Css } from './css'
import { DefsManager as Defs } from './defs'
import type { FilterOptions, GradientOptions, MarkerOptions } from './defs'
import type { EventArgs } from './events'
import { GridManager as Grid, GridDrawOptions } from './grid'
import { HighlightManager as Highlight } from './highlight'
import { MouseWheel as Wheel } from './mousewheel'
import { GraphDefinition, GraphManual, getOptions } from './options'
import { PanningManager as Panning } from './panning'
import { SizeManager as Size } from './size'
import { TransformManager as Transform } from './transform'
import type {
GetContentAreaOptions,
ZoomOptions,
ScaleContentToFitOptions,
FitToContentOptions,
FitToContentFullOptions,
CenterOptions,
PositionContentOptions,
Direction,
} from './transform'
import { GraphView } from './view'
import { VirtualRenderManager as VirtualRender } from './virtual-render'
import type { KeyPoint } from '../types'
type FindViewsInAreaOptions = {
strict?: boolean
}
export interface Options extends GraphManual {}
export type GraphPlugin = {
name: string
init: (graph: Graph, ...options: any[]) => any
dispose: () => void
enable?: () => void
disable?: () => void
isEnabled?: () => boolean
}
export class Graph extends Basecoat<EventArgs> {
static toStringTag = `X6.${Graph.name}`
static isGraph(instance: any): instance is Graph {
if (instance == null) {
return false
}
if (instance instanceof Graph) {
return true
}
const tag = instance[Symbol.toStringTag]
if (tag == null || tag === Graph.toStringTag) {
return true
}
return false
}
static render(options: Partial<Options>, data?: FromJSONData): Graph
static render(container: HTMLElement, data?: FromJSONData): Graph
static render(
options: Partial<Options> | HTMLElement,
data?: FromJSONData,
): Graph {
const graph =
options instanceof HTMLElement
? new Graph({ container: options })
: new Graph(options)
if (data != null) {
graph.fromJSON(data)
}
return graph
}
static registerNode = Node.registry.register
static registerEdge = Edge.registry.register
static registerView = CellView.registry.register
static registerAttr = attrRegistry.register
static registerGrid = gridRegistry.register
static registerFilter = filterRegistry.register
static registerNodeTool = nodeToolRegistry.register
static registerEdgeTool = edgeToolRegistry.register
static registerBackground = backgroundRegistry.register
static registerHighlighter = highlighterRegistry.register
static registerPortLayout = portLayoutRegistry.register
static registerPortLabelLayout = portLabelLayoutRegistry.register
static registerMarker = markerRegistry.register
static registerRouter = routerRegistry.register
static registerConnector = connectorRegistry.register
static registerAnchor = nodeAnchorRegistry.register
static registerEdgeAnchor = edgeAnchorRegistry.register
static registerConnectionPoint = connectionPointRegistry.register
static unregisterNode = Node.registry.unregister
static unregisterEdge = Edge.registry.unregister
static unregisterView = CellView.registry.unregister
static unregisterAttr = attrRegistry.unregister
static unregisterGrid = gridRegistry.unregister
static unregisterFilter = filterRegistry.unregister
static unregisterNodeTool = nodeToolRegistry.unregister
static unregisterEdgeTool = edgeToolRegistry.unregister
static unregisterBackground = backgroundRegistry.unregister
static unregisterHighlighter = highlighterRegistry.unregister
static unregisterPortLayout = portLayoutRegistry.unregister
static unregisterPortLabelLayout = portLabelLayoutRegistry.unregister
static unregisterMarker = markerRegistry.unregister
static unregisterRouter = routerRegistry.unregister
static unregisterConnector = connectorRegistry.unregister
static unregisterAnchor = nodeAnchorRegistry.unregister
static unregisterEdgeAnchor = edgeAnchorRegistry.unregister
static unregisterConnectionPoint = connectionPointRegistry.unregister
private installedPlugins: Set<GraphPlugin> = new Set()
public model: Model
public readonly options: GraphDefinition
public readonly css: Css
public readonly view: GraphView
public readonly grid: Grid
public readonly defs: Defs
public readonly coord: Coord
public readonly renderer: ViewRenderer
public readonly highlight: Highlight
public readonly transform: Transform
public readonly background: Background
public readonly panning: Panning
public readonly mousewheel: Wheel
public readonly virtualRender: VirtualRender
public readonly size: Size
public get container() {
return this.options.container
}
protected get [Symbol.toStringTag]() {
return Graph.toStringTag
}
constructor(options: Partial<GraphManual>) {
super()
this.options = getOptions(options)
this.css = new Css(this)
this.view = new GraphView(this)
this.defs = new Defs(this)
this.coord = new Coord(this)
this.transform = new Transform(this)
this.highlight = new Highlight(this)
this.grid = new Grid(this)
this.background = new Background(this)
if (this.options.model) {
this.model = this.options.model
} else {
this.model = new Model()
this.model.graph = this
}
this.renderer = new ViewRenderer(this)
this.panning = new Panning(this)
this.mousewheel = new Wheel(this)
this.virtualRender = new VirtualRender(this)
this.size = new Size(this)
}
// #region model
isNode(cell: Cell): cell is Node {
return cell.isNode()
}
isEdge(cell: Cell): cell is Edge {
return cell.isEdge()
}
resetCells(cells: Cell[], options: CollectionSetOptions = {}) {
this.model.resetCells(cells, options)
return this
}
clearCells(options: CellSetOptions = {}) {
this.model.clear(options)
return this
}
toJSON(options: ToJSONOptions = {}) {
return this.model.toJSON(options)
}
parseJSON(data: FromJSONData) {
return this.model.parseJSON(data)
}
fromJSON(data: FromJSONData, options: FromJSONOptions = {}) {
this.model.fromJSON(data, options)
return this
}
getCellById(id: string) {
return this.model.getCell(id)
}
addNode(metadata: NodeMetadata, options?: AddOptions): Node
addNode(node: Node, options?: AddOptions): Node
addNode(node: Node | NodeMetadata, options: AddOptions = {}): Node {
return this.model.addNode(node, options)
}
addNodes(nodes: (Node | NodeMetadata)[], options: AddOptions = {}) {
return this.addCell(
nodes.map((node) => (Node.isNode(node) ? node : this.createNode(node))),
options,
)
}
createNode(metadata: NodeMetadata) {
return this.model.createNode(metadata)
}
removeNode(nodeId: string, options?: CollectionRemoveOptions): Node | null
removeNode(node: Node, options?: CollectionRemoveOptions): Node | null
removeNode(node: Node | string, options: CollectionRemoveOptions = {}) {
return this.model.removeCell(node as Node, options) as Node
}
addEdge(metadata: EdgeMetadata, options?: AddOptions): Edge
addEdge(edge: Edge, options?: AddOptions): Edge
addEdge(edge: Edge | EdgeMetadata, options: AddOptions = {}): Edge {
return this.model.addEdge(edge, options)
}
addEdges(edges: (Edge | EdgeMetadata)[], options: AddOptions = {}) {
return this.addCell(
edges.map((edge) => (Edge.isEdge(edge) ? edge : this.createEdge(edge))),
options,
)
}
removeEdge(edgeId: string, options?: CollectionRemoveOptions): Edge | null
removeEdge(edge: Edge, options?: CollectionRemoveOptions): Edge | null
removeEdge(edge: Edge | string, options: CollectionRemoveOptions = {}) {
return this.model.removeCell(edge as Edge, options) as Edge
}
createEdge(metadata: EdgeMetadata) {
return this.model.createEdge(metadata)
}
addCell(cell: Cell | Cell[], options: AddOptions = {}) {
this.model.addCell(cell, options)
return this
}
removeCell(cellId: string, options?: CollectionRemoveOptions): Cell | null
removeCell(cell: Cell, options?: CollectionRemoveOptions): Cell | null
removeCell(cell: Cell | string, options: CollectionRemoveOptions = {}) {
return this.model.removeCell(cell as Cell, options)
}
removeCells(cells: (Cell | string)[], options: CellRemoveOptions = {}) {
return this.model.removeCells(cells, options)
}
removeConnectedEdges(cell: Cell | string, options: CellRemoveOptions = {}) {
return this.model.removeConnectedEdges(cell, options)
}
disconnectConnectedEdges(cell: Cell | string, options: EdgeSetOptions = {}) {
this.model.disconnectConnectedEdges(cell, options)
return this
}
hasCell(cellId: string): boolean
hasCell(cell: Cell): boolean
hasCell(cell: string | Cell): boolean {
return this.model.has(cell as Cell)
}
getCells() {
return this.model.getCells()
}
getCellCount() {
return this.model.total()
}
/**
* Returns all the nodes in the graph.
*/
getNodes() {
return this.model.getNodes()
}
/**
* Returns all the edges in the graph.
*/
getEdges() {
return this.model.getEdges()
}
/**
* Returns all outgoing edges for the node.
*/
getOutgoingEdges(cell: Cell | string) {
return this.model.getOutgoingEdges(cell)
}
/**
* Returns all incoming edges for the node.
*/
getIncomingEdges(cell: Cell | string) {
return this.model.getIncomingEdges(cell)
}
/**
* Returns edges connected with cell.
*/
getConnectedEdges(
cell: Cell | string,
options: GetConnectedEdgesOptions = {},
) {
return this.model.getConnectedEdges(cell, options)
}
/**
* Returns an array of all the roots of the graph.
*/
getRootNodes() {
return this.model.getRoots()
}
/**
* Returns an array of all the leafs of the graph.
*/
getLeafNodes() {
return this.model.getLeafs()
}
/**
* Returns `true` if the node is a root node, i.e.
* there is no edges coming to the node.
*/
isRootNode(cell: Cell | string) {
return this.model.isRoot(cell)
}
/**
* Returns `true` if the node is a leaf node, i.e.
* there is no edges going out from the node.
*/
isLeafNode(cell: Cell | string) {
return this.model.isLeaf(cell)
}
/**
* Returns all the neighbors of node in the graph. Neighbors are all
* the nodes connected to node via either incoming or outgoing edge.
*/
getNeighbors(cell: Cell, options: GetNeighborsOptions = {}) {
return this.model.getNeighbors(cell, options)
}
/**
* Returns `true` if `cell2` is a neighbor of `cell1`.
*/
isNeighbor(cell1: Cell, cell2: Cell, options: GetNeighborsOptions = {}) {
return this.model.isNeighbor(cell1, cell2, options)
}
getSuccessors(cell: Cell, options: GetPredecessorsOptions = {}) {
return this.model.getSuccessors(cell, options)
}
/**
* Returns `true` if `cell2` is a successor of `cell1`.
*/
isSuccessor(cell1: Cell, cell2: Cell, options: GetPredecessorsOptions = {}) {
return this.model.isSuccessor(cell1, cell2, options)
}
getPredecessors(cell: Cell, options: GetPredecessorsOptions = {}) {
return this.model.getPredecessors(cell, options)
}
/**
* Returns `true` if `cell2` is a predecessor of `cell1`.
*/
isPredecessor(
cell1: Cell,
cell2: Cell,
options: GetPredecessorsOptions = {},
) {
return this.model.isPredecessor(cell1, cell2, options)
}
getCommonAncestor(...cells: (Cell | null | undefined)[]) {
return this.model.getCommonAncestor(...cells)
}
/**
* Returns an array of cells that result from finding nodes/edges that
* are connected to any of the cells in the cells array. This function
* loops over cells and if the current cell is a edge, it collects its
* source/target nodes; if it is an node, it collects its incoming and
* outgoing edges if both the edge terminal (source/target) are in the
* cells array.
*/
getSubGraph(cells: Cell[], options: GetSubgraphOptions = {}) {
return this.model.getSubGraph(cells, options)
}
/**
* Clones the whole subgraph (including all the connected links whose
* source/target is in the subgraph). If `options.deep` is `true`, also
* take into account all the embedded cells of all the subgraph cells.
*
* Returns a map of the form: { [original cell ID]: [clone] }.
*/
cloneSubGraph(cells: Cell[], options: GetSubgraphOptions = {}) {
return this.model.cloneSubGraph(cells, options)
}
cloneCells(cells: Cell[]) {
return this.model.cloneCells(cells)
}
/**
* Returns an array of nodes whose bounding box contains point.
* Note that there can be more then one node as nodes might overlap.
*/
getNodesFromPoint(x: number, y: number): Node[]
getNodesFromPoint(p: PointLike): Node[]
getNodesFromPoint(x: number | PointLike, y?: number) {
return this.model.getNodesFromPoint(x as number, y as number)
}
/**
* Returns an array of nodes whose bounding box top/left coordinate
* falls into the rectangle.
*/
getNodesInArea(
x: number,
y: number,
w: number,
h: number,
options?: GetCellsInAreaOptions,
): Node[]
getNodesInArea(rect: RectangleLike, options?: GetCellsInAreaOptions): Node[]
getNodesInArea(
x: number | RectangleLike,
y?: number | GetCellsInAreaOptions,
w?: number,
h?: number,
options?: GetCellsInAreaOptions,
): Node[] {
return this.model.getNodesInArea(
x as number,
y as number,
w as number,
h as number,
options,
)
}
getNodesUnderNode(
node: Node,
options: {
by?: 'bbox' | KeyPoint
} = {},
) {
return this.model.getNodesUnderNode(node, options)
}
searchCell(
cell: Cell,
iterator: SearchIterator,
options: SearchOptions = {},
) {
this.model.search(cell, iterator, options)
return this
}
/** *
* Returns an array of IDs of nodes on the shortest
* path between source and target.
*/
getShortestPath(
source: Cell | string,
target: Cell | string,
options: GetShortestPathOptions = {},
) {
return this.model.getShortestPath(source, target, options)
}
/**
* Returns the bounding box that surrounds all cells in the graph.
*/
getAllCellsBBox() {
return this.model.getAllCellsBBox()
}
/**
* Returns the bounding box that surrounds all the given cells.
*/
getCellsBBox(cells: Cell[], options: CellGetCellsBBoxOptions = {}) {
return this.model.getCellsBBox(cells, options)
}
startBatch(name: string | BatchName, data: KeyValue = {}) {
this.model.startBatch(name as BatchName, data)
}
stopBatch(name: string | BatchName, data: KeyValue = {}) {
this.model.stopBatch(name as BatchName, data)
}
batchUpdate<T>(execute: () => T, data?: KeyValue): T
batchUpdate<T>(name: string | BatchName, execute: () => T, data?: KeyValue): T
batchUpdate<T>(
arg1: string | BatchName | (() => T),
arg2?: (() => T) | KeyValue,
arg3?: KeyValue,
): T {
const name = typeof arg1 === 'string' ? arg1 : 'update'
const execute = typeof arg1 === 'string' ? (arg2 as () => T) : arg1
const data = typeof arg2 === 'function' ? arg3 : arg2
this.startBatch(name, data)
const result = execute()
this.stopBatch(name, data)
return result
}
updateCellId(cell: Cell, newId: string) {
return this.model.updateCellId(cell, newId)
}
// #endregion
// #region view
findView(ref: Cell | Element) {
if (Cell.isCell(ref)) {
return this.findViewByCell(ref)
}
return this.findViewByElem(ref)
}
findViews(ref: PointLike | RectangleLike) {
if (Rectangle.isRectangleLike(ref)) {
return this.findViewsInArea(ref)
}
if (Point.isPointLike(ref)) {
return this.findViewsFromPoint(ref)
}
return []
}
findViewByCell(cellId: string | number): CellView | null
findViewByCell(cell: Cell | null): CellView | null
findViewByCell(
cell: Cell | string | number | null | undefined,
): CellView | null {
return this.renderer.findViewByCell(cell as Cell)
}
findViewByElem(elem: string | Element | undefined | null) {
return this.renderer.findViewByElem(elem)
}
findViewsFromPoint(x: number, y: number): CellView[]
findViewsFromPoint(p: PointLike): CellView[]
findViewsFromPoint(x: number | PointLike, y?: number) {
const p = typeof x === 'number' ? { x, y: y as number } : x
return this.renderer.findViewsFromPoint(p)
}
findViewsInArea(
x: number,
y: number,
width: number,
height: number,
options?: FindViewsInAreaOptions,
): CellView[]
findViewsInArea(
rect: RectangleLike,
options?: FindViewsInAreaOptions,
): CellView[]
findViewsInArea(
x: number | RectangleLike,
y?: number | FindViewsInAreaOptions,
width?: number,
height?: number,
options?: FindViewsInAreaOptions,
) {
const rect =
typeof x === 'number'
? {
x,
y: y as number,
width: width as number,
height: height as number,
}
: x
const localOptions =
typeof x === 'number' ? options : (y as FindViewsInAreaOptions)
return this.renderer.findViewsInArea(rect, localOptions)
}
// #endregion
// #region transform
/**
* Returns the current transformation matrix of the graph.
*/
matrix(): DOMMatrix
/**
* Sets new transformation with the given `matrix`
*/
matrix(mat: DOMMatrix | Dom.MatrixLike | null): this
matrix(mat?: DOMMatrix | Dom.MatrixLike | null) {
if (typeof mat === 'undefined') {
return this.transform.getMatrix()
}
this.transform.setMatrix(mat)
return this
}
resize(width?: number, height?: number) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.resize(width, height)
} else {
this.transform.resize(width, height)
}
return this
}
scale(): Dom.Scale
scale(sx: number, sy?: number, cx?: number, cy?: number): this
scale(sx?: number, sy: number = sx as number, cx = 0, cy = 0) {
if (typeof sx === 'undefined') {
return this.transform.getScale()
}
this.transform.scale(sx, sy, cx, cy)
return this
}
zoom(): number
zoom(factor: number, options?: ZoomOptions): this
zoom(factor?: number, options?: ZoomOptions) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
if (typeof factor === 'undefined') {
return scroller.zoom()
}
scroller.zoom(factor, options)
} else {
if (typeof factor === 'undefined') {
return this.transform.getZoom()
}
this.transform.zoom(factor, options)
}
return this
}
zoomTo(factor: number, options: Omit<ZoomOptions, 'absolute'> = {}) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.zoom(factor, { ...options, absolute: true })
} else {
this.transform.zoom(factor, { ...options, absolute: true })
}
return this
}
zoomToRect(
rect: RectangleLike,
options: ScaleContentToFitOptions & ScaleContentToFitOptions = {},
) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.zoomToRect(rect, options)
} else {
this.transform.zoomToRect(rect, options)
}
return this
}
zoomToFit(options: GetContentAreaOptions & ScaleContentToFitOptions = {}) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.zoomToFit(options)
} else {
this.transform.zoomToFit(options)
}
return this
}
rotate(): Dom.Rotation
rotate(angle: number, cx?: number, cy?: number): this
rotate(angle?: number, cx?: number, cy?: number) {
if (typeof angle === 'undefined') {
return this.transform.getRotation()
}
this.transform.rotate(angle, cx, cy)
return this
}
translate(): Dom.Translation
translate(tx: number, ty: number): this
translate(tx?: number, ty?: number) {
if (typeof tx === 'undefined') {
return this.transform.getTranslation()
}
this.transform.translate(tx, ty as number)
return this
}
translateBy(dx: number, dy: number): this {
const ts = this.translate()
const tx = ts.tx + dx
const ty = ts.ty + dy
return this.translate(tx, ty)
}
getGraphArea() {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
const area = scroller.getVisibleArea?.()
if (area) return area
}
return this.transform.getGraphArea()
}
getContentArea(options: GetContentAreaOptions = {}) {
return this.transform.getContentArea(options)
}
getContentBBox(options: GetContentAreaOptions = {}) {
return this.transform.getContentBBox(options)
}
fitToContent(
gridWidth?: number,
gridHeight?: number,
padding?: NumberExt.SideOptions,
options?: FitToContentOptions,
): Rectangle
fitToContent(options?: FitToContentFullOptions): Rectangle
fitToContent(
gridWidth?: number | FitToContentFullOptions,
gridHeight?: number,
padding?: NumberExt.SideOptions,
options?: FitToContentOptions,
) {
return this.transform.fitToContent(gridWidth, gridHeight, padding, options)
}
scaleContentToFit(options: ScaleContentToFitOptions = {}) {
this.transform.scaleContentToFit(options)
return this
}
/**
* Position the center of graph to the center of the viewport.
*/
center(options?: CenterOptions) {
return this.centerPoint(options)
}
/**
* Position the point (x,y) on the graph (in local coordinates) to the
* center of the viewport. If only one of the coordinates is specified,
* only center along the specified dimension and keep the other coordinate
* unchanged.
*/
centerPoint(x: number, y: null | number, options?: CenterOptions): this
centerPoint(x: null | number, y: number, options?: CenterOptions): this
centerPoint(optons?: CenterOptions): this
centerPoint(
x?: number | null | CenterOptions,
y?: number | null,
options?: CenterOptions,
) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.centerPoint(x as number, y as number, options)
} else {
this.transform.centerPoint(x as number, y as number)
}
return this
}
centerContent(options?: PositionContentOptions) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.centerContent(options)
} else {
this.transform.centerContent(options)
}
return this
}
centerCell(cell: Cell, options?: PositionContentOptions) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.centerCell(cell, options)
} else {
this.transform.centerCell(cell)
}
return this
}
positionPoint(
point: PointLike,
x: number | string,
y: number | string,
options: CenterOptions = {},
) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.positionPoint(point, x, y, options)
} else {
this.transform.positionPoint(point, x, y)
}
return this
}
positionRect(
rect: RectangleLike,
direction: Direction,
options?: CenterOptions,
) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.positionRect(rect, direction, options)
} else {
this.transform.positionRect(rect, direction)
}
return this
}
positionCell(cell: Cell, direction: Direction, options?: CenterOptions) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.positionCell(cell, direction, options)
} else {
this.transform.positionCell(cell, direction)
}
return this
}
positionContent(pos: Direction, options?: PositionContentOptions) {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.positionContent(pos, options)
} else {
this.transform.positionContent(pos, options)
}
return this
}
// #endregion
// #region coord
snapToGrid(p: PointLike): Point
snapToGrid(x: number, y: number): Point
snapToGrid(x: number | PointLike, y?: number) {
return this.coord.snapToGrid(x, y)
}
pageToLocal(rect: RectangleLike): Rectangle
pageToLocal(x: number, y: number, width: number, height: number): Rectangle
pageToLocal(p: PointLike): Point
pageToLocal(x: number, y: number): Point
pageToLocal(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.pageToLocalRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.pageToLocalRect(x, y, width, height)
}
return this.coord.pageToLocalPoint(x, y)
}
localToPage(rect: RectangleLike): Rectangle
localToPage(x: number, y: number, width: number, height: number): Rectangle
localToPage(p: PointLike): Point
localToPage(x: number, y: number): Point
localToPage(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.localToPageRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.localToPageRect(x, y, width, height)
}
return this.coord.localToPagePoint(x, y)
}
clientToLocal(rect: RectangleLike): Rectangle
clientToLocal(x: number, y: number, width: number, height: number): Rectangle
clientToLocal(p: PointLike): Point
clientToLocal(x: number, y: number): Point
clientToLocal(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.clientToLocalRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.clientToLocalRect(x, y, width, height)
}
return this.coord.clientToLocalPoint(x, y)
}
localToClient(rect: RectangleLike): Rectangle
localToClient(x: number, y: number, width: number, height: number): Rectangle
localToClient(p: PointLike): Point
localToClient(x: number, y: number): Point
localToClient(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.localToClientRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.localToClientRect(x, y, width, height)
}
return this.coord.localToClientPoint(x, y)
}
/**
* Transform the rectangle `rect` defined in the local coordinate system to
* the graph coordinate system.
*/
localToGraph(rect: RectangleLike): Rectangle
/**
* Transform the rectangle `x`, `y`, `width`, `height` defined in the local
* coordinate system to the graph coordinate system.
*/
localToGraph(x: number, y: number, width: number, height: number): Rectangle
/**
* Transform the point `p` defined in the local coordinate system to
* the graph coordinate system.
*/
localToGraph(p: PointLike): Point
/**
* Transform the point `x`, `y` defined in the local coordinate system to
* the graph coordinate system.
*/
localToGraph(x: number, y: number): Point
localToGraph(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.localToGraphRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.localToGraphRect(x, y, width, height)
}
return this.coord.localToGraphPoint(x, y)
}
graphToLocal(rect: RectangleLike): Rectangle
graphToLocal(x: number, y: number, width: number, height: number): Rectangle
graphToLocal(p: PointLike): Point
graphToLocal(x: number, y: number): Point
graphToLocal(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.graphToLocalRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.graphToLocalRect(x, y, width, height)
}
return this.coord.graphToLocalPoint(x, y)
}
clientToGraph(rect: RectangleLike): Rectangle
clientToGraph(x: number, y: number, width: number, height: number): Rectangle
clientToGraph(p: PointLike): Point
clientToGraph(x: number, y: number): Point
clientToGraph(
x: number | PointLike | RectangleLike,
y?: number,
width?: number,
height?: number,
) {
if (Rectangle.isRectangleLike(x)) {
return this.coord.clientToGraphRect(x)
}
if (
typeof x === 'number' &&
typeof y === 'number' &&
typeof width === 'number' &&
typeof height === 'number'
) {
return this.coord.clientToGraphRect(x, y, width, height)
}
return this.coord.clientToGraphPoint(x, y)
}
// #endregion
// #region defs
defineFilter(options: FilterOptions) {
return this.defs.filter(options)
}
defineGradient(options: GradientOptions) {
return this.defs.gradient(options)
}
defineMarker(options: MarkerOptions) {
return this.defs.marker(options)
}
// #endregion
// #region grid
getGridSize() {
return this.grid.getGridSize()
}
setGridSize(gridSize: number) {
this.grid.setGridSize(gridSize)
return this
}
showGrid() {
this.grid.show()
return this
}
hideGrid() {
this.grid.hide()
return this
}
clearGrid() {
this.grid.clear()
return this
}
drawGrid(options?: GridDrawOptions) {
this.grid.draw(options)
return this
}
// #endregion
// #region background
updateBackground() {
this.background.update()
return this
}
drawBackground(options?: BackgroundOptions, onGraph?: boolean) {
const scroller = this.getPlugin<any>('scroller')
if (scroller != null && (this.options.background == null || !onGraph)) {
scroller.drawBackground(options, onGraph)
} else {
this.background.draw(options)
}
return this
}
clearBackground(onGraph?: boolean) {
const scroller = this.getPlugin<any>('scroller')
if (scroller != null && (this.options.background == null || !onGraph)) {
scroller.clearBackground(onGraph)
} else {
this.background.clear()
}
return this
}
// #endregion
// #region virtual-render
enableVirtualRender() {
this.virtualRender.enableVirtualRender()
return this
}
disableVirtualRender() {
this.virtualRender.disableVirtualRender()
return this
}
// #endregion
// #region mousewheel
isMouseWheelEnabled() {
return !this.mousewheel.disabled
}
enableMouseWheel() {
this.mousewheel.enable()
return this
}
disableMouseWheel() {
this.mousewheel.disable()
return this
}
toggleMouseWheel(enabled?: boolean) {
if (enabled == null) {
if (this.isMouseWheelEnabled()) {
this.disableMouseWheel()
} else {
this.enableMouseWheel()
}
} else if (enabled) {
this.enableMouseWheel()
} else {
this.disableMouseWheel()
}
return this
}
// #endregion
// #region panning
isPannable() {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
return scroller.isPannable()
}
return this.panning.pannable
}
enablePanning() {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.enablePanning()
} else {
this.panning.enablePanning()
}
return this
}
disablePanning() {
const scroller = this.getPlugin<any>('scroller')
if (scroller) {
scroller.disablePanning()
} else {
this.panning.disablePanning()
}
return this
}
togglePanning(pannable?: boolean) {
if (pannable == null) {
if (this.isPannable()) {
this.disablePanning()
} else {
this.enablePanning()
}
} else if (pannable !== this.isPannable()) {
if (pannable) {
this.enablePanning()
} else {
this.disablePanning()
}
}
return this
}
// #endregion
// #region plugin
handleScrollerPluginStateChange(
plugin: GraphPlugin,
isBeingEnabled: boolean,
) {
if (plugin.name === 'scroller') {
if (isBeingEnabled) {
this.virtualRender.onScrollerReady(plugin)
} else {
this.virtualRender.unbindScroller()
}
}
}
use(plugin: GraphPlugin, ...options: any[]) {
if (!this.installedPlugins.has(plugin)) {
this.installedPlugins.add(plugin)
plugin.init(this, ...options)
this.handleScrollerPluginStateChange(plugin, true)
}
return this
}
getPlugin<T extends GraphPlugin>(pluginName: string): T | undefined {
return Array.from(this.installedPlugins).find(
(plugin) => plugin.name === pluginName,
) as T
}
getPlugins<T extends GraphPlugin[]>(pluginName: string[]): T | undefined {
return Array.from(this.installedPlugins).filter((plugin) =>
pluginName.includes(plugin.name),
) as T
}
enablePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin?.enable?.()
this.handleScrollerPluginStateChange(plugin, true)
})
return this
}
disablePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin?.disable?.()
this.handleScrollerPluginStateChange(plugin, false)
})
return this
}
isPluginEnabled(pluginName: string) {
const pluginIns = this.getPlugin(pluginName)
return pluginIns?.isEnabled?.()
}
disposePlugins(plugins: string[] | string) {
let postPlugins = plugins
if (!Array.isArray(postPlugins)) {
postPlugins = [postPlugins]
}
const aboutToChangePlugins = this.getPlugins(postPlugins)
aboutToChangePlugins?.forEach((plugin) => {
plugin.dispose()
this.handleScrollerPluginStateChange(plugin, false)
this.installedPlugins.delete(plugin)
})
return this
}
// #endregion
// #region dispose
dispose(clean = true) {
if (clean) {
this.model.dispose()
}
this.css.dispose()
this.defs.dispose()
this.grid.dispose()
this.coord.dispose()
this.transform.dispose()
this.highlight.dispose()
this.background.dispose()
this.mousewheel.dispose()
this.panning.dispose()
this.view.dispose()
this.renderer.dispose()
this.installedPlugins.forEach((plugin) => {
plugin.dispose()
})
}
// #endregion
}