Browse Source

Update grapher

Lutz Roeder 4 years ago
parent
commit
4c28783fd0
2 changed files with 184 additions and 188 deletions
  1. 46 79
      source/view-grapher.js
  2. 138 109
      source/view.js

+ 46 - 79
source/view-grapher.js

@@ -5,8 +5,7 @@ var dagre = dagre || require('dagre');
 
 grapher.Graph = class {
 
-    constructor(canvas, compound) {
-        this._canvas = canvas;
+    constructor(compound) {
         this._nodes = new Map();
         this._edges = new Map();
         this._children = {};
@@ -15,22 +14,16 @@ grapher.Graph = class {
         this._isCompound = compound;
     }
 
-    setGraph(label) {
-        this._label = label;
+    setGraph(options) {
+        this._options = options;
         return this;
     }
 
     graph() {
-        return this._label;
-    }
-
-    setDefaultEdgeLabel(/* callback */) {
-        // TODO
-        return this;
+        return this._options;
     }
 
     setNode(node) {
-        node.label = node.format ? node.format(this._canvas) : null;
         this._nodes.set(node.name, node);
         if (this._isCompound) {
             this._parent[node.name] = '\x00';
@@ -48,11 +41,9 @@ grapher.Graph = class {
             throw new grapher.Error();
         }
         const key = edge.v + ' ' + edge.w + ' ';
-        if (this._edges.has(key)) {
-            throw new grapher.Error();
+        if (!this._edges.has(key)) {
+            this._edges.set(key, edge);
         }
-        this._edges.set(key, edge);
-        this._edgeCount++;
         return this;
     }
 
@@ -117,31 +108,32 @@ grapher.Graph = class {
         }
     }
 
-
+    render(document, originElement) {
+        const renderer = new grapher.Renderer(document, originElement);
+        renderer.render(this);
+    }
 };
 
-grapher.Element = class {
+grapher.Node = class {
 
-    constructor(document) {
-        this._document = document;
+    constructor() {
         this._blocks = [];
     }
 
-    block(type) {
-        this._block = null;
-        switch (type) {
-            case 'header':
-                this._block = new grapher.Element.Header(this._document);
-                break;
-            case 'list':
-                this._block = new grapher.Element.List(this._document);
-                break;
-        }
-        this._blocks.push(this._block);
-        return this._block;
+    header() {
+        const block = new grapher.Node.Header();
+        this._blocks.push(block);
+        return block;
+    }
+
+    list() {
+        const block = new grapher.Node.List();
+        this._blocks.push(block);
+        return block;
     }
 
-    format(contextElement) {
+    build(document, contextElement) {
+        this._document = document;
         const rootElement = this.createElement('g');
         contextElement.appendChild(rootElement);
 
@@ -151,7 +143,7 @@ grapher.Element = class {
 
         for (const block of this._blocks) {
             tops.push(height);
-            block.layout(rootElement);
+            block.build(document, rootElement);
             if (width < block.width) {
                 width = block.width;
             }
@@ -165,7 +157,7 @@ grapher.Element = class {
 
         const borderElement = this.createElement('path');
         borderElement.setAttribute('class', [ 'node', 'border' ].join(' '));
-        borderElement.setAttribute('d', grapher.Element.roundedRect(0, 0, width, height, true, true, true, true));
+        borderElement.setAttribute('d', grapher.Node.roundedRect(0, 0, width, height, true, true, true, true));
         rootElement.appendChild(borderElement);
 
         contextElement.innerHTML = '';
@@ -195,10 +187,9 @@ grapher.Element = class {
     }
 };
 
-grapher.Element.Header = class {
+grapher.Node.Header = class {
 
-    constructor(document) {
-        this._document = document;
+    constructor() {
         this._items = [];
     }
 
@@ -212,7 +203,8 @@ grapher.Element.Header = class {
         });
     }
 
-    layout(parentElement) {
+    build(document, parentElement) {
+        this._document = document;
         this._width = 0;
         this._height = 0;
         this._elements = [];
@@ -300,7 +292,7 @@ grapher.Element.Header = class {
             const r2 = i == this._elements.length - 1 && first;
             const r3 = i == this._elements.length - 1 && last;
             const r4 = i == 0 && last;
-            element.path.setAttribute('d', grapher.Element.roundedRect(0, 0, element.width, element.height, r1, r2, r3, r4));
+            element.path.setAttribute('d', grapher.Node.roundedRect(0, 0, element.width, element.height, r1, r2, r3, r4));
             element.text.setAttribute('x', 6);
             element.text.setAttribute('y', element.ty);
         }
@@ -335,10 +327,9 @@ grapher.Element.Header = class {
     }
 };
 
-grapher.Element.List = class {
+grapher.Node.List = class {
 
-    constructor(document) {
-        this._document = document;
+    constructor() {
         this._items = [];
     }
 
@@ -354,7 +345,8 @@ grapher.Element.List = class {
         this._handler = handler;
     }
 
-    layout(parentElement) {
+    build(document, parentElement) {
+        this._document = document;
         this._width = 0;
         this._height = 0;
         const x = 0;
@@ -424,7 +416,7 @@ grapher.Element.List = class {
         const r2 = first;
         const r3 = last;
         const r4 = last;
-        this._backgroundElement.setAttribute('d', grapher.Element.roundedRect(0, 0, width, this._height, r1, r2, r3, r4));
+        this._backgroundElement.setAttribute('d', grapher.Node.roundedRect(0, 0, width, this._height, r1, r2, r3, r4));
 
         if (!first) {
             const lineElement = this.createElement('line');
@@ -442,36 +434,6 @@ grapher.Element.List = class {
     }
 };
 
-grapher.Node = class extends grapher.Element {
-
-    constructor(document) {
-        super(document);
-        this.class = 'graph-node';
-    }
-};
-
-grapher.Input = class extends grapher.Element {
-
-    constructor(document) {
-        super(document);
-        this.class = 'graph-input';
-    }
-};
-
-grapher.Output = class extends grapher.Element {
-
-    constructor(document) {
-        super(document);
-    }
-};
-
-grapher.Edge = class {
-
-    get arrowhead() {
-        return 'vee';
-    }
-};
-
 grapher.Renderer = class {
 
     constructor(document, svgElement) {
@@ -508,7 +470,7 @@ grapher.Renderer = class {
                 if (node.id) {
                     element.setAttribute('id', node.id);
                 }
-                element.setAttribute('class', Object.prototype.hasOwnProperty.call(node, 'class') ? ('node ' + node.class) : 'node');
+                element.setAttribute('class', node.class ? 'node ' + node.class : 'node');
                 element.style.opacity = 0;
                 const container = this.createElement('g');
                 container.appendChild(node.label);
@@ -599,7 +561,7 @@ grapher.Renderer = class {
             const edge = graph.edge(edgeId);
             const edgePath = grapher.Renderer._computeCurvePath(edge, graph.node(edgeId.v), graph.node(edgeId.w));
             const edgeElement = this.createElement('path');
-            edgeElement.setAttribute('class', Object.prototype.hasOwnProperty.call(edge, 'class') ? ('edge-path ' + edge.class) : 'edge-path');
+            edgeElement.setAttribute('class', edge.class ? 'edge-path ' + edge.class : 'edge-path');
             edgeElement.setAttribute('d', edgePath);
             if (edge.id) {
                 edgeElement.setAttribute('id', edge.id);
@@ -790,11 +752,16 @@ class Curve {
     }
 }
 
+grapher.Edge = class {
+
+    get arrowhead() {
+        return 'vee';
+    }
+};
+
+
 if (typeof module !== 'undefined' && typeof module.exports === 'object') {
     module.exports.Graph = grapher.Graph;
     module.exports.Node = grapher.Node;
-    module.exports.Input = grapher.Input;
-    module.exports.Output = grapher.Output;
     module.exports.Edge = grapher.Edge;
-    module.exports.Renderer = grapher.Renderer;
 }

+ 138 - 109
source/view.js

@@ -11,7 +11,6 @@ var protobuf = protobuf || require('./protobuf');
 var python = python || require('./python');
 
 var d3 = d3 || require('d3');
-var dagre = dagre || require('dagre');
 
 var sidebar = sidebar || require('./view-sidebar');
 var grapher = grapher || require('./view-grapher');
@@ -507,44 +506,27 @@ view.View = class {
                 }
 
                 const groups = graph.groups;
+                const nodes = graph.nodes;
+                this._host.event('Graph', 'Render', 'Size', nodes.length);
 
-                const graphOptions = {};
-                graphOptions.nodesep = 25;
-                graphOptions.ranksep = 20;
-
+                const options = {};
+                options.nodesep = 25;
+                options.ranksep = 20;
                 const rotate = graph.nodes.every((node) => node.inputs.filter((input) => input.arguments.every((argument) => !argument.initializer)).length === 0 && node.outputs.length === 0);
                 const showHorizontal = rotate ? !this._showHorizontal : this._showHorizontal;
                 if (showHorizontal) {
-                    graphOptions.rankdir = "LR";
-                }
-
-                const graphlib = true;
-
-                let g = null;
-                if (graphlib) {
-                    const options = { compound: groups };
-                    g = new dagre.graphlib.Graph(options);
-                    g.setGraph(graphOptions);
-                    g.setDefaultEdgeLabel(() => { return {}; });
+                    options.rankdir = "LR";
                 }
-                else {
-                    g = new grapher.Graph(canvasElement, groups);
-                    g.setGraph(graphOptions);
-                    g.setDefaultEdgeLabel(() => { return {}; });
+                if (nodes.length > 1500) {
+                    options.ranker = 'longest-path';
                 }
 
-                let nodeId = 0;
+                const viewGraph = new view.Graph(groups, options);
+
                 const edgeMap = {};
                 const clusterMap = {};
                 const clusterParentMap = {};
                 let counter = 0;
-                const nodes = graph.nodes;
-
-                if (nodes.length > 1500) {
-                    graphOptions.ranker = 'longest-path';
-                }
-
-                this._host.event('Graph', 'Render', 'Size', nodes.length);
 
                 if (groups) {
                     for (const node of nodes) {
@@ -562,11 +544,11 @@ view.View = class {
                 const self = this;
                 for (const node of nodes) {
 
-                    const element = new grapher.Node(this._host.document);
+                    const viewNode = viewGraph.createNode(node);
+                    viewNode.id = 'node-' + (node.name ? 'name-' + node.name : 'id-' + (counter++).toString());
+                    const addNode = function(viewNode, node, edges) {
 
-                    const addNode = function(element, node, edges) {
-
-                        const header =  element.block('header');
+                        const header =  viewNode.header();
                         const styles = [ 'node-item-type' ];
                         const metadata = node.metadata;
                         const category = metadata && metadata.category ? metadata.category : '';
@@ -613,7 +595,7 @@ view.View = class {
                             });
                         }
                         if (initializers.length > 0 || hiddenInitializers || sortedAttributes.length > 0) {
-                            const block = element.block('list');
+                            const block = viewNode.list();
                             block.handler = () => {
                                 self.showNodeProperties(node);
                             };
@@ -675,7 +657,7 @@ view.View = class {
                                             edgeMap[argument.name] = tuple;
                                         }
                                         tuple.to.push({
-                                            node: nodeId,
+                                            node: viewNode.name,
                                             name: input.name
                                         });
                                     }
@@ -700,7 +682,7 @@ view.View = class {
                                             edgeMap[argument.name] = tuple;
                                         }
                                         tuple.from = {
-                                            node: nodeId,
+                                            node: viewNode.name,
                                             name: output.name,
                                             type: argument.type
                                         };
@@ -711,16 +693,16 @@ view.View = class {
 
                         if (node.chain && node.chain.length > 0) {
                             for (const innerNode of node.chain) {
-                                addNode(element, innerNode, false);
+                                addNode(viewNode, innerNode, false);
                             }
                         }
 
                         if (node.inner) {
-                            addNode(element, node.inner, false);
+                            addNode(viewNode, node.inner, false);
                         }
                     };
 
-                    addNode(element, node, true);
+                    addNode(viewNode, node, true);
 
                     if (node.controlDependencies && node.controlDependencies.length > 0) {
                         for (const controlDependency of node.controlDependencies) {
@@ -730,31 +712,21 @@ view.View = class {
                                 edgeMap[controlDependency] = tuple;
                             }
                             tuple.to.push({
-                                node: nodeId,
+                                node: viewNode.name,
                                 name: controlDependency,
                                 controlDependency: true
                             });
                         }
                     }
 
-                    const nodeName = node.name;
-                    element.id = 'node-' + (nodeName ? 'name-' + nodeName : 'id-' + (counter++).toString());
-                    if (graphlib) {
-                        g.setNode(nodeId, { label: element.format(canvasElement), id: element.id, class: 'graph-node' });
-                    }
-                    else {
-                        element.name = nodeId;
-                        g.setNode(element);
-                    }
-
                     const createCluster = function(name) {
                         if (!clusterMap[name]) {
-                            g.setNode({ name: name, rx: 5, ry: 5});
+                            viewGraph.setNode({ name: name, rx: 5, ry: 5});
                             clusterMap[name] = true;
                             const parent = clusterParentMap[name];
                             if (parent) {
                                 createCluster(parent);
-                                g.setParent(name, parent);
+                                viewGraph.setParent(name, parent);
                             }
                         }
                     };
@@ -776,73 +748,60 @@ view.View = class {
                             }
                             if (groupName) {
                                 createCluster(groupName);
-                                g.setParent(nodeId, groupName);
+                                viewGraph.setParent(viewNode.name, groupName);
                             }
                         }
                     }
-
-                    nodeId++;
                 }
 
                 for (const input of graph.inputs) {
-                    for (const argument of input.arguments) {
-                        let tuple = edgeMap[argument.name];
-                        if (!tuple) {
-                            tuple = { from: null, to: [] };
-                            edgeMap[argument.name] = tuple;
-                        }
-                        tuple.from = {
-                            node: nodeId,
-                            type: argument.type
-                        };
-                    }
+                    const viewInput = viewGraph.createInput(input);
+
                     const types = input.arguments.map((argument) => argument.type || '').join('\n');
                     let inputName = input.name || '';
                     if (inputName.length > 16) {
                         inputName = inputName.split('/').pop();
                     }
 
-                    const inputElement = new grapher.Input(this._host.document);
-                    const inputHeader = inputElement.block('header');
+                    const inputHeader = viewInput.header();
                     inputHeader.add(null, [ 'graph-item-input' ], inputName, types, () => {
                         this.showModelProperties();
                     });
-                    inputElement.id = 'input-' + (inputName ? 'name-' + inputName : 'id-' + (counter++).toString());
-                    if (graphlib) {
-                        g.setNode(nodeId++, { label: inputElement.format(canvasElement), id: inputElement.id, class: 'graph-input' } );
-                    }
-                    else {
-                        inputElement.name = nodeId++;
-                        g.setNode(inputElement);
-                    }
-                }
+                    viewInput.id = 'input-' + (inputName ? 'name-' + inputName : 'id-' + (counter++).toString());
 
-                for (const output of graph.outputs) {
-                    for (const argument of output.arguments) {
+                    for (const argument of input.arguments) {
                         let tuple = edgeMap[argument.name];
                         if (!tuple) {
                             tuple = { from: null, to: [] };
                             edgeMap[argument.name] = tuple;
                         }
-                        tuple.to.push({ node: nodeId });
+                        tuple.from = {
+                            node: viewInput.name,
+                            type: argument.type
+                        };
                     }
+                }
+
+                for (const output of graph.outputs) {
+                    const viewOutput = viewGraph.createOutput(output);
+
                     const outputTypes = output.arguments.map((argument) => argument.type || '').join('\n');
                     let outputName = output.name || '';
                     if (outputName.length > 16) {
                         outputName = outputName.split('/').pop();
                     }
-
-                    const outputElement = new grapher.Output(this._host.document);
-                    const outputHeader = outputElement.block('header');
-                    outputHeader.add(null, [ 'graph-item-output' ], outputName, outputTypes, () => {
+                    const header = viewOutput.header();
+                    header.add(null, [ 'graph-item-output' ], outputName, outputTypes, () => {
                         this.showModelProperties();
                     });
-                    if (graphlib) {
-                        g.setNode(nodeId++, { label: outputElement.format(canvasElement) } );
-                    }
-                    else {
-                        outputElement.name = nodeId++;
-                        g.setNode(outputElement);
+
+                    for (const argument of output.arguments) {
+                        let tuple = edgeMap[argument.name];
+                        if (!tuple) {
+                            tuple = { from: null, to: [] };
+                            edgeMap[argument.name] = tuple;
+                        }
+                        tuple.to.push({ node: viewOutput.name });
                     }
                 }
 
@@ -860,28 +819,21 @@ view.View = class {
                                 text = edgeKey.split('\n').shift(); // custom argument id
                             }
 
-                            if (graphlib) {
-                                const edge = { label: text, id: 'edge-' + edgeKey, arrowhead: 'vee' };
-                                if (to.controlDependency) {
-                                    edge.class = 'edge-path-control-dependency';
-                                }
-                                g.setEdge(tuple.from.node, to.node, edge);
-                            }
-                            else {
-                                const edge = new grapher.Edge();
-                                edge.v = tuple.from.node;
-                                edge.w = to.node;
-                                edge.label = text;
-                                edge.id = 'edge-' + edgeKey;
-                                if (to.controlDependency) {
-                                    edge.class = 'edge-path-control-dependency';
-                                }
-                                g.setEdge(edge);
+                            const edge = viewGraph.createEdge();
+                            edge.v = tuple.from.node;
+                            edge.w = to.node;
+                            edge.label = text;
+                            edge.id = 'edge-' + edgeKey;
+                            if (to.controlDependency) {
+                                edge.class = 'edge-path-control-dependency';
                             }
+                            viewGraph.setEdge(edge);
                         }
                     }
                 }
 
+                viewGraph.build(this._host.document, canvasElement);
+
                 // Workaround for Safari background drag/zoom issue:
                 // https://stackoverflow.com/questions/40887193/d3-js-zoom-is-not-working-with-mousewheel-in-safari
                 const backgroundElement = this._host.document.createElementNS('http://www.w3.org/2000/svg', 'rect');
@@ -919,8 +871,7 @@ view.View = class {
 
                 return this._timeout(20).then(() => {
 
-                    const graphRenderer = new grapher.Renderer(this._host.document, originElement);
-                    graphRenderer.render(g);
+                    viewGraph.render(this._host.document, originElement);
 
                     const elements = Array.from(canvasElement.getElementsByClassName('graph-input') || []);
                     if (elements.length === 0) {
@@ -952,7 +903,7 @@ view.View = class {
                                 this._zoom.transform(svg, d3.zoomIdentity.translate(sx, sy));
                             }
                             else {
-                                this._zoom.transform(svg, d3.zoomIdentity.translate((svgSize.width - g.graph().width) / 2, (svgSize.height - g.graph().height) / 2));
+                                this._zoom.transform(svg, d3.zoomIdentity.translate((svgSize.width - viewGraph.graph().width) / 2, (svgSize.height - viewGraph.graph().height) / 2));
                             }
                             break;
                         }
@@ -1179,6 +1130,84 @@ view.View = class {
     }
 };
 
+view.Graph = class extends grapher.Graph {
+
+    constructor(compound, options) {
+        super(compound);
+        this._nodeKey = 0;
+        this.setGraph(options);
+    }
+
+    createNode(node) {
+        const value = new view.Node(node);
+        value.name = this._nodeKey++;
+        this.setNode(value);
+        return value;
+    }
+
+    createInput(input) {
+        const value = new view.Input(input);
+        value.name = this._nodeKey++;
+        this.setNode(value);
+        return value;
+    }
+
+    createOutput(output) {
+        const value = new view.Output(output);
+        value.name = this._nodeKey++;
+        this.setNode(value);
+        return value;
+    }
+
+    createEdge() {
+        const value = new view.Edge();
+        return value;
+    }
+
+    build(document, canvas) {
+        for (const key of this.nodes()) {
+            const node = this.node(key);
+            node.label = node.build ? node.build(document, canvas) : null;
+        }
+    }
+};
+
+view.Node = class extends grapher.Node {
+
+    constructor(value) {
+        super();
+        this.value = value;
+    }
+
+    get class() {
+        return 'graph-node';
+    }
+};
+
+view.Input = class extends grapher.Node {
+
+    constructor(value) {
+        super();
+        this.value = value;
+    }
+
+    get class() {
+        return 'graph-input';
+    }
+};
+
+view.Output = class extends grapher.Node {
+
+    constructor(value) {
+        super();
+        this.value = value;
+    }
+};
+
+view.Edge = class extends grapher.Edge {
+
+};
+
 view.ModelContext = class {
 
     constructor(context, entries) {