| 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);
|
| + }
|
| +}
|
|
|