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