From a7db5ad61f6905104ed0fb290fbcf2322f8672f7 Mon Sep 17 00:00:00 2001 From: Mihails Akimenko Date: Sat, 6 Feb 2021 19:03:45 +0200 Subject: [PATCH] Dagre composition --- .../atft-data-center-actor.module.ts | 10 +- .../layout/dagre-composition.component.ts | 56 +++++++++++ .../layout/dagre-edge.component.ts | 4 +- .../layout/dagre-layout.component.ts | 92 +++++++++++++------ .../layout/dagre-node.component.ts | 15 +++ .../actor/data-center/layout/dagre-utils.ts | 4 +- .../src/lib/actor/data-center/layout/index.ts | 2 + .../atft/src/lib/object/abstract-object-3d.ts | 19 +++- .../lib/object/mesh/plane-mesh.component.ts | 42 +++++++-- src/stories/layout/dagre.stories.ts | 52 ++++++++++- src/stories/other/mesh.stories.ts | 19 +++- 11 files changed, 269 insertions(+), 46 deletions(-) create mode 100644 projects/atft/src/lib/actor/data-center/layout/dagre-composition.component.ts create mode 100644 projects/atft/src/lib/actor/data-center/layout/dagre-node.component.ts diff --git a/projects/atft/src/lib/actor/data-center/atft-data-center-actor.module.ts b/projects/atft/src/lib/actor/data-center/atft-data-center-actor.module.ts index 0e18e68f..cc84a42c 100644 --- a/projects/atft/src/lib/actor/data-center/atft-data-center-actor.module.ts +++ b/projects/atft/src/lib/actor/data-center/atft-data-center-actor.module.ts @@ -8,7 +8,7 @@ import {ServerCompactActorComponent} from './server/server-compact-actor.compone import {ServerIconActorComponent} from './server/server-icon-actor.component'; import {WorkstationActorComponent} from './server/workstation-actor.component'; import {GridActorComponent} from './layer/grid-actor.component'; -import {DagreEdgeComponent, DagreLayoutComponent} from './layout'; +import {DagreCompositionComponent, DagreEdgeComponent, DagreLayoutComponent, DagreNodeComponent} from './layout'; @NgModule({ imports: [ @@ -24,7 +24,9 @@ import {DagreEdgeComponent, DagreLayoutComponent} from './layout'; WorkstationActorComponent, GridActorComponent, DagreLayoutComponent, - DagreEdgeComponent + DagreEdgeComponent, + DagreCompositionComponent, + DagreNodeComponent ], exports: [ LayerActorComponent, @@ -35,7 +37,9 @@ import {DagreEdgeComponent, DagreLayoutComponent} from './layout'; WorkstationActorComponent, GridActorComponent, DagreLayoutComponent, - DagreEdgeComponent + DagreEdgeComponent, + DagreCompositionComponent, + DagreNodeComponent ] }) export class AtftDataCenterActorModule { diff --git a/projects/atft/src/lib/actor/data-center/layout/dagre-composition.component.ts b/projects/atft/src/lib/actor/data-center/layout/dagre-composition.component.ts new file mode 100644 index 00000000..fb744cc2 --- /dev/null +++ b/projects/atft/src/lib/actor/data-center/layout/dagre-composition.component.ts @@ -0,0 +1,56 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {provideParent} from '../../../util'; +import {EmptyComponent} from '../../../object'; + +@Component({ + selector: 'atft-dagre-composition', + providers: [provideParent(DagreCompositionComponent)], + template: ` + + + + + ` +}) +export class DagreCompositionComponent extends EmptyComponent { + + @Input() name: string; + + private _height: number; + @Input() + set height(hight: number) { + this._height = hight; + this.translateLabelY = this._height / 2 - 5; + } + + get height(): number { + return this._height; + } + + @Input() width: number; + + + + @Output() render = new EventEmitter(); + @Output() selected = new EventEmitter(); + @Output() deselected = new EventEmitter(); + + color = 0xA0A0A0; + + translateLabelY: number; + + public onSelected() { + this.color = 0xA4A4A4; + } + + public onDeselected() { + this.color = 0xA0A0A0; + } + + public onClick() { + this.color = 0xA0A0A0; + } + +} diff --git a/projects/atft/src/lib/actor/data-center/layout/dagre-edge.component.ts b/projects/atft/src/lib/actor/data-center/layout/dagre-edge.component.ts index 3c226a85..a5ea76d1 100644 --- a/projects/atft/src/lib/actor/data-center/layout/dagre-edge.component.ts +++ b/projects/atft/src/lib/actor/data-center/layout/dagre-edge.component.ts @@ -17,9 +17,9 @@ export class DagreEdgeComponent extends MeshLineConnectorComponent { protected getLineGeometry(): THREE.BufferGeometry { if (!this.source || !this.target) { - throw new Error('DagreEdgeComponent: source or target inputs are missing!'); + throw new Error('DagreCompositionComponent: source or target inputs are missing!'); } - // console.log('DagreEdgeComponent.getLineGeometry', this.positions); + // console.log('DagreCompositionComponent.getLineGeometry', this.positions); const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(this.positions, 3)); return geometry; diff --git a/projects/atft/src/lib/actor/data-center/layout/dagre-layout.component.ts b/projects/atft/src/lib/actor/data-center/layout/dagre-layout.component.ts index fccece03..d568458c 100644 --- a/projects/atft/src/lib/actor/data-center/layout/dagre-layout.component.ts +++ b/projects/atft/src/lib/actor/data-center/layout/dagre-layout.component.ts @@ -6,6 +6,8 @@ import {RendererService} from '../../../renderer'; import {AbstractObject3D} from '../../../object'; import {DagreEdgeComponent} from './dagre-edge.component'; import * as dagre from 'dagre'; +import {DagreNodeComponent} from './dagre-node.component'; +import {DagreCompositionComponent} from './dagre-composition.component'; @Component({ @@ -39,34 +41,57 @@ export class DagreLayoutComponent extends EmptyComponent implements AfterViewIni this.graphModel = { layout: {}, nodes: [], - edges: [] + edges: [], + composition: [] }; } public addChild(object: AbstractObject3D): void { super.addChild(object); - // console.log('DagreLayoutComponent.addChild', object); + this.processDagreChild(object); + } + + protected processDagreChild(object: AbstractObject3D) { if (object instanceof DagreEdgeComponent) { - // ============== 1) EDGE: - // console.log('DagreLayoutComponent.addChild as Edge', object); - const edgeObject: DagreEdgeComponent = object; - if (edgeObject.source && edgeObject.source.getObject() && edgeObject.target && edgeObject.target.getObject()) { - this.graphModel.edges.push({ - uuid: edgeObject.getObject().uuid, - from: edgeObject.source.getObject().uuid, - to: edgeObject.target.getObject().uuid - }); - } else { - console.warn('DagreLayoutComponent.addChild: edge source/target is undefined'); - } + this.addEdge(object); } else { - // ============== 2) NODE: - // console.log('DagreLayoutComponent.addChild as Node', object); - this.graphModel.nodes.push({ - id: object.getObject().uuid, - label: '?', + this.addNode(object); + } + } + + protected addEdge(edge: DagreEdgeComponent) { + // console.log('DagreLayoutComponent.addEdge', edge); + const edgeObject: DagreEdgeComponent = edge; + if (edgeObject.source && edgeObject.source.getObject() && edgeObject.target && edgeObject.target.getObject()) { + this.graphModel.edges.push({ + uuid: edgeObject.getObject().uuid, + from: edgeObject.source.getObject().uuid, + to: edgeObject.target.getObject().uuid }); + } else { + console.warn('DagreLayoutComponent.addChild: edge source/target is undefined'); + } + } + + + protected addNode(object: AbstractObject3D) { + // console.log('DagreLayoutComponent.addNode', object); + this.graphModel.nodes.push({ + id: object.getObject().uuid, + label: object.getObject().uuid, + }); + + if (object instanceof DagreNodeComponent) { + const node: DagreNodeComponent = object; + if (node.composition) { + // console.log('DagreLayoutComponent.addNode to composition', node.composition); + this.graphModel.composition.push({ + parent: node.composition.getObject().uuid, + child: node.getObject().uuid + }); + } } + } ngAfterViewInit() { @@ -76,21 +101,20 @@ export class DagreLayoutComponent extends EmptyComponent implements AfterViewIni public layout() { // console.log('DagreLayoutComponent.layout'); - - this.graphModel.layout.align = this.align; - this.graphModel.layout.rankdir = this.rankdir; - this.graphModel.layout.nodesep = this.nodesep; - this.graphModel.layout.edgesep = this.edgesep; - this.graphModel.layout.ranksep = this.ranksep; - this.graphModel.layout.marginx = this.marginx; - this.graphModel.layout.marginy = this.marginy; - this.graphModel.layout.ranker = this.ranker; - + this.graphModel.layout = { + align: this.align, + rankdir: this.rankdir, + nodesep: this.nodesep, + edgesep: this.edgesep, + ranksep: this.ranksep, + marginx: this.marginx, + marginy: this.marginy, + ranker: this.ranker + }; const g = DagreUtils.modelToGraph(this.graphModel); // console.log('DagreLayoutComponent.layout: g', g); this.syncGraphNodes(g); this.syncGraphEdges(g); - this.rendererService.render(); } @@ -108,6 +132,14 @@ export class DagreLayoutComponent extends EmptyComponent implements AfterViewIni object.translateY = node.y; object.applyTranslation(); + if (object instanceof DagreCompositionComponent) { + // console.log('DagreLayoutComponent.layout: Update composition', node); + const composition: DagreCompositionComponent = object; + composition.width = node.width; + composition.height = node.height; + + } + } else { console.warn('DagreLayoutComponent.layout: Object not found by uuid', uuid); } diff --git a/projects/atft/src/lib/actor/data-center/layout/dagre-node.component.ts b/projects/atft/src/lib/actor/data-center/layout/dagre-node.component.ts new file mode 100644 index 00000000..94dc82a6 --- /dev/null +++ b/projects/atft/src/lib/actor/data-center/layout/dagre-node.component.ts @@ -0,0 +1,15 @@ +import {Component, Input} from '@angular/core'; +import {AbstractObject3D, EmptyComponent} from '../../../object'; +import {provideParent} from '../../../util'; + +@Component({ + selector: 'atft-dagre-node', + providers: [provideParent(DagreNodeComponent)], + template: '' +}) +export class DagreNodeComponent extends EmptyComponent { + + @Input() composition: AbstractObject3D; + + @Input() translateZ = 1; +} diff --git a/projects/atft/src/lib/actor/data-center/layout/dagre-utils.ts b/projects/atft/src/lib/actor/data-center/layout/dagre-utils.ts index f737da51..ec05ccc1 100644 --- a/projects/atft/src/lib/actor/data-center/layout/dagre-utils.ts +++ b/projects/atft/src/lib/actor/data-center/layout/dagre-utils.ts @@ -1,6 +1,5 @@ import * as dagre from 'dagre'; - export interface Node { id: string; label: string; @@ -24,6 +23,9 @@ export interface GraphModel { composition?: Array; } +/** + * WIKI: https://github.com/dagrejs/dagre/wiki + */ export class DagreUtils { public static modelToGraph(model: GraphModel): dagre.graphlib.Graph { diff --git a/projects/atft/src/lib/actor/data-center/layout/index.ts b/projects/atft/src/lib/actor/data-center/layout/index.ts index d6806189..55238c6d 100644 --- a/projects/atft/src/lib/actor/data-center/layout/index.ts +++ b/projects/atft/src/lib/actor/data-center/layout/index.ts @@ -1,3 +1,5 @@ export * from './dagre-layout.component'; +export * from './dagre-node.component'; export * from './dagre-edge.component'; +export * from './dagre-composition.component'; diff --git a/projects/atft/src/lib/object/abstract-object-3d.ts b/projects/atft/src/lib/object/abstract-object-3d.ts index 33742135..81d9c93e 100644 --- a/projects/atft/src/lib/object/abstract-object-3d.ts +++ b/projects/atft/src/lib/object/abstract-object-3d.ts @@ -156,7 +156,7 @@ export abstract class AbstractObject3D implements Afte protected afterInit() { } - protected removeChild(object: AbstractObject3D): void { + public removeChild(object: AbstractObject3D): void { if (this.object && object) { // Remove from children: @@ -183,9 +183,24 @@ export abstract class AbstractObject3D implements Afte public findByUuid(uuid: string) { // console.log('AbstractObject3D.findByUuid: Searching uuid', uuid); // console.log('AbstractObject3D.findByUuid: children', this.childlren); - const res = this.childlren.filter(i => i.object && i.object.uuid === uuid)[0]; + // const res = this.childlren.filter(i => i.object && i.object.uuid === uuid)[0]; + const res = this.getNodeByUuid(this, uuid); // console.log('AbstractObject3D.findByUuid: result', res); return res; } + protected getNodeByUuid(currentNode: AbstractObject3D, uuid) { + if (currentNode.object && currentNode.object.uuid === uuid) { + return currentNode; + } + let node; + currentNode.childlren.some(child => node = this.getNodeByUuid(child, uuid)); + return node; + } + + + public getChildren() { + return this.childlren; + } + } diff --git a/projects/atft/src/lib/object/mesh/plane-mesh.component.ts b/projects/atft/src/lib/object/mesh/plane-mesh.component.ts index dd99a2b8..d4b8831e 100644 --- a/projects/atft/src/lib/object/mesh/plane-mesh.component.ts +++ b/projects/atft/src/lib/object/mesh/plane-mesh.component.ts @@ -1,16 +1,16 @@ -import { Component, Input, Optional, SkipSelf } from '@angular/core'; +import {Component, Input, OnChanges, Optional, SimpleChanges, SkipSelf} from '@angular/core'; import * as THREE from 'three'; -import { RendererService } from '../../renderer/renderer.service'; -import { provideParent } from '../../util'; -import { AbstractObject3D } from '../abstract-object-3d'; -import { AbstractMesh } from './abstract-mesh-3d'; +import {RendererService} from '../../renderer/renderer.service'; +import {provideParent} from '../../util'; +import {AbstractObject3D} from '../abstract-object-3d'; +import {AbstractMesh} from './abstract-mesh-3d'; @Component({ selector: 'atft-plane-mesh', providers: [provideParent(PlaneMeshComponent)], template: '' }) -export class PlaneMeshComponent extends AbstractMesh { +export class PlaneMeshComponent extends AbstractMesh implements OnChanges { /** * Width; that is, the length of the edges parallel to the X axis. Optional; defaults to 1. @@ -51,4 +51,34 @@ export class PlaneMeshComponent extends AbstractMesh { return mesh; } + + public ngOnChanges(changes: SimpleChanges) { + // console.log('AbstractObject3D.ngOnChanges', this.uuid); + if (!this.object) { + return; + } + super.ngOnChanges(changes); + + let modified = false; + + if (['width', 'height', 'widthSegments', 'heightSegments'].some(propName => propName in changes)) { + if (this.getObject() instanceof THREE.Mesh) { + const mesh: THREE.Mesh = this.getObject(); + + if (mesh.geometry instanceof THREE.PlaneBufferGeometry) { + const currentGeometry: THREE.PlaneBufferGeometry = mesh.geometry; + const newGeometry = new THREE.PlaneBufferGeometry(this.width, this.height, this.widthSegments, this.heightSegments); + currentGeometry.attributes = newGeometry.attributes; + } + } + modified = true; + } + + if (modified) { + this.changed.emit(); + this.rendererService.render(); + } + + } + } diff --git a/src/stories/layout/dagre.stories.ts b/src/stories/layout/dagre.stories.ts index 8ebf9595..3ff665bc 100644 --- a/src/stories/layout/dagre.stories.ts +++ b/src/stories/layout/dagre.stories.ts @@ -31,6 +31,48 @@ import {AnimationService} from '../../../projects/atft/src/lib/animation'; }) class StorybookDagreComponent { + constructor(private animationService: AnimationService) { + this.animationService.start(); + } + +} + +@Component({ + template: worldSceneWrapper(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`) +}) +class StorybookDagreCompositionComponent { constructor(private animationService: AnimationService) { this.animationService.start(); @@ -38,6 +80,7 @@ class StorybookDagreComponent { } + export default { title: 'Layout/Dagre', decorators: [ @@ -95,7 +138,14 @@ export default { }; -export const Dagre = (args) => ({ +export const Simple = (args) => ({ component: StorybookDagreComponent, props: args }); + + +export const Composition = (args) => ({ + component: StorybookDagreCompositionComponent, + props: args +}); + diff --git a/src/stories/other/mesh.stories.ts b/src/stories/other/mesh.stories.ts index 48e2e57a..a0367909 100644 --- a/src/stories/other/mesh.stories.ts +++ b/src/stories/other/mesh.stories.ts @@ -73,6 +73,17 @@ class StorybookFrameMeshComponent { } + +@Component({ + template: axesSceneWrapper(` + + `) +}) +class StorybookPlaneMeshComponent { + +} + + export default { title: 'Other/Mesh', decorators: [ @@ -84,7 +95,8 @@ export default { ], args: { materialColor: '0x00ff00', - translateX: 0 + translateX: 0, + width: 10 }, argTypes: { materialColor: { control: { type: 'select', options: ['0xff0000', '0x00ff00', '0x0000ff'] } }, @@ -130,3 +142,8 @@ Text.argTypes = { text: 'text' }; +export const Plane = (args) => ({ + component: StorybookPlaneMeshComponent, + props: args +}); +