import { Component , Injector , Input , OnDestroy , OnInit , Optional , SkipSelf } from '@angular/core' ; import * as dagre from 'dagre' ; import { Subscription } from 'rxjs' ; import * as THREE from 'three' ; import { AnimationService } from '../../../animation' ; import { AbstractObject3D , LineConnectorComponent } from '../../../object' ; import { RendererService } from '../../../renderer' ; import { provideParent } from '../../../util' ; import { DagreLayoutComponent } from './dagre-layout.component' ; export enum LineEndType { none = 'none' , circle = 'circle' , arrow = 'arrow' } export enum EdgeType { sequence = 'sequence' , association = 'association' , message = 'message' , line = 'line' } @ Component ( { selector : 'atft-dagre-edge' , providers : [ provideParent ( DagreEdgeComponent ) ] , template : '<ng-content></ng-content>' } ) export class DagreEdgeComponent extends LineConnectorComponent implements OnInit , OnDestroy { @ Input ( ) from : string ; @ Input ( ) to : string ; @ Input ( ) startType : LineEndType = LineEndType . circle ; @ Input ( ) endType : LineEndType = LineEndType . arrow ; @ Input ( ) set type ( val ) { switch ( val ) { case EdgeType . association : this . animated = false ; this . solid = false ; this . startType = LineEndType . none ; this . endType = LineEndType . arrow ; break ; case EdgeType . message : this . animated = true ; this . solid = false ; this . dashSize = 1 ; this . startType = LineEndType . circle ; this . endType = LineEndType . arrow ; break ; case EdgeType . line : this . animated = false ; this . solid = true ; this . startType = LineEndType . none ; this . endType = LineEndType . none ; break ; case EdgeType . sequence : this . animated = false ; this . solid = true ; this . startType = LineEndType . none ; this . endType = LineEndType . arrow ; break ; default : this . animated = true ; this . solid = false ; this . dashSize = 4 ; this . startType = LineEndType . circle ; this . endType = LineEndType . arrow ; } } protected lineStart : THREE . Mesh ; protected lineEnd : THREE . Mesh ; protected positions : Array < number > ; protected dagreLayout : DagreLayoutComponent ; protected graphUpdated : Subscription ; constructor ( protected rendererService , @ SkipSelf ( ) @ Optional ( ) protected parent , protected animationService , protected injector ) { super ( rendererService , parent , animationService ) ; this . dagreLayout = this . injector . get < DagreLayoutComponent > ( DagreLayoutComponent ) ; if ( ! this . dagreLayout ) { console . warn ( 'DagreEdgeComponent.constructor: atft-dagre-layout not found!' ) ; } this . syncGraph = this . syncGraph . bind ( this ) ; this . graphUpdated = this . dagreLayout . updated . subscribe ( this . syncGraph ) ; } protected newObject3DInstance ( ) { const lineObject = super . newObject3DInstance ( ) ; this . appendLineEnds ( lineObject ) ; return lineObject ; } protected appendLineEnds ( lineObject ) { const material = new THREE . MeshBasicMaterial ( { color : this . materialColor , opacity : this . opacity , transparent : this . opacity < 1 , depthWrite : true } ) ; const startGeometry = this . getConnectorEndGeometry ( this . startType ) ; if ( startGeometry ) { this . lineStart = new THREE . Mesh ( startGeometry , material ) ; lineObject . add ( this . lineStart ) ; } const endGeometry = this . getConnectorEndGeometry ( this . endType ) ; if ( endGeometry ) { this . lineEnd = new THREE . Mesh ( endGeometry , material ) ; lineObject . add ( this . lineEnd ) ; } } protected getConnectorEndGeometry ( type ) { switch ( type ) { case LineEndType . circle : return new THREE . CircleGeometry ( 0.7 , 16 ) ; break ; case LineEndType . arrow : const shape = new THREE . Shape ( ) ; shape . moveTo ( 0 , - 0.5 ) ; shape . lineTo ( 1 , 2 ) ; shape . lineTo ( 0 , 1.7 ) ; shape . lineTo ( - 1 , 2 ) ; return new THREE . ShapeBufferGeometry ( shape ) ; break ; default : return undefined ; } } ngOnInit ( ) { super . ngOnInit ( ) ; this . addEdge ( ) ; } protected addEdge ( ) { if ( this . dagreLayout && this . dagreLayout . getGraphModel ( ) ) { this . dagreLayout . getChildren ( ) . push ( this ) ; if ( this . from && this . to ) { this . dagreLayout . getGraphModel ( ) . edges . push ( { name : this . name , from : this . from , to : this . to } ) ; } else { console . warn ( 'DagreEdgeComponent.addChild: edge source/target is undefined' ) ; } this . dagreLayout . refreshLayout ( ) ; } } ngOnDestroy ( ) { super . ngOnDestroy ( ) ; this . removeEdge ( ) ; } protected removeEdge ( ) { if ( this . dagreLayout && this . dagreLayout . getGraphModel ( ) ) { this . graphUpdated ?. unsubscribe ( ) ; this . dagreLayout . removeChildByName ( this . name ) ; this . dagreLayout . getGraphModel ( ) . edges = this . dagreLayout . getGraphModel ( ) . edges . filter ( i => i . name !== this . name ) ; this . dagreLayout . refreshLayout ( ) ; } } protected syncGraph ( ) { if ( this . object ) { this . syncGraphEdges ( this . dagreLayout . getGraph ( ) ) ; } } protected syncGraphEdges ( g ) { g . edges ( ) . forEach ( ( e ) => { const edge = g . edge ( e ) ; if ( edge . name === this . name ) { this . positions = [ ] ; edge . points . forEach ( p => { if ( ! Number . isNaN ( p . x ) && ! Number . isNaN ( p . y ) ) { this . positions . push ( p . x , p . y , 0.1 ) ; } } ) ; this . updateEnds ( ) ; this . updateLineGeometry ( ) ; } } ) ; } private updateEnds ( ) { const p = this . positions ; if ( p ?. length >= 9 ) { this . updateEnd ( this . lineStart , new THREE . Vector3 ( p [ 3 ] , p [ 4 ] , p [ 5 ] ) , new THREE . Vector3 ( p [ 0 ] , p [ 1 ] , p [ 2 ] ) ) ; this . updateEnd ( this . lineEnd , new THREE . Vector3 ( p [ p . length - 6 ] , p [ p . length - 5 ] , p [ p . length - 4 ] ) , new THREE . Vector3 ( p [ p . length - 3 ] , p [ p . length - 2 ] , p [ p . length - 1 ] ) ) ; } } private updateEnd ( lineSide , prevPoint , endPoint ) { if ( lineSide ) { const direction = prevPoint . clone ( ) . sub ( endPoint ) ; let angle = direction . angleTo ( new THREE . Vector3 ( 0 , 1 , 0 ) ) ; angle = prevPoint . x < endPoint . x ? angle : - angle ; lineSide . rotation . set ( 0 , 0 , angle ) ; lineSide . position . set ( endPoint . x || 0 , endPoint . y || 0 , endPoint . z || 0 ) ; } } protected getPositions ( ) { if ( this . positions ) { return this . positions ; } else { return [ 0 , 0 , 0 , 0 , 0 , 0 ] ; } } }