blob: 41d35d84ba63bcdacabb5ac18b8000659d8d1aa6 [file] [log] [blame]
Ben Murdoch61f157c2016-09-16 13:49:30 +01001// 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}