• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7class 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