import { LinkRouter , RoutedLinks , Vertex } from '../customization/props' ; import { DiagramModel } from './model' ; import { Link as DiagramLink , Element as DiagramElement } from './elements' ; import { Vector } from './geometry' ; export class DefaultLinkRouter implements LinkRouter { constructor ( private gap = 20 ) { } route ( model ) { const routings = new Map ( ) ; for ( const link of model . links ) { if ( routings . has ( link . id ) ) { continue ; } const { sourceId , targetId } = link ; if ( ! sourceId || ! targetId ) { continue ; } else if ( sourceId === targetId ) { this . routeFeedbackSiblingLinks ( model , sourceId , routings ) ; } else { this . routeNormalSiblingLinks ( model , sourceId , targetId , routings ) ; } } return routings ; } private routeFeedbackSiblingLinks ( model , elementId , routings ) { const element = model . getElement ( elementId ) ; const { x , y } = element . position ; const { width , height } = element . size ; let index = 0 ; for ( const sibling of element . links ) { if ( routings . has ( sibling . id ) || hasUserPlacedVertices ( sibling ) ) { continue ; } const { sourceId , targetId } = sibling ; if ( sourceId === targetId ) { const offset = this . gap * ( index + 1 ) ; const vertices = [ { x : x - offset , y : y + height / 2 } , { x : x - offset , y : y - offset } , { x : x + width / 2 , y : y - offset } , ] ; routings . set ( sibling . id , { linkId : sibling . id , vertices } ) ; index ++ ; } } } private routeNormalSiblingLinks ( model , sourceId , targetId , routings ) { const source = model . getElement ( sourceId ) ; const target = model . getElement ( targetId ) ; const sourceCenter = centerOfElement ( source ) ; const targetCenter = centerOfElement ( target ) ; const midPoint = { x : ( sourceCenter . x + targetCenter . x ) / 2 , y : ( sourceCenter . y + targetCenter . y ) / 2 , } ; const direction = Vector . normalize ( { x : targetCenter . x - sourceCenter . x , y : targetCenter . y - sourceCenter . y , } ) ; const siblings = source . links . filter ( ( link ) => ( link . sourceId === targetId || link . targetId === targetId ) && ! routings . has ( link . id ) && ! hasUserPlacedVertices ( link ) ) ; if ( siblings . length <= 1 ) { return ; } const indexModifier = siblings . length % 2 ? 0 : 1 ; siblings . forEach ( ( sibling , siblingIndex ) => { const index = siblingIndex + indexModifier ; const offset = this . gap * Math . ceil ( index / 2 ) - ( indexModifier ? this . gap / 2 : 0 ) ; const offsetDirection = index % 2 ? { x : - direction . y , y : direction . x } : { x : direction . y , y : - direction . x } ; const vertex = { x : midPoint . x + offsetDirection . x * offset , y : midPoint . y + offsetDirection . y * offset , } ; routings . set ( sibling . id , { linkId : sibling . id , vertices : [ vertex ] , labelTextAnchor : this . getLabelAlignment ( direction , siblingIndex , siblings . length ) , } ) ; } ) ; } private getLabelAlignment ( connectionDirection , siblingIndex , siblingCount ) { const angle = Math . atan2 ( connectionDirection . y , connectionDirection . x ) ; const absoluteAngle = Math . abs ( angle ) ; const isHorizontal = absoluteAngle < ( Math . PI * 1 ) / 8 || absoluteAngle > ( Math . PI * 7 ) / 8 ; const isTop = angle < 0 ; const isBottom = angle > 0 ; const firstOuter = siblingCount - 2 ; const secondOuter = siblingCount - 1 ; if ( ! isHorizontal ) { if ( ( isTop && siblingIndex === secondOuter ) || ( isBottom && siblingIndex === firstOuter ) ) { return 'end' ; } else if ( ( isTop && siblingIndex === firstOuter ) || ( isBottom && siblingIndex === secondOuter ) ) { return 'start' ; } } return 'middle' ; } } function hasUserPlacedVertices ( link ) { const vertices = link . vertices ; return vertices && vertices . length > 0 ; } function centerOfElement ( element ) { const { x , y } = element . position ; const { width , height } = element . size ; return { x : x + width / 2 , y : y + height / 2 } ; }