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