UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering.

234 lines (200 loc) 5.07 kB
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 }