Home | History | Annotate | Download | only in turbolizer
      1 // Copyright 2015 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 "use strict";
      6 
      7 class GraphView extends View {
      8   constructor (d3, id, nodes, edges, broker) {
      9     super(id, broker);
     10     var graph = this;
     11 
     12     var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
     13     graph.svg = svg;
     14 
     15     graph.nodes = nodes || [];
     16     graph.edges = edges || [];
     17 
     18     graph.minGraphX = 0;
     19     graph.maxGraphX = 1;
     20     graph.minGraphY = 0;
     21     graph.maxGraphY = 1;
     22 
     23     graph.state = {
     24       selection: null,
     25       mouseDownNode: null,
     26       justDragged: false,
     27       justScaleTransGraph: false,
     28       lastKeyDown: -1,
     29       showTypes: false
     30     };
     31 
     32     var selectionHandler = {
     33       clear: function() {
     34         broker.clear(selectionHandler);
     35       },
     36       select: function(items, selected) {
     37         var ranges = [];
     38         for (var d of items) {
     39           if (selected) {
     40             d.classList.add("selected");
     41           } else {
     42             d.classList.remove("selected");
     43           }
     44           var data = d.__data__;
     45           ranges.push([data.pos, data.pos + 1, data.id]);
     46         }
     47         broker.select(selectionHandler, ranges, selected);
     48       },
     49       selectionDifference: function(span1, inclusive1, span2, inclusive2) {
     50         // Should not be called
     51       },
     52       brokeredSelect: function(ranges, selected) {
     53         var test = [].entries().next();
     54         var selection = graph.nodes
     55           .filter(function(n) {
     56             var pos = n.pos;
     57             for (var range of ranges) {
     58               var start = range[0];
     59               var end = range[1];
     60               var id = range[2];
     61               if (end != undefined) {
     62                 if (pos >= start && pos < end) {
     63                   return true;
     64                 }
     65               } else if (start != undefined) {
     66                 if (pos === start) {
     67                   return true;
     68                 }
     69               } else {
     70                 if (n.id === id) {
     71                   return true;
     72                 }
     73               }
     74             }
     75             return false;
     76           });
     77         var newlySelected = new Set();
     78         selection.forEach(function(n) {
     79           newlySelected.add(n);
     80           if (!n.visible) {
     81             n.visible = true;
     82           }
     83         });
     84         graph.updateGraphVisibility();
     85         graph.visibleNodes.each(function(n) {
     86           if (newlySelected.has(n)) {
     87             graph.state.selection.select(this, selected);
     88           }
     89         });
     90         graph.updateGraphVisibility();
     91         graph.viewSelection();
     92       },
     93       brokeredClear: function() {
     94         graph.state.selection.clear();
     95       }
     96     };
     97     broker.addSelectionHandler(selectionHandler);
     98 
     99     graph.state.selection = new Selection(selectionHandler);
    100 
    101     var defs = svg.append('svg:defs');
    102     defs.append('svg:marker')
    103       .attr('id', 'end-arrow')
    104       .attr('viewBox', '0 -4 8 8')
    105       .attr('refX', 2)
    106       .attr('markerWidth', 2.5)
    107       .attr('markerHeight', 2.5)
    108       .attr('orient', 'auto')
    109       .append('svg:path')
    110       .attr('d', 'M0,-4L8,0L0,4');
    111 
    112     this.graphElement = svg.append("g");
    113     graph.visibleEdges = this.graphElement.append("g").selectAll("g");
    114     graph.visibleNodes = this.graphElement.append("g").selectAll("g");
    115 
    116     graph.drag = d3.behavior.drag()
    117       .origin(function(d){
    118         return {x: d.x, y: d.y};
    119       })
    120       .on("drag", function(args){
    121         graph.state.justDragged = true;
    122         graph.dragmove.call(graph, args);
    123       })
    124 
    125     d3.select("#upload").on("click", function(){
    126       document.getElementById("hidden-file-upload").click();
    127     });
    128 
    129     d3.select("#layout").on("click", function(){
    130       graph.updateGraphVisibility();
    131       graph.layoutGraph();
    132       graph.updateGraphVisibility();
    133       graph.viewWholeGraph();
    134     });
    135 
    136     d3.select("#show-all").on("click", function(){
    137       graph.nodes.filter(function(n) { n.visible = true; })
    138       graph.edges.filter(function(e) { e.visible = true; })
    139       graph.updateGraphVisibility();
    140       graph.viewWholeGraph();
    141     });
    142 
    143     d3.select("#hide-unselected").on("click", function() {
    144       var unselected = graph.visibleNodes.filter(function(n) {
    145         return !this.classList.contains("selected");
    146       });
    147       unselected.each(function(n) {
    148         n.visible = false;
    149       });
    150       graph.updateGraphVisibility();
    151     });
    152 
    153     d3.select("#hide-selected").on("click", function() {
    154       var selected = graph.visibleNodes.filter(function(n) {
    155         return this.classList.contains("selected");
    156       });
    157       selected.each(function(n) {
    158         n.visible = false;
    159       });
    160       graph.state.selection.clear();
    161       graph.updateGraphVisibility();
    162     });
    163 
    164     d3.select("#zoom-selection").on("click", function() {
    165       graph.viewSelection();
    166     });
    167 
    168     d3.select("#toggle-types").on("click", function() {
    169       graph.toggleTypes();
    170     });
    171 
    172     d3.select("#search-input").on("keydown", function() {
    173       if (d3.event.keyCode == 13) {
    174         graph.state.selection.clear();
    175         var reg = new RegExp(this.value);
    176         var filterFunction = function(n) {
    177           return (reg.exec(n.getDisplayLabel()) != null ||
    178                   (graph.state.showTypes && reg.exec(n.getDisplayType())) ||
    179                   reg.exec(n.opcode) != null);
    180         };
    181         if (d3.event.ctrlKey) {
    182           graph.nodes.forEach(function(n, i) {
    183             if (filterFunction(n)) {
    184               n.visible = true;
    185             }
    186           });
    187           graph.updateGraphVisibility();
    188         }
    189         var selected = graph.visibleNodes.each(function(n) {
    190           if (filterFunction(n)) {
    191             graph.state.selection.select(this, true);
    192           }
    193         });
    194         graph.connectVisibleSelectedNodes();
    195         graph.updateGraphVisibility();
    196         this.blur();
    197         graph.viewSelection();
    198       }
    199       d3.event.stopPropagation();
    200     });
    201 
    202     // listen for key events
    203     d3.select(window).on("keydown", function(e){
    204       graph.svgKeyDown.call(graph);
    205     })
    206       .on("keyup", function(){
    207         graph.svgKeyUp.call(graph);
    208       });
    209     svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);});
    210     svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);});
    211 
    212     graph.dragSvg = d3.behavior.zoom()
    213       .on("zoom", function(){
    214         if (d3.event.sourceEvent.shiftKey){
    215           return false;
    216         } else{
    217           graph.zoomed.call(graph);
    218         }
    219         return true;
    220       })
    221       .on("zoomstart", function(){
    222         if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
    223       })
    224       .on("zoomend", function(){
    225         d3.select('body').style("cursor", "auto");
    226       });
    227 
    228     svg.call(graph.dragSvg).on("dblclick.zoom", null);
    229   }
    230 
    231   static get selectedClass() {
    232     return "selected";
    233   }
    234   static get rectClass() {
    235     return "nodeStyle";
    236   }
    237   static get activeEditId() {
    238     return "active-editing";
    239   }
    240   static get nodeRadius() {
    241     return 50;
    242   }
    243 
    244   getNodeHeight(graph) {
    245     if (this.state.showTypes) {
    246       return DEFAULT_NODE_HEIGHT + TYPE_HEIGHT;
    247     } else {
    248       return DEFAULT_NODE_HEIGHT;
    249     }
    250   }
    251 
    252   getEdgeFrontier(nodes, inEdges, edgeFilter) {
    253     let frontier = new Set();
    254     nodes.forEach(function(element) {
    255       var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
    256       var edgeNumber = 0;
    257       edges.forEach(function(edge) {
    258         if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
    259           frontier.add(edge);
    260         }
    261         ++edgeNumber;
    262       });
    263     });
    264     return frontier;
    265   }
    266 
    267   getNodeFrontier(nodes, inEdges, edgeFilter) {
    268     let graph = this;
    269     var frontier = new Set();
    270     var newState = true;
    271     var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
    272     // Control key toggles edges rather than just turning them on
    273     if (d3.event.ctrlKey) {
    274       edgeFrontier.forEach(function(edge) {
    275         if (edge.visible) {
    276           newState = false;
    277         }
    278       });
    279     }
    280     edgeFrontier.forEach(function(edge) {
    281       edge.visible = newState;
    282       if (newState) {
    283         var node = inEdges ? edge.source : edge.target;
    284         node.visible = true;
    285         frontier.add(node);
    286       }
    287     });
    288     graph.updateGraphVisibility();
    289     if (newState) {
    290       return graph.visibleNodes.filter(function(n) {
    291         return frontier.has(n);
    292       });
    293     } else {
    294       return undefined;
    295     }
    296   }
    297 
    298   dragmove(d) {
    299     var graph = this;
    300     d.x += d3.event.dx;
    301     d.y += d3.event.dy;
    302     graph.updateGraphVisibility();
    303   }
    304 
    305   initializeContent(data, rememberedSelection) {
    306     this.createGraph(data, rememberedSelection);
    307     if (rememberedSelection != null) {
    308       this.attachSelection(rememberedSelection);
    309       this.connectVisibleSelectedNodes();
    310     }
    311     this.updateGraphVisibility();
    312   }
    313 
    314   deleteContent() {
    315     if (this.visibleNodes) {
    316       this.nodes = [];
    317       this.edges = [];
    318       this.nodeMap = [];
    319       this.updateGraphVisibility();
    320     }
    321   };
    322 
    323   createGraph(data, initiallyVisibileIds) {
    324     var g = this;
    325     g.nodes = data.nodes;
    326     g.nodeMap = [];
    327     var textMeasure = document.getElementById('text-measure');
    328     g.nodes.forEach(function(n, i){
    329       n.__proto__ = Node;
    330       n.visible = false;
    331       n.x = 0;
    332       n.y = 0;
    333       n.rank = MAX_RANK_SENTINEL;
    334       n.inputs = [];
    335       n.outputs = [];
    336       n.rpo = -1;
    337       n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
    338       n.cfg = n.control;
    339       g.nodeMap[n.id] = n;
    340       n.displayLabel = n.getDisplayLabel();
    341       textMeasure.textContent = n.getDisplayLabel();
    342       var width = textMeasure.getComputedTextLength();
    343       textMeasure.textContent = n.getDisplayType();
    344       width = Math.max(width, textMeasure.getComputedTextLength());
    345       n.width = Math.alignUp(width + NODE_INPUT_WIDTH * 2,
    346                              NODE_INPUT_WIDTH);
    347     });
    348     g.edges = [];
    349     data.edges.forEach(function(e, i){
    350       var t = g.nodeMap[e.target];
    351       var s = g.nodeMap[e.source];
    352       var newEdge = new Edge(t, e.index, s, e.type);
    353       t.inputs.push(newEdge);
    354       s.outputs.push(newEdge);
    355       g.edges.push(newEdge);
    356       if (e.type == 'control') {
    357         s.cfg = true;
    358       }
    359     });
    360     g.nodes.forEach(function(n, i) {
    361       n.visible = isNodeInitiallyVisible(n);
    362       if (initiallyVisibileIds != undefined) {
    363         if (initiallyVisibileIds.has(n.id)) {
    364           n.visible = true;
    365         }
    366       }
    367     });
    368     g.fitGraphViewToWindow();
    369     g.updateGraphVisibility();
    370     g.layoutGraph();
    371     g.updateGraphVisibility();
    372     g.viewWholeGraph();
    373   }
    374 
    375   connectVisibleSelectedNodes() {
    376     var graph = this;
    377     graph.state.selection.selection.forEach(function(element) {
    378       var edgeNumber = 0;
    379       element.__data__.inputs.forEach(function(edge) {
    380         if (edge.source.visible && edge.target.visible) {
    381           edge.visible = true;
    382         }
    383       });
    384       element.__data__.outputs.forEach(function(edge) {
    385         if (edge.source.visible && edge.target.visible) {
    386           edge.visible = true;
    387         }
    388       });
    389     });
    390   }
    391 
    392   updateInputAndOutputBubbles() {
    393     var g = this;
    394     var s = g.visibleBubbles;
    395     s.classed("filledBubbleStyle", function(c) {
    396       var components = this.id.split(',');
    397       if (components[0] == "ib") {
    398         var edge = g.nodeMap[components[3]].inputs[components[2]];
    399         return edge.isVisible();
    400       } else {
    401         return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
    402       }
    403     }).classed("halfFilledBubbleStyle", function(c) {
    404       var components = this.id.split(',');
    405       if (components[0] == "ib") {
    406         var edge = g.nodeMap[components[3]].inputs[components[2]];
    407         return false;
    408       } else {
    409         return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
    410       }
    411     }).classed("bubbleStyle", function(c) {
    412       var components = this.id.split(',');
    413       if (components[0] == "ib") {
    414         var edge = g.nodeMap[components[3]].inputs[components[2]];
    415         return !edge.isVisible();
    416       } else {
    417         return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
    418       }
    419     });
    420     s.each(function(c) {
    421       var components = this.id.split(',');
    422       if (components[0] == "ob") {
    423         var from = g.nodeMap[components[1]];
    424         var x = from.getOutputX();
    425         var y = g.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
    426         var transform = "translate(" + x + "," + y + ")";
    427         this.setAttribute('transform', transform);
    428       }
    429     });
    430   }
    431 
    432   attachSelection(s) {
    433     var graph = this;
    434     if (s.size != 0) {
    435       this.visibleNodes.each(function(n) {
    436         if (s.has(this.__data__.id)) {
    437           graph.state.selection.select(this, true);
    438         }
    439       });
    440     }
    441   }
    442 
    443   detachSelection() {
    444     var selection = this.state.selection.detachSelection();
    445     var s = new Set();
    446     for (var i of selection) {
    447       s.add(i.__data__.id);
    448     };
    449     return s;
    450   }
    451 
    452   pathMouseDown(path, d) {
    453     d3.event.stopPropagation();
    454     this.state.selection.clear();
    455     this.state.selection.add(path);
    456   };
    457 
    458   nodeMouseDown(node, d) {
    459     d3.event.stopPropagation();
    460     this.state.mouseDownNode = d;
    461   }
    462 
    463   nodeMouseUp(d3node, d) {
    464     var graph = this,
    465     state = graph.state,
    466     consts = graph.consts;
    467 
    468     var mouseDownNode = state.mouseDownNode;
    469 
    470     if (!mouseDownNode) return;
    471 
    472     if (mouseDownNode !== d){
    473       // we're in a different node: create new edge for mousedown edge and add to graph
    474       var newEdge = {source: mouseDownNode, target: d};
    475       var filtRes = graph.visibleEdges.filter(function(d){
    476         if (d.source === newEdge.target && d.target === newEdge.source){
    477           graph.edges.splice(graph.edges.indexOf(d), 1);
    478         }
    479         return d.source === newEdge.source && d.target === newEdge.target;
    480       });
    481       if (!filtRes[0].length){
    482         graph.edges.push(newEdge);
    483         graph.updateGraphVisibility();
    484       }
    485     } else{
    486       // we're in the same node
    487       if (state.justDragged) {
    488         // dragged, not clicked
    489         state.justDragged = false;
    490       } else{
    491         // clicked, not dragged
    492         var extend = d3.event.shiftKey;
    493         var selection = graph.state.selection;
    494         if (!extend) {
    495           selection.clear();
    496         }
    497         selection.select(d3node[0][0], true);
    498       }
    499     }
    500   }
    501 
    502   selectSourcePositions(start, end, selected) {
    503     var graph = this;
    504     var map = [];
    505     var sel = graph.nodes.filter(function(n) {
    506       var pos = (n.pos === undefined)
    507         ? -1
    508         : n.getFunctionRelativeSourcePosition(graph);
    509       if (pos >= start && pos < end) {
    510         map[n.id] = true;
    511         n.visible = true;
    512       }
    513     });
    514     graph.updateGraphVisibility();
    515     graph.visibleNodes.filter(function(n) { return map[n.id]; })
    516       .each(function(n) {
    517         var selection = graph.state.selection;
    518         selection.select(d3.select(this), selected);
    519       });
    520   }
    521 
    522   selectAllNodes(inEdges, filter) {
    523     var graph = this;
    524     if (!d3.event.shiftKey) {
    525       graph.state.selection.clear();
    526     }
    527     graph.state.selection.select(graph.visibleNodes[0], true);
    528     graph.updateGraphVisibility();
    529   }
    530 
    531   svgMouseDown() {
    532     this.state.graphMouseDown = true;
    533   }
    534 
    535   svgMouseUp() {
    536     var graph = this,
    537     state = graph.state;
    538     if (state.justScaleTransGraph) {
    539       // Dragged
    540       state.justScaleTransGraph = false;
    541     } else {
    542       // Clicked
    543       if (state.mouseDownNode == null) {
    544         graph.state.selection.clear();
    545       }
    546     }
    547     state.mouseDownNode = null;
    548     state.graphMouseDown = false;
    549   }
    550 
    551   svgKeyDown() {
    552     var state = this.state;
    553     var graph = this;
    554 
    555     // Don't handle key press repetition
    556     if(state.lastKeyDown !== -1) return;
    557 
    558     var showSelectionFrontierNodes = function(inEdges, filter, select) {
    559       var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter);
    560       if (frontier != undefined) {
    561         if (select) {
    562           if (!d3.event.shiftKey) {
    563             state.selection.clear();
    564           }
    565           state.selection.select(frontier[0], true);
    566         }
    567         graph.updateGraphVisibility();
    568       }
    569       allowRepetition = false;
    570     }
    571 
    572     var allowRepetition = true;
    573     var eventHandled = true; // unless the below switch defaults
    574     switch(d3.event.keyCode) {
    575     case 49:
    576     case 50:
    577     case 51:
    578     case 52:
    579     case 53:
    580     case 54:
    581     case 55:
    582     case 56:
    583     case 57:
    584       // '1'-'9'
    585       showSelectionFrontierNodes(true,
    586           (edge, index) => { return index == (d3.event.keyCode - 49); },
    587           false);
    588       break;
    589     case 67:
    590       // 'c'
    591       showSelectionFrontierNodes(true,
    592           (edge, index) => { return edge.type == 'control'; },
    593           false);
    594       break;
    595     case 69:
    596       // 'e'
    597       showSelectionFrontierNodes(true,
    598           (edge, index) => { return edge.type == 'effect'; },
    599           false);
    600       break;
    601     case 79:
    602       // 'o'
    603       showSelectionFrontierNodes(false, undefined, false);
    604       break;
    605     case 73:
    606       // 'i'
    607       showSelectionFrontierNodes(true, undefined, false);
    608       break;
    609     case 65:
    610       // 'a'
    611       graph.selectAllNodes();
    612       allowRepetition = false;
    613       break;
    614     case 38:
    615     case 40: {
    616       showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
    617       break;
    618     }
    619     default:
    620       eventHandled = false;
    621       break;
    622     }
    623     if (eventHandled) {
    624       d3.event.preventDefault();
    625     }
    626     if (!allowRepetition) {
    627       state.lastKeyDown = d3.event.keyCode;
    628     }
    629   }
    630 
    631   svgKeyUp() {
    632     this.state.lastKeyDown = -1
    633   };
    634 
    635   layoutEdges() {
    636     var graph = this;
    637     graph.maxGraphX = graph.maxGraphNodeX;
    638     this.visibleEdges.attr("d", function(edge){
    639       return edge.generatePath(graph);
    640     });
    641   }
    642 
    643   layoutGraph() {
    644     layoutNodeGraph(this);
    645   }
    646 
    647   // call to propagate changes to graph
    648   updateGraphVisibility() {
    649 
    650     var graph = this,
    651     state = graph.state;
    652 
    653     var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); });
    654     var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) {
    655       return edge.stringID();
    656     });
    657 
    658     // add new paths
    659     visibleEdges.enter()
    660       .append('path')
    661       .style('marker-end','url(#end-arrow)')
    662       .classed('hidden', function(e) {
    663         return !e.isVisible();
    664       })
    665       .attr("id", function(edge){ return "e," + edge.stringID(); })
    666       .on("mousedown", function(d){
    667         graph.pathMouseDown.call(graph, d3.select(this), d);
    668       })
    669 
    670     // Set the correct styles on all of the paths
    671     visibleEdges.classed('value', function(e) {
    672       return e.type == 'value' || e.type == 'context';
    673     }).classed('control', function(e) {
    674       return e.type == 'control';
    675     }).classed('effect', function(e) {
    676       return e.type == 'effect';
    677     }).classed('frame-state', function(e) {
    678       return e.type == 'frame-state';
    679     }).attr('stroke-dasharray', function(e) {
    680       if (e.type == 'frame-state') return "10,10";
    681       return (e.type == 'effect') ? "5,5" : "";
    682     });
    683 
    684     // remove old links
    685     visibleEdges.exit().remove();
    686 
    687     graph.visibleEdges = visibleEdges;
    688 
    689     // update existing nodes
    690     var filteredNodes = graph.nodes.filter(function(n) { return n.visible; });
    691     graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) {
    692       return d.id;
    693     });
    694     graph.visibleNodes.attr("transform", function(n){
    695       return "translate(" + n.x + "," + n.y + ")";
    696     }).select('rect').
    697       attr(HEIGHT, function(d) { return graph.getNodeHeight(); });
    698 
    699     // add new nodes
    700     var newGs = graph.visibleNodes.enter()
    701       .append("g");
    702 
    703     newGs.classed("control", function(n) { return n.isControl(); })
    704       .classed("javascript", function(n) { return n.isJavaScript(); })
    705       .classed("input", function(n) { return n.isInput(); })
    706       .classed("simplified", function(n) { return n.isSimplified(); })
    707       .classed("machine", function(n) { return n.isMachine(); })
    708       .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";})
    709       .on("mousedown", function(d){
    710         graph.nodeMouseDown.call(graph, d3.select(this), d);
    711       })
    712       .on("mouseup", function(d){
    713         graph.nodeMouseUp.call(graph, d3.select(this), d);
    714       })
    715       .call(graph.drag);
    716 
    717     newGs.append("rect")
    718       .attr("rx", 10)
    719       .attr("ry", 10)
    720       .attr(WIDTH, function(d) { return d.getTotalNodeWidth(); })
    721       .attr(HEIGHT, function(d) { return graph.getNodeHeight(); })
    722 
    723     function appendInputAndOutputBubbles(g, d) {
    724       for (var i = 0; i < d.inputs.length; ++i) {
    725         var x = d.getInputX(i);
    726         var y = -DEFAULT_NODE_BUBBLE_RADIUS / 2 - 4;
    727         var s = g.append('circle')
    728           .classed("filledBubbleStyle", function(c) {
    729             return d.inputs[i].isVisible();
    730           } )
    731           .classed("bubbleStyle", function(c) {
    732             return !d.inputs[i].isVisible();
    733           } )
    734           .attr("id", "ib," + d.inputs[i].stringID())
    735           .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
    736           .attr("transform", function(d) {
    737             return "translate(" + x + "," + y + ")";
    738           })
    739           .on("mousedown", function(d){
    740             var components = this.id.split(',');
    741             var node = graph.nodeMap[components[3]];
    742             var edge = node.inputs[components[2]];
    743             var visible = !edge.isVisible();
    744             node.setInputVisibility(components[2], visible);
    745             d3.event.stopPropagation();
    746             graph.updateGraphVisibility();
    747           });
    748       }
    749       if (d.outputs.length != 0) {
    750         var x = d.getOutputX();
    751         var y = graph.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
    752         var s = g.append('circle')
    753           .classed("filledBubbleStyle", function(c) {
    754             return d.areAnyOutputsVisible() == 2;
    755           } )
    756           .classed("halFilledBubbleStyle", function(c) {
    757             return d.areAnyOutputsVisible() == 1;
    758           } )
    759           .classed("bubbleStyle", function(c) {
    760             return d.areAnyOutputsVisible() == 0;
    761           } )
    762           .attr("id", "ob," + d.id)
    763           .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
    764           .attr("transform", function(d) {
    765             return "translate(" + x + "," + y + ")";
    766           })
    767           .on("mousedown", function(d) {
    768             d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
    769             d3.event.stopPropagation();
    770             graph.updateGraphVisibility();
    771           });
    772       }
    773     }
    774 
    775     newGs.each(function(d){
    776       appendInputAndOutputBubbles(d3.select(this), d);
    777     });
    778 
    779     newGs.each(function(d){
    780       d3.select(this).append("text")
    781         .classed("label", true)
    782         .attr("text-anchor","right")
    783         .attr("dx", "5")
    784         .attr("dy", DEFAULT_NODE_HEIGHT / 2 + 5)
    785         .append('tspan')
    786         .text(function(l) {
    787           return d.getDisplayLabel();
    788         })
    789         .append("title")
    790         .text(function(l) {
    791           return d.getLabel();
    792         })
    793       if (d.type != undefined) {
    794         d3.select(this).append("text")
    795           .classed("label", true)
    796           .classed("type", true)
    797           .attr("text-anchor","right")
    798           .attr("dx", "5")
    799           .attr("dy", DEFAULT_NODE_HEIGHT / 2 + TYPE_HEIGHT + 5)
    800           .append('tspan')
    801           .text(function(l) {
    802             return d.getDisplayType();
    803           })
    804           .append("title")
    805           .text(function(l) {
    806             return d.getType();
    807           })
    808       }
    809     });
    810 
    811     graph.visibleNodes.select('.type').each(function (d) {
    812       this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
    813     });
    814 
    815     // remove old nodes
    816     graph.visibleNodes.exit().remove();
    817 
    818     graph.visibleBubbles = d3.selectAll('circle');
    819 
    820     graph.updateInputAndOutputBubbles();
    821 
    822     graph.layoutEdges();
    823 
    824     graph.svg.style.height = '100%';
    825   }
    826 
    827   getVisibleTranslation(translate, scale) {
    828     var graph = this;
    829     var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale;
    830     var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale;
    831 
    832     var dimensions = this.getSvgViewDimensions();
    833 
    834     var baseY = translate[1];
    835     var minY = (graph.minGraphY - GRAPH_MARGIN) * scale;
    836     var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale;
    837 
    838     var adjustY = 0;
    839     var adjustYCandidate = 0;
    840     if ((maxY + baseY) < dimensions[1]) {
    841       adjustYCandidate = dimensions[1] - (maxY + baseY);
    842       if ((minY + baseY + adjustYCandidate) > 0) {
    843         adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
    844       } else {
    845         adjustY = adjustYCandidate;
    846       }
    847     } else if (-baseY < minY) {
    848       adjustYCandidate = -(baseY + minY);
    849       if ((maxY + baseY + adjustYCandidate) < dimensions[1]) {
    850         adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
    851       } else {
    852         adjustY = adjustYCandidate;
    853       }
    854     }
    855     translate[1] += adjustY;
    856 
    857     var baseX = translate[0];
    858     var minX = (graph.minGraphX - GRAPH_MARGIN) * scale;
    859     var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale;
    860 
    861     var adjustX = 0;
    862     var adjustXCandidate = 0;
    863     if ((maxX + baseX) < dimensions[0]) {
    864       adjustXCandidate = dimensions[0] - (maxX + baseX);
    865       if ((minX + baseX + adjustXCandidate) > 0) {
    866         adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
    867       } else {
    868         adjustX = adjustXCandidate;
    869       }
    870     } else if (-baseX < minX) {
    871       adjustXCandidate = -(baseX + minX);
    872       if ((maxX + baseX + adjustXCandidate) < dimensions[0]) {
    873         adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
    874       } else {
    875         adjustX = adjustXCandidate;
    876       }
    877     }
    878     translate[0] += adjustX;
    879     return translate;
    880   }
    881 
    882   translateClipped(translate, scale, transition) {
    883     var graph = this;
    884     var graphNode = this.graphElement[0][0];
    885     var translate = this.getVisibleTranslation(translate, scale);
    886     if (transition) {
    887       graphNode.classList.add('visible-transition');
    888       clearTimeout(graph.transitionTimout);
    889       graph.transitionTimout = setTimeout(function(){
    890         graphNode.classList.remove('visible-transition');
    891       }, 1000);
    892     }
    893     var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")";
    894     graphNode.style.transform = translateString;
    895     graph.dragSvg.translate(translate);
    896     graph.dragSvg.scale(scale);
    897   }
    898 
    899   zoomed(){
    900     this.state.justScaleTransGraph = true;
    901     var scale =  this.dragSvg.scale();
    902     this.translateClipped(d3.event.translate, scale);
    903   }
    904 
    905 
    906   getSvgViewDimensions() {
    907     var canvasWidth = this.parentNode.clientWidth;
    908     var documentElement = document.documentElement;
    909     var canvasHeight = documentElement.clientHeight;
    910     return [canvasWidth, canvasHeight];
    911   }
    912 
    913 
    914   minScale() {
    915     var graph = this;
    916     var dimensions = this.getSvgViewDimensions();
    917     var width = graph.maxGraphX - graph.minGraphX;
    918     var height = graph.maxGraphY - graph.minGraphY;
    919     var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2);
    920     var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2);
    921     if (minScaleYCandidate < minScale) {
    922       minScale = minScaleYCandidate;
    923     }
    924     this.dragSvg.scaleExtent([minScale, 1.5]);
    925     return minScale;
    926   }
    927 
    928   fitGraphViewToWindow() {
    929     this.svg.attr("height", document.documentElement.clientHeight + "px");
    930     this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale());
    931   }
    932 
    933   toggleTypes() {
    934     var graph = this;
    935     graph.state.showTypes = !graph.state.showTypes;
    936     var element = document.getElementById('toggle-types');
    937     if (graph.state.showTypes) {
    938       element.classList.add('button-input-toggled');
    939     } else {
    940       element.classList.remove('button-input-toggled');
    941     }
    942     graph.updateGraphVisibility();
    943   }
    944 
    945   viewSelection() {
    946     var graph = this;
    947     var minX, maxX, minY, maxY;
    948     var hasSelection = false;
    949     graph.visibleNodes.each(function(n) {
    950       if (this.classList.contains("selected")) {
    951         hasSelection = true;
    952         minX = minX ? Math.min(minX, n.x) : n.x;
    953         maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
    954           n.x + n.getTotalNodeWidth();
    955         minY = minY ? Math.min(minY, n.y) : n.y;
    956         maxY = maxY ? Math.max(maxY, n.y + DEFAULT_NODE_HEIGHT) :
    957           n.y + DEFAULT_NODE_HEIGHT;
    958       }
    959     });
    960     if (hasSelection) {
    961       graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
    962                             maxX + NODE_INPUT_WIDTH, maxY + 60,
    963                             true);
    964     }
    965   }
    966 
    967   viewGraphRegion(minX, minY, maxX, maxY, transition) {
    968     var graph = this;
    969     var dimensions = this.getSvgViewDimensions();
    970     var width = maxX - minX;
    971     var height = maxY - minY;
    972     var scale = Math.min(dimensions[0] / width, dimensions[1] / height);
    973     scale = Math.min(1.5, scale);
    974     scale = Math.max(graph.minScale(), scale);
    975     var translation = [-minX*scale, -minY*scale];
    976     translation = graph.getVisibleTranslation(translation, scale);
    977     graph.translateClipped(translation, scale, transition);
    978   }
    979 
    980   viewWholeGraph() {
    981     var graph = this;
    982     var minScale = graph.minScale();
    983     var translation = [0, 0];
    984     translation = graph.getVisibleTranslation(translation, minScale);
    985     graph.translateClipped(translation, minScale);
    986   }
    987 }
    988