Browse Source

Switch to d3.js v4

Lutz Roeder 8 years ago
parent
commit
1c6aea73da
8 changed files with 149 additions and 31 deletions
  1. 2 1
      package.json
  2. 5 5
      setup.py
  3. 4 3
      src/view-browser.html
  4. 2 1
      src/view-electron.html
  5. 2 2
      src/view-onnx.js
  6. 1 1
      src/view-render.css
  7. 110 0
      src/view-render.js
  8. 23 18
      src/view.js

+ 2 - 1
package.json

@@ -17,7 +17,8 @@
         "postinstall": "electron-builder install-app-deps"
     },
     "dependencies": {
-        "dagre-d3-renderer": "x.x.x",
+        "dagre": "x.x.x",
+        "d3": "x.x.x",
         "electron-updater": "^2.17.6",
         "flatbuffers": "^1.8.0",
         "handlebars": "x.x.x",

+ 5 - 5
setup.py

@@ -47,12 +47,12 @@ scripts = [ 'src/netron' ]
 
 custom_files = [ 
     ( 'netron', [
-        'node_modules/protobufjs/dist/protobuf.js',
-        'node_modules/flatbuffers/js/flatbuffers.js',
-        'node_modules/handlebars/dist/handlebars.js',
+        'node_modules/d3/build/d3.min.js',
+        'node_modules/dagre/dist/dagre.min.js',
+        'node_modules/handlebars/dist/handlebars.min.js',
         'node_modules/marked/marked.min.js',
-        'node_modules/dagre-d3-renderer/dist/dagre-d3.core.js',
-        'node_modules/dagre-d3-renderer/dist/dagre-d3.js',
+        'node_modules/protobufjs/dist/protobuf.min.js',
+        'node_modules/flatbuffers/js/flatbuffers.js',
         'node_modules/npm-font-open-sans/open-sans.css' ]),
     ( 'netron/fonts/Regular', [
         'node_modules/npm-font-open-sans/fonts/Regular/OpenSans-Regular.eot',

+ 4 - 3
src/view-browser.html

@@ -24,10 +24,11 @@
     <div id='sidebar-content' class='sidebar-content'>
     </div>
 </div>
-<script type='text/javascript' src='dagre-d3.js'></script>
-<script type='text/javascript' src='protobuf.js'></script>
+<script type='text/javascript' src='d3.min.js'></script>
+<script type='text/javascript' src='dagre.min.js'></script>
+<script type='text/javascript' src='protobuf.min.js'></script>
 <script type='text/javascript' src='flatbuffers.js'></script>
-<script type='text/javascript' src='handlebars.js'></script>
+<script type='text/javascript' src='handlebars.min.js'></script>
 <script type='text/javascript' src='marked.min.js'></script>
 <script type='text/javascript' src='onnx.js'></script>
 <script type='text/javascript' src='tf.js'></script>

+ 2 - 1
src/view-electron.html

@@ -23,7 +23,8 @@
     <div id='sidebar-content' class='sidebar-content'>
     </div>
 </div>
-<script type='text/javascript' src='../node_modules/dagre-d3-renderer/dist/dagre-d3.js'></script>
+<script type='text/javascript' src='../node_modules/d3/build/d3.js'></script>
+<script type='text/javascript' src='../node_modules/dagre/dist/dagre.js'></script>
 <script type='text/javascript' src='../node_modules/protobufjs/dist/protobuf.js'></script>
 <script type='text/javascript' src='../node_modules/flatbuffers/js/flatbuffers.js'></script>
 <script type='text/javascript' src='../node_modules/handlebars/dist/handlebars.min.js'></script>

+ 2 - 2
src/view-onnx.js

@@ -319,14 +319,14 @@ class OnnxAttribute {
                 if (s.filter(c => c <= 32 && c >= 128).length == 0) {
                     return '"' + String.fromCharCode.apply(null, s) + '"';
                 }
-                return s.map(v => v.toString()).join(', ');    
+                return s.map(v => v.toString()).join(', ');
             }).join(', ');
         }
         else if (this._attribute.s && this._attribute.s.length > 0) {
             if (this._attribute.s.filter(c => c <= 32 && c >= 128).length == 0) {
                 return '"' + String.fromCharCode.apply(null, this._attribute.s) + '"';
             }
-            return this._attribute.s.map(v => v.toString()).join(', ');           
+            return this._attribute.s.map(v => v.toString()).join(', ');
         }
         else if (this._attribute.hasOwnProperty('f')) {
             return this._attribute.f.toString();

+ 1 - 1
src/view-render.css

@@ -20,5 +20,5 @@
 .node-attribute:hover path { fill: #f6f6f6; }
 
 .edgeLabel text { font-family: 'Open Sans', --apple-system, "Helvetica Neue", Helvetica, Arial, sans-serf; font-size: 10px; }
-.edgePath path { stroke: #000; stroke-width: 1px; fill: none; }
+.edgePath { stroke: #000; stroke-width: 1px; fill: none; }
 .node rect { stroke: none; fill: none; stroke-width: 0; }

+ 110 - 0
src/view-render.js

@@ -1,5 +1,115 @@
 /*jshint esversion: 6 */
 
+class GraphRenderer {
+
+    constructor(svg) {
+        this._svg = svg;
+    }
+
+    render(graph) {
+
+        var svgEdgePaths = this._svg.append('g').classed('edgePaths', true);
+        var svtEdgeLabels = this._svg.append('g').classed('edgeLabels', true);
+        var svgNodes = this._svg.append('g').classed('nodes', true);
+
+        graph.nodes().forEach((nodeId) => {
+            var node = graph.node(nodeId);
+            var svgNode = svgNodes.append('g').classed('node', true).style('opacity', 0);
+            svgNode.node().appendChild(node.label);
+            if (node.hasOwnProperty('class')) {
+                svgNode.classed(node.class, true);
+            }
+            var bbox = node.label.getBBox();
+            var x = - bbox.width / 2;
+            var y = - bbox.height / 2;
+            d3.select(node.label).attr('transform', 'translate(' + x + ',' + y + ')');
+            node.width = bbox.width;
+            node.height = bbox.height;
+            node.element = svgNode;
+        });
+
+        graph.edges().forEach((edgeId) => {
+            var edge = graph.edge(edgeId);
+            var svgEdgeLabel = svtEdgeLabels.append('g').classed('edgeLabel', true).style('opacity', 0);
+            var edgeLabel = svgEdgeLabel.append('text');
+            edgeLabel.append('tspan').attr('xml:space', 'preserve').attr('dy', '1em').attr('x', '1').text(edge.label);
+            var bbox = edgeLabel.node().getBBox();
+            var x = - bbox.width / 2;
+            var y = - bbox.height / 2;
+            edgeLabel.attr('transform', 'translate(' + x + ',' + y + ')');
+            edge.width = bbox.width;
+            edge.height = bbox.height;
+            edge.element = svgEdgeLabel;
+        });
+
+        dagre.layout(graph);
+
+        graph.nodes().forEach((nodeId) => {
+            var node = graph.node(nodeId);
+            node.element.attr('transform', 'translate(' + node.x + ',' + node.y + ')').style('opacity', 1);
+        });
+
+        graph.edges().forEach((edgeId) => {
+            var edge = graph.edge(edgeId);
+            edge.element.attr('transform', 'translate(' + edge.x + ',' + edge.y + ')').style('opacity', 1);
+        });
+
+        svgEdgePaths.append('defs')
+            .append('marker')
+                .attr('id', 'arrowhead-vee')
+                .attr('viewBox', '0 0 10 10').attr('refX', 9).attr('refY', 5)
+                .attr('markerUnits', 'strokeWidth').attr('markerWidth', 8).attr('markerHeight', 6).attr('orient', 'auto')
+            .append('path')
+                .attr('d', 'M 0 0 L 10 5 L 0 10 L 4 5 z')
+                .style('stroke-width', 1).style('stroke-dasharray', '1,0');   
+        graph.edges().forEach((edgeId) => {
+            var points = GraphRenderer.calcPoints(graph, edgeId);
+            var svgEdge = svgEdgePaths.append('path').classed('edgePath', true).attr('d', points);
+            svgEdge.attr('marker-end', 'url(#arrowhead-vee)');
+        });
+    }
+
+    static calcPoints(g, e) {
+        const edge = g.edge(e);
+        const tail = g.node(e.v);
+        const head = g.node(e.w);
+        const points = edge.points.slice(1, edge.points.length - 1);
+        points.unshift(GraphRenderer.intersectRect(tail, points[0]));
+        points.push(GraphRenderer.intersectRect(head, points[points.length - 1]));
+        var line = d3.line().x(d => d.x).y(d => d.y);
+        if (edge.hasOwnProperty('curve')) {
+            line.curve(edge.curve);
+        }
+        return line(points);
+      }
+      
+    static intersectRect(node, point) {
+        var x = node.x;
+        var y = node.y;
+        var dx = point.x - x;
+        var dy = point.y - y;
+        var w = node.width / 2;
+        var h = node.height / 2;
+        var sx;
+        var 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 NodeFormatter {
 
     constructor(context) {

+ 23 - 18
src/view.js

@@ -99,9 +99,9 @@ function updateGraph(model) {
     while (svgElement.lastChild) {
         svgElement.removeChild(svgElement.lastChild);
     }
-    var svg = dagreD3.d3.select(svgElement);
+    var svg = d3.select(svgElement);
 
-    var g = new dagreD3.graphlib.Graph();
+    var g = new dagre.graphlib.Graph();
     g.setGraph({ });
     // g.setGraph({ align: 'DR' });
     // g.setGraph({ ranker: 'network-simplex' });
@@ -203,7 +203,7 @@ function updateGraph(model) {
             });
         }
 
-        g.setNode(nodeId++, { label: formatter.format(svg).node(), labelType: 'svg', padding: 0 });
+        g.setNode(nodeId++, { label: formatter.format(svg).node() });
     });
 
     graph.inputs.forEach((input) => {
@@ -219,7 +219,7 @@ function updateGraph(model) {
 
         var formatter = new NodeFormatter();
         formatter.addItem(input.name, null, input.type, null);
-        g.setNode(nodeId++, { label: formatter.format(svg).node(), class: 'graph-input', labelType: 'svg', padding: 0 } ); 
+        g.setNode(nodeId++, { label: formatter.format(svg).node(), class: 'graph-input' } ); 
     });
 
     graph.outputs.forEach((output) => {
@@ -256,10 +256,10 @@ function updateGraph(model) {
                 }
 
                 if (to.control) { 
-                    g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', lineInterpolate: "basis", style: 'stroke-dasharray: 5, 5;' } );
+                    g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', curve: d3.curveBasis, style: 'stroke-dasharray: 5, 5;' } );
                 }
                 else {
-                    g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', lineInterpolate: "basis" } );
+                    g.setEdge(tuple.from.node, to.node, { label: text, arrowhead: 'vee', curve: d3.curveBasis } );
                 }
             });
         }
@@ -272,19 +272,23 @@ function updateGraph(model) {
         }
     });
 
-    var inner = svg.append('g');
+    var output = svg.append('g');
 
     // Set up zoom support
-    var zoom = dagreD3.d3.behavior.zoom().scaleExtent([0.1, 2]).on('zoom', function() {
-        inner.attr('transform', 'translate(' + dagreD3.d3.event.translate + ')' + 'scale(' + dagreD3.d3.event.scale + ')');
+    var zoom = d3.zoom();
+    zoom.scaleExtent([0.1, 2]);
+    zoom.on('zoom', function() {
+        output.attr('transform', d3.event.transform);
     });
     svg.call(zoom);
 
     setTimeout(function () {
 
-        var render = new dagreD3.render();
-        render(dagreD3.d3.select('svg g'), g);
-    
+        var svgOutput = output;
+
+        var graphRenderer = new GraphRenderer(svgOutput);
+        graphRenderer.render(g);
+
         // Workaround for Safari background drag/zoom issue:
         // https://stackoverflow.com/questions/40887193/d3-js-zoom-is-not-working-with-mousewheel-in-safari
         svg.insert('rect', ':first-child').attr('width', '100%').attr('height', '100%').attr('fill', 'none').attr('pointer-events', 'all');
@@ -297,15 +301,16 @@ function updateGraph(model) {
             var x = 0;
             var y = 0;
             for (var i = 0; i < inputElements.length; i++) {
-                var inputTransform = dagreD3.d3.transform(dagreD3.d3.select(inputElements[i]).attr('transform'));
-                x += inputTransform.translate[0];
-                y += inputTransform.translate[1];
+                var inputTransform = inputElements[i].transform.baseVal.consolidate().matrix;
+                x += inputTransform.e;
+                y += inputTransform.f;
             }
             x = x / inputElements.length;
             y = y / inputElements.length;
-            zoom.translate([ 
-                (svgSize.width / 2) - x,
-                (svgSize.height / 4) - y ]).event(svg);
+
+            zoom.translateBy(svg, (svgSize.width / 2) - x, (svgSize.height / 4) - y);
+
+            output.append('rect').attr('width', 2).attr('height', 2).style('fill', '#f00');
         }
         else {
         //    zoom.translate([ (svgSize.width - g.graph().width) / 2, 40 ]).event(svg);