@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
234 lines (200 loc) • 5.07 kB
text/typescript
import { NumberExt } from '../../../util'
import { Point, Rectangle, Angle } from '../../../geometry'
import { Edge } from '../../../model'
import { EdgeView } from '../../../view'
import { orth } from '../orth'
import { Router } from '../index'
export type Direction = 'top' | 'right' | 'bottom' | 'left'
type Callable<T> = T | ((this: ManhattanRouterOptions) => T)
export interface ResolvedOptions {
/**
* The size of step to find a route (the grid of the manhattan pathfinder).
*/
step: number
/**
* The number of route finding loops that cause the router to abort returns
* fallback route instead.
*/
maxLoopCount: number
/**
* The number of decimal places to round floating point coordinates.
*/
precision: number
/**
* The maximum change of direction.
*/
maxDirectionChange: number
/**
* Should the router use perpendicular edgeView option? Does not connect
* to the anchor of node but rather a point close-by that is orthogonal.
*/
perpendicular: boolean
/**
* Should the source and/or target not be considered as obstacles?
*/
excludeTerminals: Edge.TerminalType[]
/**
* Should certain types of nodes not be considered as obstacles?
*/
excludeShapes: string[]
/**
* Possible starting directions from a node.
*/
startDirections: Direction[]
/**
* Possible ending directions to a node.
*/
endDirections: Direction[]
/**
* Specify the directions used above and what they mean
*/
directionMap: {
top: Point.PointLike
right: Point.PointLike
bottom: Point.PointLike
left: Point.PointLike
}
/**
* Returns the cost of an orthogonal step.
*/
cost: number
/**
* Returns an array of directions to find next points on the route different
* from start/end directions.
*/
directions: {
cost: number
offsetX: number
offsetY: number
angle?: number
gridOffsetX?: number
gridOffsetY?: number
}[]
/**
* A penalty received for direction change.
*/
penalties: {
[key: number]: number
}
padding?: {
top: number
right: number
bottom: number
left: number
}
/**
* The padding applied on the element bounding boxes.
*/
paddingBox: Rectangle.RectangleLike
fallbackRouter: Router.Definition<any>
draggingRouter?:
| ((
this: EdgeView,
dragFrom: Point.PointLike,
dragTo: Point.PointLike,
options: ResolvedOptions,
) => Point[])
| null
fallbackRoute?: (
this: EdgeView,
from: Point,
to: Point,
options: ResolvedOptions,
) => Point[] | null
previousDirectionAngle?: number | null
}
export type ManhattanRouterOptions = {
[Key in keyof ResolvedOptions]: Callable<ResolvedOptions[Key]>
}
export const defaults: ManhattanRouterOptions = {
step: 10,
maxLoopCount: 2000,
precision: 1,
maxDirectionChange: 90,
perpendicular: true,
excludeTerminals: [],
excludeShapes: [], // ['text']
startDirections: ['top', 'right', 'bottom', 'left'],
endDirections: ['top', 'right', 'bottom', 'left'],
directionMap: {
top: { x: 0, y: -1 },
right: { x: 1, y: 0 },
bottom: { x: 0, y: 1 },
left: { x: -1, y: 0 },
},
cost() {
const step = resolve(this.step, this)
return step
},
directions() {
const step = resolve(this.step, this)
const cost = resolve(this.cost, this)
return [
{ cost, offsetX: step, offsetY: 0 },
{ cost, offsetX: 0, offsetY: step },
{ cost, offsetX: -step, offsetY: 0 },
{ cost, offsetX: 0, offsetY: -step },
]
},
penalties() {
const step = resolve(this.step, this)
return {
0: 0,
45: step / 2,
90: step / 2,
}
},
paddingBox() {
const step = resolve(this.step, this)
return {
x: -step,
y: -step,
width: 2 * step,
height: 2 * step,
}
},
fallbackRouter: orth,
draggingRouter: null,
}
export function resolve<T>(
input: Callable<T>,
options: ManhattanRouterOptions,
) {
if (typeof input === 'function') {
return input.call(options)
}
return input
}
export function resolveOptions(options: ManhattanRouterOptions) {
const result = Object.keys(options).reduce(
(memo, key: keyof ResolvedOptions) => {
const ret = memo as any
if (
key === 'fallbackRouter' ||
key === 'draggingRouter' ||
key === 'fallbackRoute'
) {
ret[key] = options[key]
} else {
ret[key] = resolve(options[key], options)
}
return memo
},
{} as ResolvedOptions,
)
if (result.padding) {
const sides = NumberExt.normalizeSides(result.padding)
options.paddingBox = {
x: -sides.left,
y: -sides.top,
width: sides.left + sides.right,
height: sides.top + sides.bottom,
}
}
result.directions.forEach((direction) => {
const point1 = new Point(0, 0)
const point2 = new Point(direction.offsetX, direction.offsetY)
direction.angle = Angle.normalize(point1.theta(point2))
})
return result
}