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