Browse Source

Automatic graph distribution

David Catuhe 6 years ago
parent
commit
b79f93bfec

+ 2 - 0
nodeEditor/src/components/diagram/diagram.scss

@@ -40,6 +40,8 @@
         text-align: center;
         font-size: 20px;
         font-weight: bold;
+        overflow: hidden;
+        margin: 0 5px;
 
         .fullColor {
             height: 100%;

+ 3 - 0
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -61,6 +61,9 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                         <ButtonLineComponent label="Zoom to fit" onClick={() => {
                             this.props.globalState.onZoomToFitRequiredObservable.notifyObservers();
                         }} />
+                        <ButtonLineComponent label="Reorganize" onClick={() => {
+                            this.props.globalState.onReOrganizedRequiredObservable.notifyObservers();
+                        }} />
                     </LineContainerComponent>
                 </div>
             </div>

+ 1 - 0
nodeEditor/src/globalState.ts

@@ -13,5 +13,6 @@ export class GlobalState {
     onResetRequiredObservable = new Observable<void>();
     onUpdateRequiredObservable = new Observable<void>();
     onZoomToFitRequiredObservable = new Observable<void>();
+    onReOrganizedRequiredObservable = new Observable<void>();
     onLogRequiredObservable = new Observable<LogEntry>();
 }

+ 77 - 0
nodeEditor/src/graphEditor.tsx

@@ -7,6 +7,7 @@ import {
 } from "storm-react-diagrams";
 
 import * as React from "react";
+import * as dagre from "dagre";
 import { GlobalState } from './globalState';
 
 import { GenericNodeFactory } from './components/diagram/generic/genericNodeFactory';
@@ -168,9 +169,67 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             this._engine.zoomToFit();
         });
 
+        this.props.globalState.onReOrganizedRequiredObservable.add(() => {
+            this.reOrganize();
+        })
+
         this.build();
     }
 
+    distributeGraph() {
+        let nodes = this.mapElements();
+        let edges = this.mapEdges();
+        let graph = new dagre.graphlib.Graph();
+        graph.setGraph({});
+        graph.setDefaultEdgeLabel(() => ({}));
+        graph.graph().rankdir = "LR";
+        //add elements to dagre graph
+        nodes.forEach(node => {
+            graph.setNode(node.id, node.metadata);
+        });
+        edges.forEach(edge => {
+            if (edge.from && edge.to) {
+                graph.setEdge(edge.from.id, edge.to.id);
+            }
+        });
+        //auto-distribute
+        dagre.layout(graph);
+        return graph.nodes().map(node => graph.node(node));
+    }
+
+    mapElements() {
+        let output = [];
+
+        // dagre compatible format
+        for (var nodeName in this._model.nodes) {
+            let node = this._model.nodes[nodeName];
+            let size = {
+                width: node.width,
+                height: node.height
+            };
+            output.push({ id: node.id, metadata: { ...size, id: node.id } });
+        }
+
+        return output;
+    }
+
+    mapEdges() {
+        // returns links which connects nodes
+        // we check are there both from and to nodes in the model. Sometimes links can be detached
+        let output = [];
+
+        for (var linkName in this._model.links) {
+            let link = this._model.links[linkName];
+
+            output.push({
+                from: link.sourcePort!.parent,
+                to: link.targetPort!.parent
+            });
+        }
+
+        return output;
+    }
+
     buildMaterial() {
         if (!this.props.globalState.nodeMaterial) {
             return;
@@ -257,10 +316,28 @@ export class GraphEditor extends React.Component<IGraphEditorProps> {
             }
             this._toAdd = null;
             this._engine.setDiagramModel(this._model);
+
             this.forceUpdate();
+
+            this.reOrganize();
         }, 550);
     }
 
+    reOrganize() {
+        let nodes = this.distributeGraph();
+        nodes.forEach(node => {
+            for (var nodeName in this._model.nodes) {
+                let modelNode = this._model.nodes[nodeName];
+
+                if (modelNode.id === node.id) {
+                    modelNode.setPosition(node.x - node.width / 2, node.y - node.height / 2);
+                    return;
+                }
+            }
+        });
+        this.forceUpdate();
+    }
+
     addNodeFromClass(ObjectClass: typeof NodeMaterialBlock) {
         var block = new ObjectClass(ObjectClass.prototype.getClassName())
         var localNode = this.createNodeFromObject({ column: 0, nodeMaterialBlock: block })

+ 2 - 1
package.json

@@ -35,7 +35,6 @@
     },
     "readme": "Babylon.js is a 3D engine based on webgl and javascript",
     "readmeFilename": "README.md",
-    "dependencies": {},
     "devDependencies": {
         "@fortawesome/fontawesome-svg-core": "~1.2.8",
         "@fortawesome/free-regular-svg-icons": "~5.4.1",
@@ -47,6 +46,7 @@
         "@types/react": "~16.7.3",
         "@types/react-dom": "~16.0.9",
         "@types/sinon": "^4.1.3",
+        "@types/dagre": "^0.7.42",
         "ajv": "^6.9.1",
         "awesome-typescript-loader": "^5.2.1",
         "base64-font-loader": "0.0.4",
@@ -91,6 +91,7 @@
         "sinon": "^6.1.4",
         "split.js": "^1.5.9",
         "storm-react-diagrams": "^5.2.1",
+        "dagre": "0.8.4",
         "style-loader": "^0.21.0",
         "through2": "~2.0.3",
         "ts-loader": "^5.2.1",