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