Index: tools/turbolizer/graph-view.js |
diff --git a/tools/turbolizer/graph-view.js b/tools/turbolizer/graph-view.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..aa1b638f57ba248de205754882c872ff48231405 |
--- /dev/null |
+++ b/tools/turbolizer/graph-view.js |
@@ -0,0 +1,860 @@ |
+// Copyright 2015 the V8 project authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+"use strict"; |
+ |
+class GraphView extends View { |
+ constructor (d3, id, nodes, edges, broker) { |
+ super(id, broker); |
+ var graph = this; |
+ |
+ var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%"); |
+ graph.svg = svg; |
+ |
+ graph.nodes = nodes || []; |
+ graph.edges = edges || []; |
+ |
+ graph.minGraphX = 0; |
+ graph.maxGraphX = 1; |
+ graph.minGraphY = 0; |
+ graph.maxGraphY = 1; |
+ |
+ graph.state = { |
+ selection: null, |
+ mouseDownNode: null, |
+ justDragged: false, |
+ justScaleTransGraph: false, |
+ lastKeyDown: -1, |
+ showTypes: false |
+ }; |
+ |
+ var selectionHandler = { |
+ clear: function() { |
+ broker.clear(selectionHandler); |
+ }, |
+ select: function(items, selected) { |
+ var ranges = []; |
+ for (var d of items) { |
+ if (selected) { |
+ d.classList.add("selected"); |
+ } else { |
+ d.classList.remove("selected"); |
+ } |
+ var data = d.__data__; |
+ ranges.push([data.pos, data.pos + 1, data.id]); |
+ } |
+ broker.select(selectionHandler, ranges, selected); |
+ }, |
+ selectionDifference: function(span1, inclusive1, span2, inclusive2) { |
+ // Should not be called |
+ }, |
+ brokeredSelect: function(ranges, selected) { |
+ var test = [].entries().next(); |
+ var selection = graph.nodes |
+ .filter(function(n) { |
+ var pos = n.pos; |
+ for (var range of ranges) { |
+ var start = range[0]; |
+ var end = range[1]; |
+ var id = range[2]; |
+ if (end != undefined) { |
+ if (pos >= start && pos < end) { |
+ return true; |
+ } |
+ } else if (start != undefined) { |
+ if (pos === start) { |
+ return true; |
+ } |
+ } else { |
+ if (n.id === id) { |
+ return true; |
+ } |
+ } |
+ } |
+ return false; |
+ }); |
+ var newlySelected = new Set(); |
+ selection.forEach(function(n) { |
+ newlySelected.add(n); |
+ if (!n.visible) { |
+ n.visible = true; |
+ } |
+ }); |
+ graph.updateGraphVisibility(); |
+ graph.visibleNodes.each(function(n) { |
+ if (newlySelected.has(n)) { |
+ graph.state.selection.select(this, selected); |
+ } |
+ }); |
+ graph.updateGraphVisibility(); |
+ graph.viewSelection(); |
+ }, |
+ brokeredClear: function() { |
+ graph.state.selection.clear(); |
+ } |
+ }; |
+ broker.addSelectionHandler(selectionHandler); |
+ |
+ graph.state.selection = new Selection(selectionHandler); |
+ |
+ var defs = svg.append('svg:defs'); |
+ defs.append('svg:marker') |
+ .attr('id', 'end-arrow') |
+ .attr('viewBox', '0 -4 8 8') |
+ .attr('refX', 2) |
+ .attr('markerWidth', 2.5) |
+ .attr('markerHeight', 2.5) |
+ .attr('orient', 'auto') |
+ .append('svg:path') |
+ .attr('d', 'M0,-4L8,0L0,4'); |
+ |
+ this.graphElement = svg.append("g"); |
+ graph.visibleEdges = this.graphElement.append("g").selectAll("g"); |
+ graph.visibleNodes = this.graphElement.append("g").selectAll("g"); |
+ |
+ graph.drag = d3.behavior.drag() |
+ .origin(function(d){ |
+ return {x: d.x, y: d.y}; |
+ }) |
+ .on("drag", function(args){ |
+ graph.state.justDragged = true; |
+ graph.dragmove.call(graph, args); |
+ }) |
+ |
+ d3.select("#upload").on("click", function(){ |
+ document.getElementById("hidden-file-upload").click(); |
+ }); |
+ |
+ d3.select("#layout").on("click", function(){ |
+ graph.updateGraphVisibility(); |
+ graph.layoutGraph(); |
+ graph.updateGraphVisibility(); |
+ graph.viewWholeGraph(); |
+ }); |
+ |
+ d3.select("#show-all").on("click", function(){ |
+ graph.nodes.filter(function(n) { n.visible = true; }) |
+ graph.edges.filter(function(e) { e.visible = true; }) |
+ graph.updateGraphVisibility(); |
+ graph.viewWholeGraph(); |
+ }); |
+ |
+ d3.select("#hide-unselected").on("click", function() { |
+ var unselected = graph.visibleNodes.filter(function(n) { |
+ return !this.classList.contains("selected"); |
+ }); |
+ unselected.each(function(n) { |
+ n.visible = false; |
+ }); |
+ graph.updateGraphVisibility(); |
+ }); |
+ |
+ d3.select("#hide-selected").on("click", function() { |
+ var selected = graph.visibleNodes.filter(function(n) { |
+ return this.classList.contains("selected"); |
+ }); |
+ selected.each(function(n) { |
+ n.visible = false; |
+ }); |
+ graph.state.selection.clear(); |
+ graph.updateGraphVisibility(); |
+ }); |
+ |
+ d3.select("#zoom-selection").on("click", function() { |
+ graph.viewSelection(); |
+ }); |
+ |
+ d3.select("#toggle-types").on("click", function() { |
+ graph.toggleTypes(); |
+ }); |
+ |
+ d3.select("#search-input").on("keydown", function() { |
+ if (d3.event.keyCode == 13) { |
+ graph.state.selection.clear(); |
+ var reg = new RegExp(this.value); |
+ var selected = graph.visibleNodes.each(function(n) { |
+ if (reg.exec(n.getDisplayLabel()) != null || |
+ (graph.state.showTypes && reg.exec(n.getDisplayType())) || |
+ reg.exec(n.opcode) != null) { |
+ graph.state.selection.select(this, true); |
+ } |
+ }); |
+ this.blur(); |
+ graph.viewSelection(); |
+ } |
+ }); |
+ |
+ // listen for key events |
+ d3.select(window).on("keydown", function(e){ |
+ graph.svgKeyDown.call(graph); |
+ }) |
+ .on("keyup", function(){ |
+ graph.svgKeyUp.call(graph); |
+ }); |
+ svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);}); |
+ svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);}); |
+ |
+ graph.dragSvg = d3.behavior.zoom() |
+ .on("zoom", function(){ |
+ if (d3.event.sourceEvent.shiftKey){ |
+ return false; |
+ } else{ |
+ graph.zoomed.call(graph); |
+ } |
+ return true; |
+ }) |
+ .on("zoomstart", function(){ |
+ if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move"); |
+ }) |
+ .on("zoomend", function(){ |
+ d3.select('body').style("cursor", "auto"); |
+ }); |
+ |
+ svg.call(graph.dragSvg).on("dblclick.zoom", null); |
+ } |
+ |
+ static get selectedClass() { |
+ return "selected"; |
+ } |
+ static get rectClass() { |
+ return "nodeStyle"; |
+ } |
+ static get activeEditId() { |
+ return "active-editing"; |
+ } |
+ static get nodeRadius() { |
+ return 50; |
+ } |
+ |
+ getNodeHeight(graph) { |
+ if (this.state.showTypes) { |
+ return DEFAULT_NODE_HEIGHT + TYPE_HEIGHT; |
+ } else { |
+ return DEFAULT_NODE_HEIGHT; |
+ } |
+ } |
+ |
+ dragmove(d) { |
+ var graph = this; |
+ d.x += d3.event.dx; |
+ d.y += d3.event.dy; |
+ graph.updateGraphVisibility(); |
+ } |
+ |
+ initializeContent(data, rememberedSelection) { |
+ this.createGraph(data); |
+ if (rememberedSelection != null) { |
+ this.attachSelection(rememberedSelection); |
+ } |
+ this.updateGraphVisibility(); |
+ } |
+ |
+ deleteContent() { |
+ if (this.visibleNodes) { |
+ this.nodes = []; |
+ this.edges = []; |
+ this.nodeMap = []; |
+ this.updateGraphVisibility(); |
+ } |
+ }; |
+ |
+ createGraph(data) { |
+ var g = this; |
+ g.nodes = data.nodes; |
+ g.nodeMap = []; |
+ var textMeasure = document.getElementById('text-measure'); |
+ g.nodes.forEach(function(n, i){ |
+ n.__proto__ = Node; |
+ n.visible = false; |
+ n.x = 0; |
+ n.y = 0; |
+ n.rank = MAX_RANK_SENTINEL; |
+ n.inputs = []; |
+ n.outputs = []; |
+ n.rpo = -1; |
+ n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH; |
+ n.cfg = n.control; |
+ g.nodeMap[n.id] = n; |
+ n.displayLabel = n.getDisplayLabel(); |
+ textMeasure.textContent = n.getDisplayLabel(); |
+ var width = textMeasure.getComputedTextLength(); |
+ textMeasure.textContent = n.getDisplayType(); |
+ width = Math.max(width, textMeasure.getComputedTextLength()); |
+ n.width = Math.alignUp(width + NODE_INPUT_WIDTH * 2, |
+ NODE_INPUT_WIDTH); |
+ }); |
+ g.edges = []; |
+ data.edges.forEach(function(e, i){ |
+ var t = g.nodeMap[e.target]; |
+ var s = g.nodeMap[e.source]; |
+ var newEdge = new Edge(t, e.index, s, e.type); |
+ t.inputs.push(newEdge); |
+ s.outputs.push(newEdge); |
+ g.edges.push(newEdge); |
+ if (e.type == 'control') { |
+ s.cfg = true; |
+ } |
+ }); |
+ g.nodes.forEach(function(n, i) { |
+ n.visible = isNodeInitiallyVisible(n); |
+ }); |
+ g.fitGraphViewToWindow(); |
+ g.updateGraphVisibility(); |
+ g.layoutGraph(); |
+ g.updateGraphVisibility(); |
+ g.viewWholeGraph(); |
+ } |
+ |
+ updateInputAndOutputBubbles() { |
+ var g = this; |
+ var s = g.visibleBubbles; |
+ s.classed("filledBubbleStyle", function(c) { |
+ var components = this.id.split(','); |
+ if (components[0] == "ib") { |
+ var edge = g.nodeMap[components[3]].inputs[components[2]]; |
+ return edge.isVisible(); |
+ } else { |
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 2; |
+ } |
+ }).classed("halfFilledBubbleStyle", function(c) { |
+ var components = this.id.split(','); |
+ if (components[0] == "ib") { |
+ var edge = g.nodeMap[components[3]].inputs[components[2]]; |
+ return false; |
+ } else { |
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 1; |
+ } |
+ }).classed("bubbleStyle", function(c) { |
+ var components = this.id.split(','); |
+ if (components[0] == "ib") { |
+ var edge = g.nodeMap[components[3]].inputs[components[2]]; |
+ return !edge.isVisible(); |
+ } else { |
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 0; |
+ } |
+ }); |
+ s.each(function(c) { |
+ var components = this.id.split(','); |
+ if (components[0] == "ob") { |
+ var from = g.nodeMap[components[1]]; |
+ var x = from.getOutputX(); |
+ var y = g.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4; |
+ var transform = "translate(" + x + "," + y + ")"; |
+ this.setAttribute('transform', transform); |
+ } |
+ }); |
+ } |
+ |
+ attachSelection(s) { |
+ var graph = this; |
+ if (s.size != 0) { |
+ this.visibleNodes.each(function(n) { |
+ if (s.has(this.__data__.id)) { |
+ graph.state.selection.select(this, true); |
+ } |
+ }); |
+ } |
+ } |
+ |
+ detachSelection() { |
+ var selection = this.state.selection.detachSelection(); |
+ var result = new Set(); |
+ for (var i of selection) { |
+ result.add(i.__data__.id); |
+ }; |
+ return result; |
+ } |
+ |
+ pathMouseDown(path, d) { |
+ d3.event.stopPropagation(); |
+ this.state.selection.clear(); |
+ this.state.selection.add(path); |
+ }; |
+ |
+ nodeMouseDown(node, d) { |
+ d3.event.stopPropagation(); |
+ this.state.mouseDownNode = d; |
+ } |
+ |
+ nodeMouseUp(d3node, d) { |
+ var graph = this, |
+ state = graph.state, |
+ consts = graph.consts; |
+ |
+ var mouseDownNode = state.mouseDownNode; |
+ |
+ if (!mouseDownNode) return; |
+ |
+ if (mouseDownNode !== d){ |
+ // we're in a different node: create new edge for mousedown edge and add to graph |
+ var newEdge = {source: mouseDownNode, target: d}; |
+ var filtRes = graph.visibleEdges.filter(function(d){ |
+ if (d.source === newEdge.target && d.target === newEdge.source){ |
+ graph.edges.splice(graph.edges.indexOf(d), 1); |
+ } |
+ return d.source === newEdge.source && d.target === newEdge.target; |
+ }); |
+ if (!filtRes[0].length){ |
+ graph.edges.push(newEdge); |
+ graph.updateGraphVisibility(); |
+ } |
+ } else{ |
+ // we're in the same node |
+ if (state.justDragged) { |
+ // dragged, not clicked |
+ state.justDragged = false; |
+ } else{ |
+ // clicked, not dragged |
+ var extend = d3.event.shiftKey; |
+ var selection = graph.state.selection; |
+ if (!extend) { |
+ selection.clear(); |
+ } |
+ selection.select(d3node[0][0], true); |
+ } |
+ } |
+ } |
+ |
+ selectSourcePositions(start, end, selected) { |
+ var graph = this; |
+ var map = []; |
+ var sel = graph.nodes.filter(function(n) { |
+ var pos = (n.pos === undefined) |
+ ? -1 |
+ : n.getFunctionRelativeSourcePosition(graph); |
+ if (pos >= start && pos < end) { |
+ map[n.id] = true; |
+ n.visible = true; |
+ } |
+ }); |
+ graph.updateGraphVisibility(); |
+ graph.visibleNodes.filter(function(n) { return map[n.id]; }) |
+ .each(function(n) { |
+ var selection = graph.state.selection; |
+ selection.select(d3.select(this), selected); |
+ }); |
+ } |
+ |
+ svgMouseDown() { |
+ this.state.graphMouseDown = true; |
+ } |
+ |
+ svgMouseUp() { |
+ var graph = this, |
+ state = graph.state; |
+ if (state.justScaleTransGraph) { |
+ // Dragged |
+ state.justScaleTransGraph = false; |
+ } else { |
+ // Clicked |
+ if (state.mouseDownNode == null) { |
+ graph.state.selection.clear(); |
+ } |
+ } |
+ state.mouseDownNode = null; |
+ state.graphMouseDown = false; |
+ } |
+ |
+ svgKeyDown() { |
+ var state = this.state; |
+ var graph = this; |
+ |
+ // Don't handle key press repetition |
+ if(state.lastKeyDown !== -1) return; |
+ |
+ var getEdgeFrontier = function(inEdges) { |
+ var frontierSet = new Set(); |
+ state.selection.selection.forEach(function(element) { |
+ var nodes = inEdges ? element.__data__.inputs : element.__data__.outputs; |
+ nodes.forEach(function(i) { |
+ i.visible = true; |
+ var candidate = inEdges ? i.source : i.target; |
+ candidate.visible = true; |
+ frontierSet.add(candidate); |
+ }); |
+ }); |
+ graph.updateGraphVisibility(); |
+ return graph.visibleNodes.filter(function(n) { |
+ return frontierSet.has(n); |
+ }); |
+ } |
+ |
+ var allowRepetition = true; |
+ switch(d3.event.keyCode) { |
+ case 38: |
+ case 40: { |
+ var frontier = getEdgeFrontier(d3.event.keyCode == 38); |
+ if (!d3.event.shiftKey) { |
+ state.selection.clear(); |
+ } |
+ frontier.each(function(n) { |
+ state.selection.select(this, true); |
+ }); |
+ graph.updateGraphVisibility(); |
+ allowRepetition = false; |
+ break; |
+ } |
+ } |
+ if (!allowRepetition) { |
+ state.lastKeyDown = d3.event.keyCode; |
+ } |
+ } |
+ |
+ svgKeyUp() { |
+ this.state.lastKeyDown = -1 |
+ }; |
+ |
+ layoutEdges() { |
+ var graph = this; |
+ graph.maxGraphX = graph.maxGraphNodeX; |
+ this.visibleEdges.attr("d", function(edge){ |
+ return edge.generatePath(graph); |
+ }); |
+ } |
+ |
+ layoutGraph() { |
+ layoutNodeGraph(this); |
+ } |
+ |
+ // call to propagate changes to graph |
+ updateGraphVisibility() { |
+ |
+ var graph = this, |
+ state = graph.state; |
+ |
+ var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); }); |
+ var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) { |
+ return edge.stringID(); |
+ }); |
+ |
+ // add new paths |
+ visibleEdges.enter() |
+ .append('path') |
+ .style('marker-end','url(#end-arrow)') |
+ .classed('hidden', function(e) { |
+ return !e.isVisible(); |
+ }) |
+ .attr("id", function(edge){ return "e," + edge.stringID(); }) |
+ .on("mousedown", function(d){ |
+ graph.pathMouseDown.call(graph, d3.select(this), d); |
+ }) |
+ |
+ // Set the correct styles on all of the paths |
+ visibleEdges.classed('value', function(e) { |
+ return e.type == 'value' || e.type == 'context'; |
+ }).classed('control', function(e) { |
+ return e.type == 'control'; |
+ }).classed('effect', function(e) { |
+ return e.type == 'effect'; |
+ }).classed('frame-state', function(e) { |
+ return e.type == 'frame-state'; |
+ }).attr('stroke-dasharray', function(e) { |
+ if (e.type == 'frame-state') return "10,10"; |
+ return (e.type == 'effect') ? "5,5" : ""; |
+ }); |
+ |
+ // remove old links |
+ visibleEdges.exit().remove(); |
+ |
+ graph.visibleEdges = visibleEdges; |
+ |
+ // update existing nodes |
+ var filteredNodes = graph.nodes.filter(function(n) { return n.visible; }); |
+ graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) { |
+ return d.id; |
+ }); |
+ graph.visibleNodes.attr("transform", function(n){ |
+ return "translate(" + n.x + "," + n.y + ")"; |
+ }).select('rect'). |
+ attr(HEIGHT, function(d) { return graph.getNodeHeight(); }); |
+ |
+ // add new nodes |
+ var newGs = graph.visibleNodes.enter() |
+ .append("g"); |
+ |
+ newGs.classed("control", function(n) { return n.isControl(); }) |
+ .classed("javascript", function(n) { return n.isJavaScript(); }) |
+ .classed("input", function(n) { return n.isInput(); }) |
+ .classed("simplified", function(n) { return n.isSimplified(); }) |
+ .classed("machine", function(n) { return n.isMachine(); }) |
+ .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";}) |
+ .on("mousedown", function(d){ |
+ graph.nodeMouseDown.call(graph, d3.select(this), d); |
+ }) |
+ .on("mouseup", function(d){ |
+ graph.nodeMouseUp.call(graph, d3.select(this), d); |
+ }) |
+ .call(graph.drag); |
+ |
+ newGs.append("rect") |
+ .attr("rx", 10) |
+ .attr("ry", 10) |
+ .attr(WIDTH, function(d) { return d.getTotalNodeWidth(); }) |
+ .attr(HEIGHT, function(d) { return graph.getNodeHeight(); }) |
+ |
+ function appendInputAndOutputBubbles(g, d) { |
+ for (var i = 0; i < d.inputs.length; ++i) { |
+ var x = d.getInputX(i); |
+ var y = -DEFAULT_NODE_BUBBLE_RADIUS / 2 - 4; |
+ var s = g.append('circle') |
+ .classed("filledBubbleStyle", function(c) { |
+ return d.inputs[i].isVisible(); |
+ } ) |
+ .classed("bubbleStyle", function(c) { |
+ return !d.inputs[i].isVisible(); |
+ } ) |
+ .attr("id", "ib," + d.inputs[i].stringID()) |
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS) |
+ .attr("transform", function(d) { |
+ return "translate(" + x + "," + y + ")"; |
+ }) |
+ .on("mousedown", function(d){ |
+ var components = this.id.split(','); |
+ var node = graph.nodeMap[components[3]]; |
+ var edge = node.inputs[components[2]]; |
+ var visible = !edge.isVisible(); |
+ node.setInputVisibility(components[2], visible); |
+ d3.event.stopPropagation(); |
+ graph.updateGraphVisibility(); |
+ }); |
+ } |
+ if (d.outputs.length != 0) { |
+ var x = d.getOutputX(); |
+ var y = graph.getNodeHeight() + DEFAULT_NODE_BUBBLE_RADIUS / 2 + 4; |
+ var s = g.append('circle') |
+ .classed("filledBubbleStyle", function(c) { |
+ return d.areAnyOutputsVisible() == 2; |
+ } ) |
+ .classed("halFilledBubbleStyle", function(c) { |
+ return d.areAnyOutputsVisible() == 1; |
+ } ) |
+ .classed("bubbleStyle", function(c) { |
+ return d.areAnyOutputsVisible() == 0; |
+ } ) |
+ .attr("id", "ob," + d.id) |
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS) |
+ .attr("transform", function(d) { |
+ return "translate(" + x + "," + y + ")"; |
+ }) |
+ .on("mousedown", function(d) { |
+ d.setOutputVisibility(d.areAnyOutputsVisible() == 0); |
+ d3.event.stopPropagation(); |
+ graph.updateGraphVisibility(); |
+ }); |
+ } |
+ } |
+ |
+ newGs.each(function(d){ |
+ appendInputAndOutputBubbles(d3.select(this), d); |
+ }); |
+ |
+ newGs.each(function(d){ |
+ d3.select(this).append("text") |
+ .classed("label", true) |
+ .attr("text-anchor","right") |
+ .attr("dx", "5") |
+ .attr("dy", DEFAULT_NODE_HEIGHT / 2 + 5) |
+ .append('tspan') |
+ .text(function(l) { |
+ return d.getDisplayLabel(); |
+ }) |
+ .append("title") |
+ .text(function(l) { |
+ return d.getLabel(); |
+ }) |
+ if (d.type != undefined) { |
+ d3.select(this).append("text") |
+ .classed("label", true) |
+ .classed("type", true) |
+ .attr("text-anchor","right") |
+ .attr("dx", "5") |
+ .attr("dy", DEFAULT_NODE_HEIGHT / 2 + TYPE_HEIGHT + 5) |
+ .append('tspan') |
+ .text(function(l) { |
+ return d.getDisplayType(); |
+ }) |
+ .append("title") |
+ .text(function(l) { |
+ return d.getType(); |
+ }) |
+ } |
+ }); |
+ |
+ graph.visibleNodes.select('.type').each(function (d) { |
+ this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden'); |
+ }); |
+ |
+ // remove old nodes |
+ graph.visibleNodes.exit().remove(); |
+ |
+ graph.visibleBubbles = d3.selectAll('circle'); |
+ |
+ graph.updateInputAndOutputBubbles(); |
+ |
+ graph.layoutEdges(); |
+ |
+ graph.svg.style.height = '100%'; |
+ } |
+ |
+ getVisibleTranslation(translate, scale) { |
+ var graph = this; |
+ var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale; |
+ var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale; |
+ |
+ var dimensions = this.getSvgViewDimensions(); |
+ |
+ var baseY = translate[1]; |
+ var minY = (graph.minGraphY - GRAPH_MARGIN) * scale; |
+ var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale; |
+ |
+ var adjustY = 0; |
+ var adjustYCandidate = 0; |
+ if ((maxY + baseY) < dimensions[1]) { |
+ adjustYCandidate = dimensions[1] - (maxY + baseY); |
+ if ((minY + baseY + adjustYCandidate) > 0) { |
+ adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY; |
+ } else { |
+ adjustY = adjustYCandidate; |
+ } |
+ } else if (-baseY < minY) { |
+ adjustYCandidate = -(baseY + minY); |
+ if ((maxY + baseY + adjustYCandidate) < dimensions[1]) { |
+ adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY; |
+ } else { |
+ adjustY = adjustYCandidate; |
+ } |
+ } |
+ translate[1] += adjustY; |
+ |
+ var baseX = translate[0]; |
+ var minX = (graph.minGraphX - GRAPH_MARGIN) * scale; |
+ var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale; |
+ |
+ var adjustX = 0; |
+ var adjustXCandidate = 0; |
+ if ((maxX + baseX) < dimensions[0]) { |
+ adjustXCandidate = dimensions[0] - (maxX + baseX); |
+ if ((minX + baseX + adjustXCandidate) > 0) { |
+ adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX; |
+ } else { |
+ adjustX = adjustXCandidate; |
+ } |
+ } else if (-baseX < minX) { |
+ adjustXCandidate = -(baseX + minX); |
+ if ((maxX + baseX + adjustXCandidate) < dimensions[0]) { |
+ adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX; |
+ } else { |
+ adjustX = adjustXCandidate; |
+ } |
+ } |
+ translate[0] += adjustX; |
+ return translate; |
+ } |
+ |
+ translateClipped(translate, scale, transition) { |
+ var graph = this; |
+ var graphNode = this.graphElement[0][0]; |
+ var translate = this.getVisibleTranslation(translate, scale); |
+ if (transition) { |
+ graphNode.classList.add('visible-transition'); |
+ clearTimeout(graph.transitionTimout); |
+ graph.transitionTimout = setTimeout(function(){ |
+ graphNode.classList.remove('visible-transition'); |
+ }, 1000); |
+ } |
+ var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")"; |
+ graphNode.style.transform = translateString; |
+ graph.dragSvg.translate(translate); |
+ graph.dragSvg.scale(scale); |
+ } |
+ |
+ zoomed(){ |
+ this.state.justScaleTransGraph = true; |
+ var scale = this.dragSvg.scale(); |
+ this.translateClipped(d3.event.translate, scale); |
+ } |
+ |
+ |
+ getSvgViewDimensions() { |
+ var canvasWidth = this.parentNode.clientWidth; |
+ var documentElement = document.documentElement; |
+ var canvasHeight = documentElement.clientHeight; |
+ return [canvasWidth, canvasHeight]; |
+ } |
+ |
+ |
+ minScale() { |
+ var graph = this; |
+ var dimensions = this.getSvgViewDimensions(); |
+ var width = graph.maxGraphX - graph.minGraphX; |
+ var height = graph.maxGraphY - graph.minGraphY; |
+ var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2); |
+ var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2); |
+ if (minScaleYCandidate < minScale) { |
+ minScale = minScaleYCandidate; |
+ } |
+ this.dragSvg.scaleExtent([minScale, 1.5]); |
+ return minScale; |
+ } |
+ |
+ fitGraphViewToWindow() { |
+ this.svg.attr("height", document.documentElement.clientHeight + "px"); |
+ this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale()); |
+ } |
+ |
+ toggleTypes() { |
+ var graph = this; |
+ graph.state.showTypes = !graph.state.showTypes; |
+ var element = document.getElementById('toggle-types'); |
+ if (graph.state.showTypes) { |
+ element.classList.add('button-input-toggled'); |
+ } else { |
+ element.classList.remove('button-input-toggled'); |
+ } |
+ graph.updateGraphVisibility(); |
+ } |
+ |
+ viewSelection() { |
+ var graph = this; |
+ var minX, maxX, minY, maxY; |
+ var hasSelection = false; |
+ graph.visibleNodes.each(function(n) { |
+ if (this.classList.contains("selected")) { |
+ hasSelection = true; |
+ minX = minX ? Math.min(minX, n.x) : n.x; |
+ maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) : |
+ n.x + n.getTotalNodeWidth(); |
+ minY = minY ? Math.min(minY, n.y) : n.y; |
+ maxY = maxY ? Math.max(maxY, n.y + DEFAULT_NODE_HEIGHT) : |
+ n.y + DEFAULT_NODE_HEIGHT; |
+ } |
+ }); |
+ if (hasSelection) { |
+ graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60, |
+ maxX + NODE_INPUT_WIDTH, maxY + 60, |
+ true); |
+ } |
+ } |
+ |
+ viewGraphRegion(minX, minY, maxX, maxY, transition) { |
+ var graph = this; |
+ var dimensions = this.getSvgViewDimensions(); |
+ var width = maxX - minX; |
+ var height = maxY - minY; |
+ var scale = Math.min(dimensions[0] / width, dimensions[1] / height); |
+ scale = Math.min(1.5, scale); |
+ scale = Math.max(graph.minScale(), scale); |
+ var translation = [-minX*scale, -minY*scale]; |
+ translation = graph.getVisibleTranslation(translation, scale); |
+ graph.translateClipped(translation, scale, transition); |
+ } |
+ |
+ viewWholeGraph() { |
+ var graph = this; |
+ var minScale = graph.minScale(); |
+ var translation = [0, 0]; |
+ translation = graph.getVisibleTranslation(translation, minScale); |
+ graph.translateClipped(translation, minScale); |
+ } |
+} |