UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

147 lines (134 loc) 7.41 kB
import { Point } from "@devexpress/utils/lib/geometry/point"; import { Metrics } from "@devexpress/utils/lib/geometry/metrics"; import { DiagramItem } from "../../../DiagramItem"; import { Rectangle } from "@devexpress/utils/lib/geometry/rectangle"; import { ConnectorRoutingPenaltyDescription } from "../ConnectorRoutingModel"; export enum RightAngleTurnDirection { Straight, Left, Right, Backwards } export interface IAStarMetrics<TPoint extends Point, TDirection> { distance(point1: TPoint, point2: TPoint): number; penalty(distance: number, position: Point, turnDirection: TDirection, intersectedItems: DiagramItem[]): number; } export class IntersectedShapeMetrics { constructor( readonly description: ConnectorRoutingPenaltyDescription, readonly shapeMargin: number) { } penalty(oldValue: number, distance: number, position: Point, item: DiagramItem) : number { const rectangle = item.rectangle; if(rectangle.containsPoint(position)) return oldValue + this.description.shape * distance; const relativePenalty = this.createRelativeMarginPenalty(position, rectangle, this.shapeMargin); if(relativePenalty > 0) return oldValue + this.description.margin * relativePenalty * distance; return oldValue; } private createRelativeMarginPenalty(point: Point, rectangle: Rectangle, margin: number): number { const isTopPosition = this.isTopPosition(point, rectangle, margin); const isLeftPosition = this.isLeftPosition(point, rectangle, margin); const isBottomPosition = this.isBottomPosition(point, rectangle, margin); const isRightPosition = this.isRightPosition(point, rectangle, margin); if(isTopPosition) { if(isLeftPosition) { const extendedRectangle = rectangle.clone().inflate(margin); const extendedTopLeft = extendedRectangle.createPosition(); const rectangleTopLeft = rectangle.createPosition(); const topLeftDistance = Metrics.euclideanDistance(rectangleTopLeft, extendedTopLeft); const currentDistance = Metrics.euclideanDistance(rectangleTopLeft, point); return 1 - currentDistance / topLeftDistance; } if(isRightPosition) { const extendedRectangle = rectangle.clone().inflate(margin); const extendedTopRight = new Point(extendedRectangle.right, extendedRectangle.y); const rectangleTopRight = new Point(rectangle.right, rectangle.y); const topRightDistance = Metrics.euclideanDistance(rectangleTopRight, extendedTopRight); const currentDistance = Metrics.euclideanDistance(rectangleTopRight, point); return 1 - currentDistance / topRightDistance; } return 1 - (rectangle.y - point.y) / margin; } if(isBottomPosition) { if(isLeftPosition) { const extendedRectangle = rectangle.clone().inflate(margin); const extendedBottomLeft = new Point(extendedRectangle.x, extendedRectangle.bottom); const rectangleBottomLeft = new Point(rectangle.x, rectangle.bottom); const bottomLeftDistance = Metrics.euclideanDistance(rectangleBottomLeft, extendedBottomLeft); const currentDistance = Metrics.euclideanDistance(rectangleBottomLeft, point); return 1 - currentDistance / bottomLeftDistance; } if(isRightPosition) { const extendedRectangle = rectangle.clone().inflate(margin); const extendedBottomRight = new Point(extendedRectangle.right, extendedRectangle.bottom); const rectangleBottomRight = new Point(rectangle.right, rectangle.bottom); const bottomRightDistance = Metrics.euclideanDistance(rectangleBottomRight, extendedBottomRight); const currentDistance = Metrics.euclideanDistance(rectangleBottomRight, point); return 1 - currentDistance / bottomRightDistance; } return 1 - (point.y - rectangle.bottom) / margin; } if(isLeftPosition) return 1 - (rectangle.x - point.x) / margin; if(isRightPosition) return 1 - (point.x - rectangle.right) / margin; return 0; } private isTopPosition(point: Point, rectangle: Rectangle, margin: number): boolean { return point.x > rectangle.x - margin && point.x < rectangle.right + margin && point.y > rectangle.y - margin && point.y < rectangle.y; } private isBottomPosition(point: Point, rectangle: Rectangle, margin: number): boolean { return point.x > rectangle.x - margin && point.x < rectangle.right + margin && point.y > rectangle.bottom && point.y < rectangle.bottom + margin; } private isLeftPosition(point: Point, rectangle: Rectangle, margin: number): boolean { return point.x > rectangle.x - margin && point.x < rectangle.x && point.y > rectangle.y - margin && point.y < rectangle.bottom + margin; } private isRightPosition(point: Point, rectangle: Rectangle, margin: number): boolean { return point.x > rectangle.right && point.x < rectangle.right + margin && point.y > rectangle.y - margin && point.y < rectangle.bottom + margin; } } export class TurnDirectionMetrics { constructor(readonly description: ConnectorRoutingPenaltyDescription) { } penalty(oldValue: number, turnDirection: RightAngleTurnDirection) : number { switch(turnDirection) { case RightAngleTurnDirection.Backwards: return oldValue * this.description.turnBack; case RightAngleTurnDirection.Left: return oldValue * this.description.turnLeft; case RightAngleTurnDirection.Right: return oldValue * this.description.turnRight; } return oldValue; } } export class IntersectedShapesMetrics { constructor(readonly shapeMetrics: IntersectedShapeMetrics) { } penalty(oldValue: number, distance: number, position: Point, intersectedItems: DiagramItem[]) : number { if(!intersectedItems || !intersectedItems.length) return oldValue; let result = oldValue; intersectedItems.forEach(item => result = this.shapeMetrics.penalty(result, distance, position, item)); return result; } } export class AStarMetrics implements IAStarMetrics<Point, RightAngleTurnDirection> { constructor( readonly turnDirectionMetrics : TurnDirectionMetrics, readonly shapesMetrics: IntersectedShapesMetrics) { } distance(point1: Point, point2: Point): number { return Metrics.manhattanDistance(point1, point2); } penalty(distance: number, position: Point, turnDirection: RightAngleTurnDirection, intersectedItems: DiagramItem[]): number { let result = distance; result = this.shapesMetrics.penalty(result, distance, position, intersectedItems); result = this.turnDirectionMetrics.penalty(result, turnDirection); return result; } } export enum IntersectedShapeZone { None = 0, Shape = 1, Margin = 2, }