• 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 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