Skip to content

Commit

Permalink
Merge pull request #339 from makimenko/dagra-layout
Browse files Browse the repository at this point in the history
Dagre layout
  • Loading branch information
makimenko authored Feb 6, 2021
2 parents de17a5c + a7db5ad commit 418946b
Show file tree
Hide file tree
Showing 22 changed files with 687 additions and 39 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test": "ng test --watch=false --code-coverage --browsers=ChromeHeadlessCustom",
"lint": "ng lint",
"e2e": "ng e2e",
"storybook": "start-storybook -p 6006",
"storybook": "start-storybook -p 6006 --ci",
"build-storybook": "build-storybook",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
Expand All @@ -26,6 +26,7 @@
"@angular/platform-browser-dynamic": "^11.1.2",
"@angular/router": "^11.1.2",
"core-js": "^3.8.3",
"dagre": "^0.8.5",
"rxjs": "^6.6.3",
"snyk": "^1.445.0",
"three": "^0.124.0",
Expand All @@ -46,6 +47,7 @@
"@storybook/addon-links": "^6.1.17",
"@storybook/addons": "^6.1.17",
"@storybook/angular": "^6.1.17",
"@types/dagre": "^0.7.44",
"@types/jasmine": "^3.6.3",
"@types/jasminewd2": "^2.0.8",
"@types/node": "^13.13.41",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +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 {DagreCompositionComponent, DagreEdgeComponent, DagreLayoutComponent, DagreNodeComponent} from './layout';

@NgModule({
imports: [
Expand All @@ -21,7 +22,11 @@ import {GridActorComponent} from './layer/grid-actor.component';
ServerCompactActorComponent,
ServerIconActorComponent,
WorkstationActorComponent,
GridActorComponent
GridActorComponent,
DagreLayoutComponent,
DagreEdgeComponent,
DagreCompositionComponent,
DagreNodeComponent
],
exports: [
LayerActorComponent,
Expand All @@ -30,7 +35,11 @@ import {GridActorComponent} from './layer/grid-actor.component';
ServerCompactActorComponent,
ServerIconActorComponent,
WorkstationActorComponent,
GridActorComponent
GridActorComponent,
DagreLayoutComponent,
DagreEdgeComponent,
DagreCompositionComponent,
DagreNodeComponent
]
})
export class AtftDataCenterActorModule {
Expand Down
1 change: 1 addition & 0 deletions projects/atft/src/lib/actor/data-center/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './atft-data-center-actor.module';
export * from './layer';
export * from './server';
export * from './layout';
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ export class GridActorComponent extends EmptyComponent {
}

public onMouseClick(event: RaycasterEmitEvent) {
console.log('GridActorComponent.onMouseClick');
// console.log('GridActorComponent.onMouseClick');
const pos = this.getLinePosition(event);
if (pos) {
console.log('GridActorComponent.onMouseClick position', pos);
// console.log('GridActorComponent.onMouseClick position', pos);
this.gridClick.emit(pos);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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: `
<atft-plane-mesh atft-raycaster-group [width]="width" [height]="height" [materialColor]="color" (mouseEnter)="onSelected()"
(mouseExit)="onDeselected()">
<atft-text-mesh [centered]="true" [text]="name" size="3" [translateY]="translateLabelY"
materialColor="0xE0E0E0">
</atft-text-mesh>
</atft-plane-mesh>
`
})
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<void>();
@Output() selected = new EventEmitter<void>();
@Output() deselected = new EventEmitter<void>();

color = 0xA0A0A0;

translateLabelY: number;

public onSelected() {
this.color = 0xA4A4A4;
}

public onDeselected() {
this.color = 0xA0A0A0;
}

public onClick() {
this.color = 0xA0A0A0;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Component} from '@angular/core';
import {MeshLineConnectorComponent} from '../../../object';
import {provideParent} from '../../../util';
import * as THREE from 'three';

@Component({
selector: 'atft-dagre-edge',
providers: [provideParent(DagreEdgeComponent)],
template: '<ng-content></ng-content>'
})
export class DagreEdgeComponent extends MeshLineConnectorComponent {

public positions: Array<number>;

public animated = true;


protected getLineGeometry(): THREE.BufferGeometry {
if (!this.source || !this.target) {
throw new Error('DagreCompositionComponent: source or target inputs are missing!');
}
// console.log('DagreCompositionComponent.getLineGeometry', this.positions);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(this.positions, 3));
return geometry;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import {AfterViewInit, Component, Input, OnChanges, Optional, SimpleChanges, SkipSelf} from '@angular/core';
import {EmptyComponent} from '../../../object/helper';
import {provideParent} from '../../../util';
import {DagreUtils, GraphModel} from './dagre-utils';
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({
selector: 'atft-dagre-layout',
providers: [provideParent(DagreLayoutComponent)],
template: `
`
})
export class DagreLayoutComponent extends EmptyComponent implements AfterViewInit, OnChanges {

@Input() align = 'DR';
@Input() rankdir = 'TB';
@Input() nodesep = 15;
@Input() edgesep = 1;
@Input() ranksep = 15;
@Input() marginx = 0;
@Input() marginy = 0;
@Input() ranker = 'network-simplex';


protected graphModel: GraphModel;

constructor(
protected rendererService: RendererService,
@SkipSelf() @Optional() protected parent: AbstractObject3D<any>
) {
super(rendererService, parent);

// Initialize empty model:
this.graphModel = {
layout: {},
nodes: [],
edges: [],
composition: []
};
}

public addChild(object: AbstractObject3D<any>): void {
super.addChild(object);
this.processDagreChild(object);
}

protected processDagreChild(object: AbstractObject3D<any>) {
if (object instanceof DagreEdgeComponent) {
this.addEdge(object);
} else {
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<any>) {
// 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() {
super.ngAfterViewInit();
this.layout();
}

public layout() {
// console.log('DagreLayoutComponent.layout');
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();
}

protected syncGraphNodes(g: dagre.graphlib.Graph) {
// console.log('DagreLayoutComponent.syncGraphNodes');
g.nodes().forEach((uuid) => {
// console.log('Node ' + uuid + ': ' + JSON.stringify(g.node(uuid)));
const object: AbstractObject3D<any> = this.findByUuid(uuid);

if (object) {
const node = g.node(uuid);
// console.log('DagreLayoutComponent.layout: Update position', node);

object.translateX = node.x;
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);
}
});
}

protected syncGraphEdges(g: dagre.graphlib.Graph) {
// console.log('DagreLayoutComponent.syncGraphEdges');
g.edges().forEach((e) => {
const edge: dagre.GraphEdge = g.edge(e);
// console.log('DagreLayoutComponent.syncGraphEdges: edge', edge);
const object: AbstractObject3D<any> = this.findByUuid(edge.uuid);
if (object && object instanceof DagreEdgeComponent) {
const edgeComponent: DagreEdgeComponent = object;
edgeComponent.positions = [];
// console.log('DagreLayoutComponent.syncGraphEdges: edge.points', edge.points);

edge.points.forEach(p => {
if (!Number.isNaN(p.x) && !Number.isNaN(p.y)) {
// console.log('x=' + p.x + ', y=' + p.y);
edgeComponent.positions.push(p.x, p.y, 0);
}
});
edgeComponent.updateLineGeometry();
} else {
console.warn('DagreLayoutComponent.layout: Object not found by uuid', e.name);
}
});
}


public ngOnChanges(changes: SimpleChanges) {
// console.log('AbstractObject3D.ngOnChanges', this.uuid);
if (!this.object) {
return;
}
super.ngOnChanges(changes);

let modified = false;

if (['align', 'rankdir', 'ranksep', 'nodesep', 'edgesep', 'marginx', 'marginy', 'ranker'].some(propName => propName in changes)) {
this.layout();
modified = true;
}

if (modified) {
this.changed.emit();
// this.rendererService.render();
}

}


}
Loading

0 comments on commit 418946b

Please sign in to comment.