Merge V8 5.3.332.45.  DO NOT MERGE

Test: Manual

FPIIM-449

Change-Id: Id3254828b068abdea3cb10442e0172a8c9a98e03
(cherry picked from commit 13e2dadd00298019ed862f2b2fc5068bba730bcf)
diff --git a/tools/turbolizer/graph-view.js b/tools/turbolizer/graph-view.js
new file mode 100644
index 0000000..41d35d8
--- /dev/null
+++ b/tools/turbolizer/graph-view.js
@@ -0,0 +1,987 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+"use strict";
+
+class GraphView extends View {
+  constructor (d3, id, nodes, edges, broker) {
+    super(id, broker);
+    var graph = this;
+
+    var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
+    graph.svg = svg;
+
+    graph.nodes = nodes || [];
+    graph.edges = edges || [];
+
+    graph.minGraphX = 0;
+    graph.maxGraphX = 1;
+    graph.minGraphY = 0;
+    graph.maxGraphY = 1;
+
+    graph.state = {
+      selection: null,
+      mouseDownNode: null,
+      justDragged: false,
+      justScaleTransGraph: false,
+      lastKeyDown: -1,
+      showTypes: false
+    };
+
+    var selectionHandler = {
+      clear: function() {
+        broker.clear(selectionHandler);
+      },
+      select: function(items, selected) {
+        var ranges = [];
+        for (var d of items) {
+          if (selected) {
+            d.classList.add("selected");
+          } else {
+            d.classList.remove("selected");
+          }
+          var data = d.__data__;
+          ranges.push([data.pos, data.pos + 1, data.id]);
+        }
+        broker.select(selectionHandler, ranges, selected);
+      },
+      selectionDifference: function(span1, inclusive1, span2, inclusive2) {
+        // Should not be called
+      },
+      brokeredSelect: function(ranges, selected) {
+        var test = [].entries().next();
+        var selection = graph.nodes
+          .filter(function(n) {
+            var pos = n.pos;
+            for (var range of ranges) {
+              var start = range[0];
+              var end = range[1];
+              var id = range[2];
+              if (end != undefined) {
+                if (pos >= start && pos < end) {
+                  return true;
+                }
+              } else if (start != undefined) {
+                if (pos === start) {
+                  return true;
+                }
+              } else {
+                if (n.id === id) {
+                  return true;
+                }
+              }
+            }
+            return false;
+          });
+        var newlySelected = new Set();
+        selection.forEach(function(n) {
+          newlySelected.add(n);
+          if (!n.visible) {
+            n.visible = true;
+          }
+        });
+        graph.updateGraphVisibility();
+        graph.visibleNodes.each(function(n) {
+          if (newlySelected.has(n)) {
+            graph.state.selection.select(this, selected);
+          }
+        });
+        graph.updateGraphVisibility();
+        graph.viewSelection();
+      },
+      brokeredClear: function() {
+        graph.state.selection.clear();
+      }
+    };
+    broker.addSelectionHandler(selectionHandler);
+
+    graph.state.selection = new Selection(selectionHandler);
+
+    var defs = svg.append('svg:defs');
+    defs.append('svg:marker')
+      .attr('id', 'end-arrow')
+      .attr('viewBox', '0 -4 8 8')
+      .attr('refX', 2)
+      .attr('markerWidth', 2.5)
+      .attr('markerHeight', 2.5)
+      .attr('orient', 'auto')
+      .append('svg:path')
+      .attr('d', 'M0,-4L8,0L0,4');
+
+    this.graphElement = svg.append("g");
+    graph.visibleEdges = this.graphElement.append("g").selectAll("g");
+    graph.visibleNodes = this.graphElement.append("g").selectAll("g");
+
+    graph.drag = d3.behavior.drag()
+      .origin(function(d){
+        return {x: d.x, y: d.y};
+      })
+      .on("drag", function(args){
+        graph.state.justDragged = true;
+        graph.dragmove.call(graph, args);
+      })
+
+    d3.select("#upload").on("click", function(){
+      document.getElementById("hidden-file-upload").click();
+    });
+
+    d3.select("#layout").on("click", function(){
+      graph.updateGraphVisibility();
+      graph.layoutGraph();
+      graph.updateGraphVisibility();
+      graph.viewWholeGraph();
+    });
+
+    d3.select("#show-all").on("click", function(){
+      graph.nodes.filter(function(n) { n.visible = true; })
+      graph.edges.filter(function(e) { e.visible = true; })
+      graph.updateGraphVisibility();
+      graph.viewWholeGraph();
+    });
+
+    d3.select("#hide-unselected").on("click", function() {
+      var unselected = graph.visibleNodes.filter(function(n) {
+        return !this.classList.contains("selected");
+      });
+      unselected.each(function(n) {
+        n.visible = false;
+      });
+      graph.updateGraphVisibility();
+    });
+
+    d3.select("#hide-selected").on("click", function() {
+      var selected = graph.visibleNodes.filter(function(n) {
+        return this.classList.contains("selected");
+      });
+      selected.each(function(n) {
+        n.visible = false;
+      });
+      graph.state.selection.clear();
+      graph.updateGraphVisibility();
+    });
+
+    d3.select("#zoom-selection").on("click", function() {
+      graph.viewSelection();
+    });
+
+    d3.select("#toggle-types").on("click", function() {
+      graph.toggleTypes();
+    });
+
+    d3.select("#search-input").on("keydown", function() {
+      if (d3.event.keyCode == 13) {
+        graph.state.selection.clear();
+        var reg = new RegExp(this.value);
+        var filterFunction = function(n) {
+          return (reg.exec(n.getDisplayLabel()) != null ||
+                  (graph.state.showTypes && reg.exec(n.getDisplayType())) ||
+                  reg.exec(n.opcode) != null);
+        };
+        if (d3.event.ctrlKey) {
+          graph.nodes.forEach(function(n, i) {
+            if (filterFunction(n)) {
+              n.visible = true;
+            }
+          });
+          graph.updateGraphVisibility();
+        }
+        var selected = graph.visibleNodes.each(function(n) {
+          if (filterFunction(n)) {
+            graph.state.selection.select(this, true);
+          }
+        });
+        graph.connectVisibleSelectedNodes();
+        graph.updateGraphVisibility();
+        this.blur();
+        graph.viewSelection();
+      }
+      d3.event.stopPropagation();
+    });
+
+    // listen for key events
+    d3.select(window).on("keydown", function(e){
+      graph.svgKeyDown.call(graph);
+    })
+      .on("keyup", function(){
+        graph.svgKeyUp.call(graph);
+      });
+    svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);});
+    svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);});
+
+    graph.dragSvg = d3.behavior.zoom()
+      .on("zoom", function(){
+        if (d3.event.sourceEvent.shiftKey){
+          return false;
+        } else{
+          graph.zoomed.call(graph);
+        }
+        return true;
+      })
+      .on("zoomstart", function(){
+        if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
+      })
+      .on("zoomend", function(){
+        d3.select('body').style("cursor", "auto");
+      });
+
+    svg.call(graph.dragSvg).on("dblclick.zoom", null);
+  }
+
+  static get selectedClass() {
+    return "selected";
+  }
+  static get rectClass() {
+    return "nodeStyle";
+  }
+  static get activeEditId() {
+    return "active-editing";
+  }
+  static get nodeRadius() {
+    return 50;
+  }
+
+  getNodeHeight(graph) {
+    if (this.state.showTypes) {
+      return DEFAULT_NODE_HEIGHT + TYPE_HEIGHT;
+    } else {
+      return DEFAULT_NODE_HEIGHT;
+    }
+  }
+
+  getEdgeFrontier(nodes, inEdges, edgeFilter) {
+    let frontier = new Set();
+    nodes.forEach(function(element) {
+      var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
+      var edgeNumber = 0;
+      edges.forEach(function(edge) {
+        if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
+          frontier.add(edge);
+        }
+        ++edgeNumber;
+      });
+    });
+    return frontier;
+  }
+
+  getNodeFrontier(nodes, inEdges, edgeFilter) {
+    let graph = this;
+    var frontier = new Set();
+    var newState = true;
+    var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
+    // Control key toggles edges rather than just turning them on
+    if (d3.event.ctrlKey) {
+      edgeFrontier.forEach(function(edge) {
+        if (edge.visible) {
+          newState = false;
+        }
+      });
+    }
+    edgeFrontier.forEach(function(edge) {
+      edge.visible = newState;
+      if (newState) {
+        var node = inEdges ? edge.source : edge.target;
+        node.visible = true;
+        frontier.add(node);
+      }
+    });
+    graph.updateGraphVisibility();
+    if (newState) {
+      return graph.visibleNodes.filter(function(n) {
+        return frontier.has(n);
+      });
+    } else {
+      return undefined;
+    }
+  }
+
+  dragmove(d) {
+    var graph = this;
+    d.x += d3.event.dx;
+    d.y += d3.event.dy;
+    graph.updateGraphVisibility();
+  }
+
+  initializeContent(data, rememberedSelection) {
+    this.createGraph(data, rememberedSelection);
+    if (rememberedSelection != null) {
+      this.attachSelection(rememberedSelection);
+      this.connectVisibleSelectedNodes();
+    }
+    this.updateGraphVisibility();
+  }
+
+  deleteContent() {
+    if (this.visibleNodes) {
+      this.nodes = [];
+      this.edges = [];
+      this.nodeMap = [];
+      this.updateGraphVisibility();
+    }
+  };
+
+  createGraph(data, initiallyVisibileIds) {
+    var g = this;
+    g.nodes = data.nodes;
+    g.nodeMap = [];
+    var textMeasure = document.getElementById('text-measure');
+    g.nodes.forEach(function(n, i){
+      n.__proto__ = Node;
+      n.visible = false;
+      n.x = 0;
+      n.y = 0;
+      n.rank = MAX_RANK_SENTINEL;
+      n.inputs = [];
+      n.outputs = [];
+      n.rpo = -1;
+      n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
+      n.cfg = n.control;
+      g.nodeMap[n.id] = n;
+      n.displayLabel = n.getDisplayLabel();
+      textMeasure.textContent = n.getDisplayLabel();
+      var width = textMeasure.getComputedTextLength();
+      textMeasure.textContent = n.getDisplayType();
+      width = Math.max(width, textMeasure.getComputedTextLength());
+      n.width = Math.alignUp(width + NODE_INPUT_WIDTH * 2,
+                             NODE_INPUT_WIDTH);
+    });
+    g.edges = [];
+    data.edges.forEach(function(e, i){
+      var t = g.nodeMap[e.target];
+      var s = g.nodeMap[e.source];
+      var newEdge = new Edge(t, e.index, s, e.type);
+      t.inputs.push(newEdge);
+      s.outputs.push(newEdge);
+      g.edges.push(newEdge);
+      if (e.type == 'control') {
+        s.cfg = true;
+      }
+    });
+    g.nodes.forEach(function(n, i) {
+      n.visible = isNodeInitiallyVisible(n);
+      if (initiallyVisibileIds != undefined) {
+        if (initiallyVisibileIds.has(n.id)) {
+          n.visible = true;
+        }
+      }
+    });
+    g.fitGraphViewToWindow();
+    g.updateGraphVisibility();
+    g.layoutGraph();
+    g.updateGraphVisibility();
+    g.viewWholeGraph();
+  }
+
+  connectVisibleSelectedNodes() {
+    var graph = this;
+    graph.state.selection.selection.forEach(function(element) {
+      var edgeNumber = 0;
+      element.__data__.inputs.forEach(function(edge) {
+        if (edge.source.visible && edge.target.visible) {
+          edge.visible = true;
+        }
+      });
+      element.__data__.outputs.forEach(function(edge) {
+        if (edge.source.visible && edge.target.visible) {
+          edge.visible = true;
+        }
+      });
+    });
+  }
+
+  updateInputAndOutputBubbles() {
+    var g = this;
+    var s = g.visibleBubbles;
+    s.classed("filledBubbleStyle", function(c) {
+      var components = this.id.split(',');
+      if (components[0] == "ib") {
+        var edge = g.nodeMap[components[3]].inputs[components[2]];
+        return edge.isVisible();
+      } else {
+        return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
+      }
+    }).classed("halfFilledBubbleStyle", function(c) {
+      var components = this.id.split(',');
+      if (components[0] == "ib") {
+        var edge = g.nodeMap[components[3]].inputs[components[2]];
+        return false;
+      } else {
+        return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
+      }
+    }).classed("bubbleStyle", function(c) {
+      var components = this.id.split(',');
+      if (components[0] == "ib") {
+        var edge = g.nodeMap[components[3]].inputs[components[2]];
+        return !edge.isVisible();
+      } else {
+        return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
+      }
+    });
+    s.each(function(c) {
+      var components = this.id.split(',');
+      if (components[0] == "ob") {
+        var from = g.nodeMap[components[1]];
+        var x = from.getOutputX();
+        var y = g.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
+        var transform = "translate(" + x + "," + y + ")";
+        this.setAttribute('transform', transform);
+      }
+    });
+  }
+
+  attachSelection(s) {
+    var graph = this;
+    if (s.size != 0) {
+      this.visibleNodes.each(function(n) {
+        if (s.has(this.__data__.id)) {
+          graph.state.selection.select(this, true);
+        }
+      });
+    }
+  }
+
+  detachSelection() {
+    var selection = this.state.selection.detachSelection();
+    var s = new Set();
+    for (var i of selection) {
+      s.add(i.__data__.id);
+    };
+    return s;
+  }
+
+  pathMouseDown(path, d) {
+    d3.event.stopPropagation();
+    this.state.selection.clear();
+    this.state.selection.add(path);
+  };
+
+  nodeMouseDown(node, d) {
+    d3.event.stopPropagation();
+    this.state.mouseDownNode = d;
+  }
+
+  nodeMouseUp(d3node, d) {
+    var graph = this,
+    state = graph.state,
+    consts = graph.consts;
+
+    var mouseDownNode = state.mouseDownNode;
+
+    if (!mouseDownNode) return;
+
+    if (mouseDownNode !== d){
+      // we're in a different node: create new edge for mousedown edge and add to graph
+      var newEdge = {source: mouseDownNode, target: d};
+      var filtRes = graph.visibleEdges.filter(function(d){
+        if (d.source === newEdge.target && d.target === newEdge.source){
+          graph.edges.splice(graph.edges.indexOf(d), 1);
+        }
+        return d.source === newEdge.source && d.target === newEdge.target;
+      });
+      if (!filtRes[0].length){
+        graph.edges.push(newEdge);
+        graph.updateGraphVisibility();
+      }
+    } else{
+      // we're in the same node
+      if (state.justDragged) {
+        // dragged, not clicked
+        state.justDragged = false;
+      } else{
+        // clicked, not dragged
+        var extend = d3.event.shiftKey;
+        var selection = graph.state.selection;
+        if (!extend) {
+          selection.clear();
+        }
+        selection.select(d3node[0][0], true);
+      }
+    }
+  }
+
+  selectSourcePositions(start, end, selected) {
+    var graph = this;
+    var map = [];
+    var sel = graph.nodes.filter(function(n) {
+      var pos = (n.pos === undefined)
+        ? -1
+        : n.getFunctionRelativeSourcePosition(graph);
+      if (pos >= start && pos < end) {
+        map[n.id] = true;
+        n.visible = true;
+      }
+    });
+    graph.updateGraphVisibility();
+    graph.visibleNodes.filter(function(n) { return map[n.id]; })
+      .each(function(n) {
+        var selection = graph.state.selection;
+        selection.select(d3.select(this), selected);
+      });
+  }
+
+  selectAllNodes(inEdges, filter) {
+    var graph = this;
+    if (!d3.event.shiftKey) {
+      graph.state.selection.clear();
+    }
+    graph.state.selection.select(graph.visibleNodes[0], true);
+    graph.updateGraphVisibility();
+  }
+
+  svgMouseDown() {
+    this.state.graphMouseDown = true;
+  }
+
+  svgMouseUp() {
+    var graph = this,
+    state = graph.state;
+    if (state.justScaleTransGraph) {
+      // Dragged
+      state.justScaleTransGraph = false;
+    } else {
+      // Clicked
+      if (state.mouseDownNode == null) {
+        graph.state.selection.clear();
+      }
+    }
+    state.mouseDownNode = null;
+    state.graphMouseDown = false;
+  }
+
+  svgKeyDown() {
+    var state = this.state;
+    var graph = this;
+
+    // Don't handle key press repetition
+    if(state.lastKeyDown !== -1) return;
+
+    var showSelectionFrontierNodes = function(inEdges, filter, select) {
+      var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter);
+      if (frontier != undefined) {
+        if (select) {
+          if (!d3.event.shiftKey) {
+            state.selection.clear();
+          }
+          state.selection.select(frontier[0], true);
+        }
+        graph.updateGraphVisibility();
+      }
+      allowRepetition = false;
+    }
+
+    var allowRepetition = true;
+    var eventHandled = true; // unless the below switch defaults
+    switch(d3.event.keyCode) {
+    case 49:
+    case 50:
+    case 51:
+    case 52:
+    case 53:
+    case 54:
+    case 55:
+    case 56:
+    case 57:
+      // '1'-'9'
+      showSelectionFrontierNodes(true,
+          (edge, index) => { return index == (d3.event.keyCode - 49); },
+          false);
+      break;
+    case 67:
+      // 'c'
+      showSelectionFrontierNodes(true,
+          (edge, index) => { return edge.type == 'control'; },
+          false);
+      break;
+    case 69:
+      // 'e'
+      showSelectionFrontierNodes(true,
+          (edge, index) => { return edge.type == 'effect'; },
+          false);
+      break;
+    case 79:
+      // 'o'
+      showSelectionFrontierNodes(false, undefined, false);
+      break;
+    case 73:
+      // 'i'
+      showSelectionFrontierNodes(true, undefined, false);
+      break;
+    case 65:
+      // 'a'
+      graph.selectAllNodes();
+      allowRepetition = false;
+      break;
+    case 38:
+    case 40: {
+      showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
+      break;
+    }
+    default:
+      eventHandled = false;
+      break;
+    }
+    if (eventHandled) {
+      d3.event.preventDefault();
+    }
+    if (!allowRepetition) {
+      state.lastKeyDown = d3.event.keyCode;
+    }
+  }
+
+  svgKeyUp() {
+    this.state.lastKeyDown = -1
+  };
+
+  layoutEdges() {
+    var graph = this;
+    graph.maxGraphX = graph.maxGraphNodeX;
+    this.visibleEdges.attr("d", function(edge){
+      return edge.generatePath(graph);
+    });
+  }
+
+  layoutGraph() {
+    layoutNodeGraph(this);
+  }
+
+  // call to propagate changes to graph
+  updateGraphVisibility() {
+
+    var graph = this,
+    state = graph.state;
+
+    var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); });
+    var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) {
+      return edge.stringID();
+    });
+
+    // add new paths
+    visibleEdges.enter()
+      .append('path')
+      .style('marker-end','url(#end-arrow)')
+      .classed('hidden', function(e) {
+        return !e.isVisible();
+      })
+      .attr("id", function(edge){ return "e," + edge.stringID(); })
+      .on("mousedown", function(d){
+        graph.pathMouseDown.call(graph, d3.select(this), d);
+      })
+
+    // Set the correct styles on all of the paths
+    visibleEdges.classed('value', function(e) {
+      return e.type == 'value' || e.type == 'context';
+    }).classed('control', function(e) {
+      return e.type == 'control';
+    }).classed('effect', function(e) {
+      return e.type == 'effect';
+    }).classed('frame-state', function(e) {
+      return e.type == 'frame-state';
+    }).attr('stroke-dasharray', function(e) {
+      if (e.type == 'frame-state') return "10,10";
+      return (e.type == 'effect') ? "5,5" : "";
+    });
+
+    // remove old links
+    visibleEdges.exit().remove();
+
+    graph.visibleEdges = visibleEdges;
+
+    // update existing nodes
+    var filteredNodes = graph.nodes.filter(function(n) { return n.visible; });
+    graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) {
+      return d.id;
+    });
+    graph.visibleNodes.attr("transform", function(n){
+      return "translate(" + n.x + "," + n.y + ")";
+    }).select('rect').
+      attr(HEIGHT, function(d) { return graph.getNodeHeight(); });
+
+    // add new nodes
+    var newGs = graph.visibleNodes.enter()
+      .append("g");
+
+    newGs.classed("control", function(n) { return n.isControl(); })
+      .classed("javascript", function(n) { return n.isJavaScript(); })
+      .classed("input", function(n) { return n.isInput(); })
+      .classed("simplified", function(n) { return n.isSimplified(); })
+      .classed("machine", function(n) { return n.isMachine(); })
+      .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";})
+      .on("mousedown", function(d){
+        graph.nodeMouseDown.call(graph, d3.select(this), d);
+      })
+      .on("mouseup", function(d){
+        graph.nodeMouseUp.call(graph, d3.select(this), d);
+      })
+      .call(graph.drag);
+
+    newGs.append("rect")
+      .attr("rx", 10)
+      .attr("ry", 10)
+      .attr(WIDTH, function(d) { return d.getTotalNodeWidth(); })
+      .attr(HEIGHT, function(d) { return graph.getNodeHeight(); })
+
+    function appendInputAndOutputBubbles(g, d) {
+      for (var i = 0; i < d.inputs.length; ++i) {
+        var x = d.getInputX(i);
+        var y = -DEFAULT_NODE_BUBBLE_RADIUS / 2 - 4;
+        var s = g.append('circle')
+          .classed("filledBubbleStyle", function(c) {
+            return d.inputs[i].isVisible();
+          } )
+          .classed("bubbleStyle", function(c) {
+            return !d.inputs[i].isVisible();
+          } )
+          .attr("id", "ib," + d.inputs[i].stringID())
+          .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+          .attr("transform", function(d) {
+            return "translate(" + x + "," + y + ")";
+          })
+          .on("mousedown", function(d){
+            var components = this.id.split(',');
+            var node = graph.nodeMap[components[3]];
+            var edge = node.inputs[components[2]];
+            var visible = !edge.isVisible();
+            node.setInputVisibility(components[2], visible);
+            d3.event.stopPropagation();
+            graph.updateGraphVisibility();
+          });
+      }
+      if (d.outputs.length != 0) {
+        var x = d.getOutputX();
+        var y = graph.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4;
+        var s = g.append('circle')
+          .classed("filledBubbleStyle", function(c) {
+            return d.areAnyOutputsVisible() == 2;
+          } )
+          .classed("halFilledBubbleStyle", function(c) {
+            return d.areAnyOutputsVisible() == 1;
+          } )
+          .classed("bubbleStyle", function(c) {
+            return d.areAnyOutputsVisible() == 0;
+          } )
+          .attr("id", "ob," + d.id)
+          .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+          .attr("transform", function(d) {
+            return "translate(" + x + "," + y + ")";
+          })
+          .on("mousedown", function(d) {
+            d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
+            d3.event.stopPropagation();
+            graph.updateGraphVisibility();
+          });
+      }
+    }
+
+    newGs.each(function(d){
+      appendInputAndOutputBubbles(d3.select(this), d);
+    });
+
+    newGs.each(function(d){
+      d3.select(this).append("text")
+        .classed("label", true)
+        .attr("text-anchor","right")
+        .attr("dx", "5")
+        .attr("dy", DEFAULT_NODE_HEIGHT / 2 + 5)
+        .append('tspan')
+        .text(function(l) {
+          return d.getDisplayLabel();
+        })
+        .append("title")
+        .text(function(l) {
+          return d.getLabel();
+        })
+      if (d.type != undefined) {
+        d3.select(this).append("text")
+          .classed("label", true)
+          .classed("type", true)
+          .attr("text-anchor","right")
+          .attr("dx", "5")
+          .attr("dy", DEFAULT_NODE_HEIGHT / 2 + TYPE_HEIGHT + 5)
+          .append('tspan')
+          .text(function(l) {
+            return d.getDisplayType();
+          })
+          .append("title")
+          .text(function(l) {
+            return d.getType();
+          })
+      }
+    });
+
+    graph.visibleNodes.select('.type').each(function (d) {
+      this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
+    });
+
+    // remove old nodes
+    graph.visibleNodes.exit().remove();
+
+    graph.visibleBubbles = d3.selectAll('circle');
+
+    graph.updateInputAndOutputBubbles();
+
+    graph.layoutEdges();
+
+    graph.svg.style.height = '100%';
+  }
+
+  getVisibleTranslation(translate, scale) {
+    var graph = this;
+    var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale;
+    var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale;
+
+    var dimensions = this.getSvgViewDimensions();
+
+    var baseY = translate[1];
+    var minY = (graph.minGraphY - GRAPH_MARGIN) * scale;
+    var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale;
+
+    var adjustY = 0;
+    var adjustYCandidate = 0;
+    if ((maxY + baseY) < dimensions[1]) {
+      adjustYCandidate = dimensions[1] - (maxY + baseY);
+      if ((minY + baseY + adjustYCandidate) > 0) {
+        adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
+      } else {
+        adjustY = adjustYCandidate;
+      }
+    } else if (-baseY < minY) {
+      adjustYCandidate = -(baseY + minY);
+      if ((maxY + baseY + adjustYCandidate) < dimensions[1]) {
+        adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
+      } else {
+        adjustY = adjustYCandidate;
+      }
+    }
+    translate[1] += adjustY;
+
+    var baseX = translate[0];
+    var minX = (graph.minGraphX - GRAPH_MARGIN) * scale;
+    var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale;
+
+    var adjustX = 0;
+    var adjustXCandidate = 0;
+    if ((maxX + baseX) < dimensions[0]) {
+      adjustXCandidate = dimensions[0] - (maxX + baseX);
+      if ((minX + baseX + adjustXCandidate) > 0) {
+        adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
+      } else {
+        adjustX = adjustXCandidate;
+      }
+    } else if (-baseX < minX) {
+      adjustXCandidate = -(baseX + minX);
+      if ((maxX + baseX + adjustXCandidate) < dimensions[0]) {
+        adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
+      } else {
+        adjustX = adjustXCandidate;
+      }
+    }
+    translate[0] += adjustX;
+    return translate;
+  }
+
+  translateClipped(translate, scale, transition) {
+    var graph = this;
+    var graphNode = this.graphElement[0][0];
+    var translate = this.getVisibleTranslation(translate, scale);
+    if (transition) {
+      graphNode.classList.add('visible-transition');
+      clearTimeout(graph.transitionTimout);
+      graph.transitionTimout = setTimeout(function(){
+        graphNode.classList.remove('visible-transition');
+      }, 1000);
+    }
+    var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")";
+    graphNode.style.transform = translateString;
+    graph.dragSvg.translate(translate);
+    graph.dragSvg.scale(scale);
+  }
+
+  zoomed(){
+    this.state.justScaleTransGraph = true;
+    var scale =  this.dragSvg.scale();
+    this.translateClipped(d3.event.translate, scale);
+  }
+
+
+  getSvgViewDimensions() {
+    var canvasWidth = this.parentNode.clientWidth;
+    var documentElement = document.documentElement;
+    var canvasHeight = documentElement.clientHeight;
+    return [canvasWidth, canvasHeight];
+  }
+
+
+  minScale() {
+    var graph = this;
+    var dimensions = this.getSvgViewDimensions();
+    var width = graph.maxGraphX - graph.minGraphX;
+    var height = graph.maxGraphY - graph.minGraphY;
+    var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2);
+    var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2);
+    if (minScaleYCandidate < minScale) {
+      minScale = minScaleYCandidate;
+    }
+    this.dragSvg.scaleExtent([minScale, 1.5]);
+    return minScale;
+  }
+
+  fitGraphViewToWindow() {
+    this.svg.attr("height", document.documentElement.clientHeight + "px");
+    this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale());
+  }
+
+  toggleTypes() {
+    var graph = this;
+    graph.state.showTypes = !graph.state.showTypes;
+    var element = document.getElementById('toggle-types');
+    if (graph.state.showTypes) {
+      element.classList.add('button-input-toggled');
+    } else {
+      element.classList.remove('button-input-toggled');
+    }
+    graph.updateGraphVisibility();
+  }
+
+  viewSelection() {
+    var graph = this;
+    var minX, maxX, minY, maxY;
+    var hasSelection = false;
+    graph.visibleNodes.each(function(n) {
+      if (this.classList.contains("selected")) {
+        hasSelection = true;
+        minX = minX ? Math.min(minX, n.x) : n.x;
+        maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
+          n.x + n.getTotalNodeWidth();
+        minY = minY ? Math.min(minY, n.y) : n.y;
+        maxY = maxY ? Math.max(maxY, n.y + DEFAULT_NODE_HEIGHT) :
+          n.y + DEFAULT_NODE_HEIGHT;
+      }
+    });
+    if (hasSelection) {
+      graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
+                            maxX + NODE_INPUT_WIDTH, maxY + 60,
+                            true);
+    }
+  }
+
+  viewGraphRegion(minX, minY, maxX, maxY, transition) {
+    var graph = this;
+    var dimensions = this.getSvgViewDimensions();
+    var width = maxX - minX;
+    var height = maxY - minY;
+    var scale = Math.min(dimensions[0] / width, dimensions[1] / height);
+    scale = Math.min(1.5, scale);
+    scale = Math.max(graph.minScale(), scale);
+    var translation = [-minX*scale, -minY*scale];
+    translation = graph.getVisibleTranslation(translation, scale);
+    graph.translateClipped(translation, scale, transition);
+  }
+
+  viewWholeGraph() {
+    var graph = this;
+    var minScale = graph.minScale();
+    var translation = [0, 0];
+    translation = graph.getVisibleTranslation(translation, minScale);
+    graph.translateClipped(translation, minScale);
+  }
+}