Lutz Roeder vor 4 Jahren
Ursprung
Commit
0df2818a93
2 geänderte Dateien mit 237 neuen und 238 gelöschten Zeilen
  1. 223 226
      source/view-grapher.js
  2. 14 12
      source/view.js

+ 223 - 226
source/view-grapher.js

@@ -108,9 +108,98 @@ grapher.Graph = class {
         }
     }
 
-    render(document, originElement) {
-        const renderer = new grapher.Renderer(document, originElement);
-        renderer.render(this);
+    build(document, originElement) {
+
+        const createElement = (name) => {
+            return document.createElementNS('http://www.w3.org/2000/svg', name);
+        };
+        const createGroup = (name) => {
+            const element = createElement('g');
+            element.setAttribute('id', name);
+            element.setAttribute('class', name);
+            originElement.appendChild(element);
+            return element;
+        };
+
+        const clusterGroup = createGroup('clusters');
+        const edgePathGroup = createGroup('edge-paths');
+        const edgeLabelGroup = createGroup('edge-labels');
+        const nodeGroup = createGroup('nodes');
+
+        const edgePathGroupDefs = createElement('defs');
+        edgePathGroup.appendChild(edgePathGroupDefs);
+        const marker = (id) => {
+            const element = createElement('marker');
+            element.setAttribute('id', id);
+            element.setAttribute('viewBox', '0 0 10 10');
+            element.setAttribute('refX', 9);
+            element.setAttribute('refY', 5);
+            element.setAttribute('markerUnits', 'strokeWidth');
+            element.setAttribute('markerWidth', 8);
+            element.setAttribute('markerHeight', 6);
+            element.setAttribute('orient', 'auto');
+            const markerPath = createElement('path');
+            markerPath.setAttribute('d', 'M 0 0 L 10 5 L 0 10 L 4 5 z');
+            markerPath.style.setProperty('stroke-width', 1);
+            element.appendChild(markerPath);
+            return element;
+        };
+        edgePathGroupDefs.appendChild(marker("arrowhead-vee"));
+        edgePathGroupDefs.appendChild(marker("arrowhead-vee-select"));
+
+        for (const nodeId of this.nodes()) {
+            const node = this.node(nodeId);
+            if (this.children(nodeId).length == 0) {
+                // node
+                node.build(document, nodeGroup);
+            }
+            else {
+                // cluster
+                node.rectangle = createElement('rect');
+                if (node.rx) {
+                    node.rectangle.setAttribute('rx', node.rx);
+                }
+                if (node.ry) {
+                    node.rectangle.setAttribute('ry', node.ry);
+                }
+                node.element = createElement('g');
+                node.element.setAttribute('class', 'cluster');
+                node.element.appendChild(node.rectangle);
+                clusterGroup.appendChild(node.element);
+            }
+        }
+
+        for (const edgeId of this.edges()) {
+            const edge = this.edge(edgeId);
+            edge.build(document, edgePathGroup, edgeLabelGroup);
+        }
+    }
+
+    layout() {
+
+        dagre.layout(this);
+
+        for (const nodeId of this.nodes()) {
+            const node = this.node(nodeId);
+            if (this.children(nodeId).length == 0) {
+                // node
+                node.layout();
+            }
+            else {
+                // cluster
+                const node = this.node(nodeId);
+                node.element.setAttribute('transform', 'translate(' + node.x + ',' + node.y + ')');
+                node.rectangle.setAttribute('x', - node.width / 2);
+                node.rectangle.setAttribute('y', - node.height / 2 );
+                node.rectangle.setAttribute('width', node.width);
+                node.rectangle.setAttribute('height', node.height);
+            }
+        }
+
+        for (const edgeId of this.edges()) {
+            const edge = this.edge(edgeId);
+            edge.layout();
+        }
     }
 };
 
@@ -133,9 +222,22 @@ grapher.Node = class {
     }
 
     build(document, contextElement) {
-        this._document = document;
-        const rootElement = this.createElement('g');
-        contextElement.appendChild(rootElement);
+        const createElement = (name) => {
+            return document.createElementNS('http://www.w3.org/2000/svg', name);
+        };
+        this.label = createElement('g');
+
+        this.element = createElement('g');
+        if (this.id) {
+            this.element.setAttribute('id', this.id);
+        }
+        this.element.setAttribute('class', this.class ? 'node ' + this.class : 'node');
+        this.element.style.opacity = 0;
+        this.element.appendChild(this.label);
+
+        contextElement.appendChild(this.element);
+
+        // contextElement.appendChild(this.label);
 
         let width = 0;
         let height = 0;
@@ -143,7 +245,7 @@ grapher.Node = class {
 
         for (const block of this._blocks) {
             tops.push(height);
-            block.build(document, rootElement);
+            block.build(document, this.label);
             if (width < block.width) {
                 width = block.width;
             }
@@ -152,16 +254,25 @@ grapher.Node = class {
 
         for (let i = 0; i < this._blocks.length; i++) {
             const top = tops.shift();
-            this._blocks[i].update(rootElement, top, width, i == 0, i == this._blocks.length - 1);
+            this._blocks[i].update(this.label, top, width, i == 0, i == this._blocks.length - 1);
         }
 
-        const borderElement = this.createElement('path');
+        const borderElement = createElement('path');
         borderElement.setAttribute('class', [ 'node', 'border' ].join(' '));
         borderElement.setAttribute('d', grapher.Node.roundedRect(0, 0, width, height, true, true, true, true));
-        rootElement.appendChild(borderElement);
+        this.label.appendChild(borderElement);
 
-        contextElement.innerHTML = '';
-        return rootElement;
+        const nodeBox = this.label.getBBox();
+        const nodeX = - nodeBox.width / 2;
+        const nodeY = - nodeBox.height / 2;
+        this.width = nodeBox.width;
+        this.height = nodeBox.height;
+        this.label.setAttribute('transform', 'translate(' + nodeX + ',' + nodeY + ')');
+    }
+
+    layout() {
+        this.element.setAttribute('transform', 'translate(' + this.x + ',' + this.y + ')');
+        this.element.style.opacity = 1;
     }
 
     static roundedRect(x, y, width, height, r1, r2, r3, r4) {
@@ -181,10 +292,6 @@ grapher.Node = class {
             "a" + r1 + "," + r1 + " 0 0 1 " + r1 + "," + -r1 +
             "z";
     }
-
-    createElement(name) {
-        return this._document.createElementNS('http://www.w3.org/2000/svg', name);
-    }
 };
 
 grapher.Node.Header = class {
@@ -434,216 +541,6 @@ grapher.Node.List = class {
     }
 };
 
-grapher.Renderer = class {
-
-    constructor(document, svgElement) {
-        this._document = document;
-        this._svgElement = svgElement;
-    }
-
-    render(graph) {
-
-        const svgClusterGroup = this.createElement('g');
-        svgClusterGroup.setAttribute('id', 'clusters');
-        svgClusterGroup.setAttribute('class', 'clusters');
-        this._svgElement.appendChild(svgClusterGroup);
-
-        const svgEdgePathGroup = this.createElement('g');
-        svgEdgePathGroup.setAttribute('id', 'edge-paths');
-        svgEdgePathGroup.setAttribute('class', 'edge-paths');
-        this._svgElement.appendChild(svgEdgePathGroup);
-
-        const svgEdgeLabelGroup = this.createElement('g');
-        svgEdgeLabelGroup.setAttribute('id', 'edge-labels');
-        svgEdgeLabelGroup.setAttribute('class', 'edge-labels');
-        this._svgElement.appendChild(svgEdgeLabelGroup);
-
-        const svgNodeGroup = this.createElement('g');
-        svgNodeGroup.setAttribute('id', 'nodes');
-        svgNodeGroup.setAttribute('class', 'nodes');
-        this._svgElement.appendChild(svgNodeGroup);
-
-        for (const nodeId of graph.nodes()) {
-            if (graph.children(nodeId).length == 0) {
-                const node = graph.node(nodeId);
-                const element = this.createElement('g');
-                if (node.id) {
-                    element.setAttribute('id', node.id);
-                }
-                element.setAttribute('class', node.class ? 'node ' + node.class : 'node');
-                element.style.opacity = 0;
-                const container = this.createElement('g');
-                container.appendChild(node.label);
-                element.appendChild(container);
-                svgNodeGroup.appendChild(element);
-                const nodeBox = node.label.getBBox();
-                const nodeX = - nodeBox.width / 2;
-                const nodeY = - nodeBox.height / 2;
-                container.setAttribute('transform', 'translate(' + nodeX + ',' + nodeY + ')');
-                node.width = nodeBox.width;
-                node.height = nodeBox.height;
-                node.element = element;
-            }
-        }
-
-        for (const edgeId of graph.edges()) {
-            const edge = graph.edge(edgeId);
-            if (edge.label) {
-                const tspan = this.createElement('tspan');
-                tspan.setAttribute('xml:space', 'preserve');
-                tspan.setAttribute('dy', '1em');
-                tspan.setAttribute('x', '1');
-                tspan.appendChild(this._document.createTextNode(edge.label));
-                const text = this.createElement('text');
-                text.appendChild(tspan);
-                const textContainer = this.createElement('g');
-                textContainer.appendChild(text);
-                const labelElement = this.createElement('g');
-                labelElement.style.opacity = 0;
-                labelElement.setAttribute('class', 'edge-label');
-                if (edge.id) {
-                    labelElement.setAttribute('id', 'edge-label-' + edge.id);
-                }
-                labelElement.appendChild(textContainer);
-                svgEdgeLabelGroup.appendChild(labelElement);
-                const edgeBox = textContainer.getBBox();
-                const edgeX = - edgeBox.width / 2;
-                const edgeY = - edgeBox.height / 2;
-                textContainer.setAttribute('transform', 'translate(' + edgeX + ',' + edgeY + ')');
-                edge.width = edgeBox.width;
-                edge.height = edgeBox.height;
-                edge.labelElement = labelElement;
-            }
-        }
-
-        dagre.layout(graph);
-
-        for (const nodeId of graph.nodes()) {
-            if (graph.children(nodeId).length == 0) {
-                const node = graph.node(nodeId);
-                node.element.setAttribute('transform', 'translate(' + node.x + ',' + node.y + ')');
-                node.element.style.opacity = 1;
-                delete node.element;
-            }
-        }
-
-        for (const edgeId of graph.edges()) {
-            const edge = graph.edge(edgeId);
-            if (edge.labelElement) {
-                edge.labelElement.setAttribute('transform', 'translate(' + edge.x + ',' + edge.y + ')');
-                edge.labelElement.style.opacity = 1;
-                delete edge.labelElement;
-            }
-        }
-
-        const edgePathGroupDefs = this.createElement('defs');
-        svgEdgePathGroup.appendChild(edgePathGroupDefs);
-        const marker = (id) => {
-            const element = this.createElement('marker');
-            element.setAttribute('id', id);
-            element.setAttribute('viewBox', '0 0 10 10');
-            element.setAttribute('refX', 9);
-            element.setAttribute('refY', 5);
-            element.setAttribute('markerUnits', 'strokeWidth');
-            element.setAttribute('markerWidth', 8);
-            element.setAttribute('markerHeight', 6);
-            element.setAttribute('orient', 'auto');
-            const markerPath = this.createElement('path');
-            markerPath.setAttribute('d', 'M 0 0 L 10 5 L 0 10 L 4 5 z');
-            markerPath.style.setProperty('stroke-width', 1);
-            element.appendChild(markerPath);
-            return element;
-        };
-        edgePathGroupDefs.appendChild(marker("arrowhead-vee"));
-        edgePathGroupDefs.appendChild(marker("arrowhead-vee-select"));
-
-        for (const edgeId of graph.edges()) {
-            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', edge.class ? 'edge-path ' + edge.class : 'edge-path');
-            edgeElement.setAttribute('d', edgePath);
-            if (edge.id) {
-                edgeElement.setAttribute('id', edge.id);
-            }
-            svgEdgePathGroup.appendChild(edgeElement);
-        }
-
-        for (const nodeId of graph.nodes()) {
-            if (graph.children(nodeId).length > 0) {
-                const node = graph.node(nodeId);
-                const nodeElement = this.createElement('g');
-                nodeElement.setAttribute('class', 'cluster');
-                nodeElement.setAttribute('transform', 'translate(' + node.x + ',' + node.y + ')');
-                const rect = this.createElement('rect');
-                rect.setAttribute('x', - node.width / 2);
-                rect.setAttribute('y', - node.height / 2 );
-                rect.setAttribute('width', node.width);
-                rect.setAttribute('height', node.height);
-                if (node.rx) {
-                    rect.setAttribute('rx', node.rx);
-                }
-                if (node.ry) {
-                    rect.setAttribute('ry', node.ry);
-                }
-                nodeElement.appendChild(rect);
-                svgClusterGroup.appendChild(nodeElement);
-            }
-        }
-    }
-
-    createElement(name) {
-        return this._document.createElementNS('http://www.w3.org/2000/svg', name);
-    }
-
-    static _computeCurvePath(edge, tail, head) {
-        const points = edge.points.slice(1, edge.points.length - 1);
-        points.unshift(grapher.Renderer.intersectRect(tail, points[0]));
-        points.push(grapher.Renderer.intersectRect(head, points[points.length - 1]));
-
-        const path = new Path();
-        const curve = new Curve(path);
-        for (let i = 0; i < points.length; i++) {
-            const point = points[i];
-            if (i == 0) {
-                curve.lineStart();
-            }
-            curve.point(point.x, point.y);
-            if (i == points.length - 1) {
-                curve.lineEnd();
-            }
-        }
-
-        return path.data;
-    }
-
-    static intersectRect(node, point) {
-        const x = node.x;
-        const y = node.y;
-        const dx = point.x - x;
-        const dy = point.y - y;
-        let w = node.width / 2;
-        let h = node.height / 2;
-        let sx;
-        let sy;
-        if (Math.abs(dy) * w > Math.abs(dx) * h) {
-            if (dy < 0) {
-                h = -h;
-            }
-            sx = dy === 0 ? 0 : h * dx / dy;
-            sy = h;
-        }
-        else {
-            if (dx < 0) {
-                w = -w;
-            }
-            sx = w;
-            sy = dx === 0 ? 0 : w * dy / dx;
-        }
-        return {x: x + sx, y: y + sy};
-    }
-};
-
 class Path {
 
     constructor() {
@@ -754,9 +651,109 @@ class Curve {
 
 grapher.Edge = class {
 
+    constructor(from, to) {
+        this.from = from;
+        this.to = to;
+    }
+
     get arrowhead() {
         return 'vee';
     }
+
+    build(document, edgePathGroupElement, edgeLabelGroupElement) {
+        const createElement = (name) => {
+            return document.createElementNS('http://www.w3.org/2000/svg', name);
+        };
+        this.element = createElement('path');
+        if (this.id) {
+            this.element.setAttribute('id', this.id);
+        }
+        this.element.setAttribute('class', this.class ? 'edge-path ' + this.class : 'edge-path');
+        edgePathGroupElement.appendChild(this.element);
+
+        if (this.label) {
+            const tspan = createElement('tspan');
+            tspan.setAttribute('xml:space', 'preserve');
+            tspan.setAttribute('dy', '1em');
+            tspan.setAttribute('x', '1');
+            tspan.appendChild(document.createTextNode(this.label));
+            const text = createElement('text');
+            text.appendChild(tspan);
+            const textContainer = createElement('g');
+            textContainer.appendChild(text);
+            this.labelElement = createElement('g');
+            this.labelElement.style.opacity = 0;
+            this.labelElement.setAttribute('class', 'edge-label');
+            if (this.id) {
+                this.labelElement.setAttribute('id', 'edge-label-' + this.id);
+            }
+            this.labelElement.appendChild(textContainer);
+            edgeLabelGroupElement.appendChild(this.labelElement);
+            const edgeBox = textContainer.getBBox();
+            const edgeX = - edgeBox.width / 2;
+            const edgeY = - edgeBox.height / 2;
+            textContainer.setAttribute('transform', 'translate(' + edgeX + ',' + edgeY + ')');
+            this.width = edgeBox.width;
+            this.height = edgeBox.height;
+        }
+    }
+
+    layout() {
+        const edgePath = grapher.Edge._computeCurvePath(this, this.from, this.to);
+        this.element.setAttribute('d', edgePath);
+
+        if (this.labelElement) {
+            this.labelElement.setAttribute('transform', 'translate(' + this.x + ',' + this.y + ')');
+            this.labelElement.style.opacity = 1;
+        }
+    }
+
+    static _computeCurvePath(edge, tail, head) {
+        const points = edge.points.slice(1, edge.points.length - 1);
+        points.unshift(grapher.Edge._intersectRect(tail, points[0]));
+        points.push(grapher.Edge._intersectRect(head, points[points.length - 1]));
+
+        const path = new Path();
+        const curve = new Curve(path);
+        for (let i = 0; i < points.length; i++) {
+            const point = points[i];
+            if (i == 0) {
+                curve.lineStart();
+            }
+            curve.point(point.x, point.y);
+            if (i == points.length - 1) {
+                curve.lineEnd();
+            }
+        }
+
+        return path.data;
+    }
+
+    static _intersectRect(node, point) {
+        const x = node.x;
+        const y = node.y;
+        const dx = point.x - x;
+        const dy = point.y - y;
+        let w = node.width / 2;
+        let h = node.height / 2;
+        let sx;
+        let sy;
+        if (Math.abs(dy) * w > Math.abs(dx) * h) {
+            if (dy < 0) {
+                h = -h;
+            }
+            sx = dy === 0 ? 0 : h * dx / dy;
+            sy = h;
+        }
+        else {
+            if (dx < 0) {
+                w = -w;
+            }
+            sx = w;
+            sy = dx === 0 ? 0 : w * dy / dx;
+        }
+        return {x: x + sx, y: y + sy};
+    }
 };
 
 

+ 14 - 12
source/view.js

@@ -628,8 +628,6 @@ view.View = class {
                     }
                 }
 
-                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');
@@ -646,6 +644,8 @@ view.View = class {
                 originElement.setAttribute('id', 'origin');
                 canvasElement.appendChild(originElement);
 
+                viewGraph.build(this._host.document, originElement);
+
                 let svg = null;
                 switch (this._host.environment('zoom')) {
                     case 'd3': {
@@ -667,7 +667,7 @@ view.View = class {
 
                 return this._timeout(20).then(() => {
 
-                    viewGraph.render(this._host.document, originElement);
+                    viewGraph.layout();
 
                     const elements = Array.from(canvasElement.getElementsByClassName('graph-input') || []);
                     if (elements.length === 0) {
@@ -961,21 +961,18 @@ view.Graph = class extends grapher.Graph {
         return this._arguments.get(name);
     }
 
-    createEdge() {
-        const value = new view.Edge();
+    createEdge(from, to) {
+        const value = new view.Edge(from, to);
         return value;
     }
 
-    build(document, canvas) {
+    build(document, originElement) {
 
         for (const argument of this._arguments.values()) {
             argument.build();
         }
 
-        for (const key of this.nodes()) {
-            const node = this.node(key);
-            node.label = node.build ? node.build(document, canvas) : null;
-        }
+        super.build(document, originElement);
     }
 };
 
@@ -1154,6 +1151,7 @@ view.Argument = class {
     }
 
     build() {
+        this._edges = this._edges || [];
         if (this._from && this._to) {
             for (const to of this._to) {
                 let text = '';
@@ -1162,9 +1160,9 @@ view.Argument = class {
                     text = type.shape.dimensions.map((dimension) => dimension || '?').join('\u00D7');
                 }
                 if (this.context.view.showNames) {
-                    text = this._name.split('\n').shift(); // custom argument id
+                    text = this._argument.name.split('\n').shift(); // custom argument id
                 }
-                const edge = this.context.createEdge();
+                const edge = this.context.createEdge(this._from, to);
                 edge.v = this._from.name;
                 edge.w = to.name;
                 edge.label = text;
@@ -1173,6 +1171,7 @@ view.Argument = class {
                     edge.class = 'edge-path-control-dependency';
                 }
                 this.context.setEdge(edge);
+                this._edges.push(edge);
             }
         }
     }
@@ -1180,6 +1179,9 @@ view.Argument = class {
 
 view.Edge = class extends grapher.Edge {
 
+    constructor(from, to) {
+        super(from, to);
+    }
 };
 
 view.ModelContext = class {