[turbolizer] Split Graph class from GraphView
This CL splits out a Graph class from the GraphView, which improves maintainability and is a first step towards preserving node positions during phase view changes. This CL also removes duplication of node storage on the graph and provides a generator function instead. The only storage for nodes in the graph is now the {nodeMap}. Bug: v8:7327 Notry: true Change-Id: I1659ecfe46f62a12d2fb3c40ccd6f4936f081b53 Reviewed-on: https://chromium-review.googlesource.com/c/1396087 Commit-Queue: Sigurd Schneider <sigurds@chromium.org> Reviewed-by: Georg Neis <neis@chromium.org> Cr-Commit-Position: refs/heads/master@{#58549}
This commit is contained in:
parent
a53332fe35
commit
b53dcfd5a6
@ -2,7 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import {GNode, DEFAULT_NODE_BUBBLE_RADIUS} from "../src/node"
|
import { GNode, DEFAULT_NODE_BUBBLE_RADIUS } from "../src/node"
|
||||||
|
import { Graph } from "./graph";
|
||||||
|
|
||||||
export const MINIMUM_EDGE_SEPARATION = 20;
|
export const MINIMUM_EDGE_SEPARATION = 20;
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export class Edge {
|
|||||||
return this.visible && this.source.visible && this.target.visible;
|
return this.visible && this.source.visible && this.target.visible;
|
||||||
};
|
};
|
||||||
|
|
||||||
getInputHorizontalPosition(graph) {
|
getInputHorizontalPosition(graph: Graph, showTypes: boolean) {
|
||||||
if (this.backEdgeNumber > 0) {
|
if (this.backEdgeNumber > 0) {
|
||||||
return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
||||||
}
|
}
|
||||||
@ -41,7 +42,7 @@ export class Edge {
|
|||||||
var index = this.index;
|
var index = this.index;
|
||||||
var input_x = target.x + target.getInputX(index);
|
var input_x = target.x + target.getInputX(index);
|
||||||
var inputApproach = target.getInputApproach(this.index);
|
var inputApproach = target.getInputApproach(this.index);
|
||||||
var outputApproach = source.getOutputApproach(graph);
|
var outputApproach = source.getOutputApproach(showTypes);
|
||||||
if (inputApproach > outputApproach) {
|
if (inputApproach > outputApproach) {
|
||||||
return input_x;
|
return input_x;
|
||||||
} else {
|
} else {
|
||||||
@ -52,17 +53,17 @@ export class Edge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePath(graph) {
|
generatePath(graph: Graph, showTypes: boolean) {
|
||||||
var target = this.target;
|
var target = this.target;
|
||||||
var source = this.source;
|
var source = this.source;
|
||||||
var input_x = target.x + target.getInputX(this.index);
|
var input_x = target.x + target.getInputX(this.index);
|
||||||
var arrowheadHeight = 7;
|
var arrowheadHeight = 7;
|
||||||
var input_y = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
|
var input_y = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
|
||||||
var output_x = source.x + source.getOutputX();
|
var output_x = source.x + source.getOutputX();
|
||||||
var output_y = source.y + graph.getNodeHeight(source) + DEFAULT_NODE_BUBBLE_RADIUS;
|
var output_y = source.y + source.getNodeHeight(showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
var inputApproach = target.getInputApproach(this.index);
|
var inputApproach = target.getInputApproach(this.index);
|
||||||
var outputApproach = source.getOutputApproach(graph);
|
var outputApproach = source.getOutputApproach(showTypes);
|
||||||
var horizontalPos = this.getInputHorizontalPosition(graph);
|
var horizontalPos = this.getInputHorizontalPosition(graph, showTypes);
|
||||||
|
|
||||||
var result = "M" + output_x + "," + output_y +
|
var result = "M" + output_x + "," + output_y +
|
||||||
"L" + output_x + "," + outputApproach +
|
"L" + output_x + "," + outputApproach +
|
||||||
|
@ -6,13 +6,14 @@
|
|||||||
import { MAX_RANK_SENTINEL } from "../src/constants"
|
import { MAX_RANK_SENTINEL } from "../src/constants"
|
||||||
import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"
|
import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"
|
||||||
import { NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS, GNode } from "../src/node"
|
import { NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH, DEFAULT_NODE_BUBBLE_RADIUS, GNode } from "../src/node"
|
||||||
|
import { Graph } from "./graph";
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_NODE_ROW_SEPARATION = 130
|
const DEFAULT_NODE_ROW_SEPARATION = 130
|
||||||
|
|
||||||
var traceLayout = false;
|
var traceLayout = false;
|
||||||
|
|
||||||
function newGraphOccupation(graph) {
|
function newGraphOccupation(graph:Graph) {
|
||||||
var isSlotFilled = [];
|
var isSlotFilled = [];
|
||||||
var maxSlot = 0;
|
var maxSlot = 0;
|
||||||
var minSlot = 0;
|
var minSlot = 0;
|
||||||
@ -138,7 +139,6 @@ function newGraphOccupation(graph) {
|
|||||||
if (node.inputs[i].isVisible()) {
|
if (node.inputs[i].isVisible()) {
|
||||||
var edge = node.inputs[i];
|
var edge = node.inputs[i];
|
||||||
if (!edge.isBackEdge()) {
|
if (!edge.isBackEdge()) {
|
||||||
var source = edge.source;
|
|
||||||
var horizontalPos = edge.getInputHorizontalPosition(graph);
|
var horizontalPos = edge.getInputHorizontalPosition(graph);
|
||||||
if (traceLayout) {
|
if (traceLayout) {
|
||||||
console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
|
console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
|
||||||
@ -252,7 +252,7 @@ function newGraphOccupation(graph) {
|
|||||||
return occupation;
|
return occupation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function layoutNodeGraph(graph) {
|
export function layoutNodeGraph(graph: Graph, showTypes: boolean): void {
|
||||||
// First determine the set of nodes that have no outputs. Those are the
|
// First determine the set of nodes that have no outputs. Those are the
|
||||||
// basis for bottom-up DFS to determine rank and node placement.
|
// basis for bottom-up DFS to determine rank and node placement.
|
||||||
|
|
||||||
@ -260,10 +260,10 @@ export function layoutNodeGraph(graph) {
|
|||||||
|
|
||||||
const endNodesHasNoOutputs = [];
|
const endNodesHasNoOutputs = [];
|
||||||
const startNodesHasNoInputs = [];
|
const startNodesHasNoInputs = [];
|
||||||
graph.nodes.forEach(function (n: GNode) {
|
for (const n of graph.nodes()) {
|
||||||
endNodesHasNoOutputs[n.id] = true;
|
endNodesHasNoOutputs[n.id] = true;
|
||||||
startNodesHasNoInputs[n.id] = true;
|
startNodesHasNoInputs[n.id] = true;
|
||||||
});
|
};
|
||||||
graph.forEachEdge((e: Edge) => {
|
graph.forEachEdge((e: Edge) => {
|
||||||
endNodesHasNoOutputs[e.source.id] = false;
|
endNodesHasNoOutputs[e.source.id] = false;
|
||||||
startNodesHasNoInputs[e.target.id] = false;
|
startNodesHasNoInputs[e.target.id] = false;
|
||||||
@ -274,7 +274,7 @@ export function layoutNodeGraph(graph) {
|
|||||||
var startNodes = [];
|
var startNodes = [];
|
||||||
var visited = [];
|
var visited = [];
|
||||||
var rank = [];
|
var rank = [];
|
||||||
graph.nodes.forEach(function (n, i) {
|
for (const n of graph.nodes()) {
|
||||||
if (endNodesHasNoOutputs[n.id]) {
|
if (endNodesHasNoOutputs[n.id]) {
|
||||||
endNodes.push(n);
|
endNodes.push(n);
|
||||||
}
|
}
|
||||||
@ -286,7 +286,7 @@ export function layoutNodeGraph(graph) {
|
|||||||
n.rank = 0;
|
n.rank = 0;
|
||||||
n.visitOrderWithinRank = 0;
|
n.visitOrderWithinRank = 0;
|
||||||
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
|
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
|
||||||
});
|
};
|
||||||
|
|
||||||
if (traceLayout) {
|
if (traceLayout) {
|
||||||
console.log(`layoutGraph init ${performance.now() - start}`);
|
console.log(`layoutGraph init ${performance.now() - start}`);
|
||||||
@ -385,8 +385,8 @@ export function layoutNodeGraph(graph) {
|
|||||||
|
|
||||||
var rankSets = [];
|
var rankSets = [];
|
||||||
// Collect sets for each rank.
|
// Collect sets for each rank.
|
||||||
graph.nodes.forEach(function (n, i) {
|
for (const n of graph.nodes()) {
|
||||||
n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight(n) +
|
n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + n.getNodeHeight(showTypes) +
|
||||||
2 * DEFAULT_NODE_BUBBLE_RADIUS);
|
2 * DEFAULT_NODE_BUBBLE_RADIUS);
|
||||||
if (n.visible) {
|
if (n.visible) {
|
||||||
if (rankSets[n.rank] === undefined) {
|
if (rankSets[n.rank] === undefined) {
|
||||||
@ -395,13 +395,12 @@ export function layoutNodeGraph(graph) {
|
|||||||
rankSets[n.rank].push(n);
|
rankSets[n.rank].push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// Iterate backwards from highest to lowest rank, placing nodes so that they
|
// Iterate backwards from highest to lowest rank, placing nodes so that they
|
||||||
// spread out from the "center" as much as possible while still being
|
// spread out from the "center" as much as possible while still being
|
||||||
// compact and not overlapping live input lines.
|
// compact and not overlapping live input lines.
|
||||||
var occupation = newGraphOccupation(graph);
|
var occupation = newGraphOccupation(graph);
|
||||||
var rankCount = 0;
|
|
||||||
|
|
||||||
rankSets.reverse().forEach(function (rankSet) {
|
rankSets.reverse().forEach(function (rankSet) {
|
||||||
|
|
||||||
@ -462,57 +461,11 @@ export function layoutNodeGraph(graph) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
graph.maxBackEdgeNumber = 0;
|
graph.maxBackEdgeNumber = 0;
|
||||||
graph.visibleEdges.selectAll("path").each(function (e) {
|
graph.forEachEdge((e) => {
|
||||||
if (e.isBackEdge()) {
|
if (e.isBackEdge()) {
|
||||||
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
|
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
|
||||||
} else {
|
} else {
|
||||||
e.backEdgeNumber = 0;
|
e.backEdgeNumber = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
redetermineGraphBoundingBox(graph);
|
|
||||||
}
|
|
||||||
|
|
||||||
function redetermineGraphBoundingBox(graph) {
|
|
||||||
graph.minGraphX = 0;
|
|
||||||
graph.maxGraphNodeX = 1;
|
|
||||||
graph.maxGraphX = undefined; // see below
|
|
||||||
graph.minGraphY = 0;
|
|
||||||
graph.maxGraphY = 1;
|
|
||||||
|
|
||||||
for (var i = 0; i < graph.nodes.length; ++i) {
|
|
||||||
var node = graph.nodes[i];
|
|
||||||
|
|
||||||
if (!node.visible) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.x < graph.minGraphX) {
|
|
||||||
graph.minGraphX = node.x;
|
|
||||||
}
|
|
||||||
if ((node.x + node.getTotalNodeWidth()) > graph.maxGraphNodeX) {
|
|
||||||
graph.maxGraphNodeX = node.x + node.getTotalNodeWidth();
|
|
||||||
}
|
|
||||||
if ((node.y - 50) < graph.minGraphY) {
|
|
||||||
graph.minGraphY = node.y - 50;
|
|
||||||
}
|
|
||||||
if ((node.y + graph.getNodeHeight(node) + 50) > graph.maxGraphY) {
|
|
||||||
graph.maxGraphY = node.y + graph.getNodeHeight(node) + 50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.maxGraphX = graph.maxGraphNodeX +
|
|
||||||
graph.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
|
||||||
|
|
||||||
const width = (graph.maxGraphX - graph.minGraphX);
|
|
||||||
const height = graph.maxGraphY - graph.minGraphY;
|
|
||||||
graph.width = width;
|
|
||||||
graph.height = height;
|
|
||||||
|
|
||||||
const extent = [
|
|
||||||
[graph.minGraphX - width / 2, graph.minGraphY - height / 2],
|
|
||||||
[graph.maxGraphX + width / 2, graph.maxGraphY + height / 2]
|
|
||||||
];
|
|
||||||
graph.panZoom.translateExtent(extent);
|
|
||||||
graph.minScale();
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { View, PhaseView } from "../src/view"
|
|||||||
import { MySelection } from "../src/selection"
|
import { MySelection } from "../src/selection"
|
||||||
import { partial, alignUp } from "../src/util"
|
import { partial, alignUp } from "../src/util"
|
||||||
import { NodeSelectionHandler, ClearableHandler } from "./selection-handler";
|
import { NodeSelectionHandler, ClearableHandler } from "./selection-handler";
|
||||||
|
import { Graph } from "./graph";
|
||||||
|
|
||||||
function nodeToStringKey(n) {
|
function nodeToStringKey(n) {
|
||||||
return "" + n.id;
|
return "" + n.id;
|
||||||
@ -33,23 +34,17 @@ export class GraphView extends View implements PhaseView {
|
|||||||
svg: d3.Selection<any, any, any, any>;
|
svg: d3.Selection<any, any, any, any>;
|
||||||
showPhaseByName: (string) => void;
|
showPhaseByName: (string) => void;
|
||||||
state: GraphState;
|
state: GraphState;
|
||||||
nodes: Array<GNode>;
|
|
||||||
selectionHandler: NodeSelectionHandler & ClearableHandler;
|
selectionHandler: NodeSelectionHandler & ClearableHandler;
|
||||||
graphElement: d3.Selection<any, any, any, any>;
|
graphElement: d3.Selection<any, any, any, any>;
|
||||||
visibleNodes: d3.Selection<any, GNode, any, any>;
|
visibleNodes: d3.Selection<any, GNode, any, any>;
|
||||||
visibleEdges: d3.Selection<any, Edge, any, any>;
|
visibleEdges: d3.Selection<any, Edge, any, any>;
|
||||||
minGraphX: number;
|
|
||||||
maxGraphX: number;
|
|
||||||
minGraphY: number;
|
|
||||||
maxGraphY: number;
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
maxGraphNodeX: number;
|
|
||||||
drag: d3.DragBehavior<any, GNode, GNode>;
|
drag: d3.DragBehavior<any, GNode, GNode>;
|
||||||
panZoom: d3.ZoomBehavior<SVGElement, any>;
|
panZoom: d3.ZoomBehavior<SVGElement, any>;
|
||||||
nodeMap: Array<any>;
|
|
||||||
visibleBubbles: d3.Selection<any, any, any, any>;
|
visibleBubbles: d3.Selection<any, any, any, any>;
|
||||||
transitionTimout: number;
|
transitionTimout: number;
|
||||||
|
graph: Graph;
|
||||||
|
|
||||||
createViewElement() {
|
createViewElement() {
|
||||||
const pane = document.createElement('div');
|
const pane = document.createElement('div');
|
||||||
@ -57,43 +52,20 @@ export class GraphView extends View implements PhaseView {
|
|||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
|
|
||||||
*filteredEdges(p: (e: Edge) => boolean) {
|
|
||||||
for (const node of this.nodes) {
|
|
||||||
for (const edge of node.inputs) {
|
|
||||||
if (p(edge)) yield edge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forEachEdge(p: (e: Edge) => void) {
|
|
||||||
for (const node of this.nodes) {
|
|
||||||
for (const edge of node.inputs) {
|
|
||||||
p(edge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(id, broker, showPhaseByName: (string) => void) {
|
constructor(id, broker, showPhaseByName: (string) => void) {
|
||||||
super(id);
|
super(id);
|
||||||
var graph = this;
|
const view = this;
|
||||||
this.showPhaseByName = showPhaseByName;
|
this.showPhaseByName = showPhaseByName;
|
||||||
this.divElement = d3.select(this.divNode);
|
this.divElement = d3.select(this.divNode);
|
||||||
const svg = this.divElement.append("svg").attr('version', '1.1')
|
const svg = this.divElement.append("svg").attr('version', '1.1')
|
||||||
.attr("width", "100%")
|
.attr("width", "100%")
|
||||||
.attr("height", "100%");
|
.attr("height", "100%");
|
||||||
svg.on("click", function (d) {
|
svg.on("click", function (d) {
|
||||||
graph.selectionHandler.clear();
|
view.selectionHandler.clear();
|
||||||
});
|
});
|
||||||
graph.svg = svg;
|
view.svg = svg;
|
||||||
|
|
||||||
graph.nodes = [];
|
this.state = {
|
||||||
|
|
||||||
graph.minGraphX = 0;
|
|
||||||
graph.maxGraphX = 1;
|
|
||||||
graph.minGraphY = 0;
|
|
||||||
graph.maxGraphY = 1;
|
|
||||||
|
|
||||||
graph.state = {
|
|
||||||
selection: null,
|
selection: null,
|
||||||
mouseDownNode: null,
|
mouseDownNode: null,
|
||||||
justDragged: false,
|
justDragged: false,
|
||||||
@ -105,9 +77,9 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
this.selectionHandler = {
|
this.selectionHandler = {
|
||||||
clear: function () {
|
clear: function () {
|
||||||
graph.state.selection.clear();
|
view.state.selection.clear();
|
||||||
broker.broadcastClear(this);
|
broker.broadcastClear(this);
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
},
|
},
|
||||||
select: function (nodes, selected) {
|
select: function (nodes, selected) {
|
||||||
let locations = [];
|
let locations = [];
|
||||||
@ -119,39 +91,38 @@ export class GraphView extends View implements PhaseView {
|
|||||||
locations.push({ bytecodePosition: node.origin.bytecodePosition });
|
locations.push({ bytecodePosition: node.origin.bytecodePosition });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graph.state.selection.select(nodes, selected);
|
view.state.selection.select(nodes, selected);
|
||||||
broker.broadcastSourcePositionSelect(this, locations, selected);
|
broker.broadcastSourcePositionSelect(this, locations, selected);
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
},
|
},
|
||||||
brokeredNodeSelect: function (locations, selected) {
|
brokeredNodeSelect: function (locations, selected) {
|
||||||
let selection = graph.nodes
|
let selection = view.graph.nodes((n) => {
|
||||||
.filter(function (n) {
|
return locations.has(nodeToStringKey(n))
|
||||||
return locations.has(nodeToStringKey(n))
|
&& (!view.state.hideDead || n.isLive());
|
||||||
&& (!graph.state.hideDead || n.isLive());
|
});
|
||||||
});
|
view.state.selection.select(selection, selected);
|
||||||
graph.state.selection.select(selection, selected);
|
|
||||||
// Update edge visibility based on selection.
|
// Update edge visibility based on selection.
|
||||||
graph.nodes.forEach((n) => {
|
for (const n of view.graph.nodes()) {
|
||||||
if (graph.state.selection.isSelected(n)) {
|
if (view.state.selection.isSelected(n)) {
|
||||||
n.visible = true;
|
n.visible = true;
|
||||||
n.inputs.forEach((e) => {
|
n.inputs.forEach((e) => {
|
||||||
e.visible = e.visible || graph.state.selection.isSelected(e.source);
|
e.visible = e.visible || view.state.selection.isSelected(e.source);
|
||||||
});
|
});
|
||||||
n.outputs.forEach((e) => {
|
n.outputs.forEach((e) => {
|
||||||
e.visible = e.visible || graph.state.selection.isSelected(e.target);
|
e.visible = e.visible || view.state.selection.isSelected(e.target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
},
|
},
|
||||||
brokeredClear: function () {
|
brokeredClear: function () {
|
||||||
graph.state.selection.clear();
|
view.state.selection.clear();
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
broker.addNodeHandler(this.selectionHandler);
|
broker.addNodeHandler(this.selectionHandler);
|
||||||
|
|
||||||
graph.state.selection = new MySelection(nodeToStringKey);
|
view.state.selection = new MySelection(nodeToStringKey);
|
||||||
|
|
||||||
const defs = svg.append('svg:defs');
|
const defs = svg.append('svg:defs');
|
||||||
defs.append('svg:marker')
|
defs.append('svg:marker')
|
||||||
@ -165,27 +136,27 @@ export class GraphView extends View implements PhaseView {
|
|||||||
.attr('d', 'M0,-4L8,0L0,4');
|
.attr('d', 'M0,-4L8,0L0,4');
|
||||||
|
|
||||||
this.graphElement = svg.append("g");
|
this.graphElement = svg.append("g");
|
||||||
graph.visibleEdges = this.graphElement.append("g");
|
view.visibleEdges = this.graphElement.append("g");
|
||||||
graph.visibleNodes = this.graphElement.append("g");
|
view.visibleNodes = this.graphElement.append("g");
|
||||||
|
|
||||||
graph.drag = d3.drag<any, GNode, GNode>()
|
view.drag = d3.drag<any, GNode, GNode>()
|
||||||
.on("drag", function (d) {
|
.on("drag", function (d) {
|
||||||
d.x += d3.event.dx;
|
d.x += d3.event.dx;
|
||||||
d.y += d3.event.dy;
|
d.y += d3.event.dy;
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// listen for key events
|
// listen for key events
|
||||||
d3.select(window).on("keydown", function (e) {
|
d3.select(window).on("keydown", function (e) {
|
||||||
graph.svgKeyDown.call(graph);
|
view.svgKeyDown.call(view);
|
||||||
}).on("keyup", function () {
|
}).on("keyup", function () {
|
||||||
graph.svgKeyUp.call(graph);
|
view.svgKeyUp.call(view);
|
||||||
});
|
});
|
||||||
|
|
||||||
function zoomed() {
|
function zoomed() {
|
||||||
if (d3.event.shiftKey) return false;
|
if (d3.event.shiftKey) return false;
|
||||||
graph.graphElement.attr("transform", d3.event.transform);
|
view.graphElement.attr("transform", d3.event.transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomSvg = d3.zoom<SVGElement, any>()
|
const zoomSvg = d3.zoom<SVGElement, any>()
|
||||||
@ -201,7 +172,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
svg.call(zoomSvg).on("dblclick.zoom", null);
|
svg.call(zoomSvg).on("dblclick.zoom", null);
|
||||||
|
|
||||||
graph.panZoom = zoomSvg;
|
view.panZoom = zoomSvg;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,14 +190,6 @@ export class GraphView extends View implements PhaseView {
|
|||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeHeight(d): number {
|
|
||||||
if (this.state.showTypes) {
|
|
||||||
return d.normalheight + d.labelbbox.height;
|
|
||||||
} else {
|
|
||||||
return d.normalheight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getEdgeFrontier(nodes, inEdges, edgeFilter) {
|
getEdgeFrontier(nodes, inEdges, edgeFilter) {
|
||||||
let frontier = new Set();
|
let frontier = new Set();
|
||||||
for (const n of nodes) {
|
for (const n of nodes) {
|
||||||
@ -243,10 +206,10 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNodeFrontier(nodes, inEdges, edgeFilter) {
|
getNodeFrontier(nodes, inEdges, edgeFilter) {
|
||||||
let graph = this;
|
const view = this;
|
||||||
var frontier = new Set();
|
var frontier = new Set();
|
||||||
var newState = true;
|
var newState = true;
|
||||||
var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
|
var edgeFrontier = view.getEdgeFrontier(nodes, inEdges, edgeFilter);
|
||||||
// Control key toggles edges rather than just turning them on
|
// Control key toggles edges rather than just turning them on
|
||||||
if (d3.event.ctrlKey) {
|
if (d3.event.ctrlKey) {
|
||||||
edgeFrontier.forEach(function (edge) {
|
edgeFrontier.forEach(function (edge) {
|
||||||
@ -263,7 +226,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
frontier.add(node);
|
frontier.add(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
if (newState) {
|
if (newState) {
|
||||||
return frontier;
|
return frontier;
|
||||||
} else {
|
} else {
|
||||||
@ -296,76 +259,23 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
deleteContent() {
|
deleteContent() {
|
||||||
if (this.visibleNodes) {
|
if (this.visibleNodes) {
|
||||||
this.nodes = [];
|
|
||||||
this.nodeMap = [];
|
|
||||||
this.updateGraphVisibility();
|
this.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
measureText(text) {
|
|
||||||
const textMeasure = document.getElementById('text-measure');
|
|
||||||
if (textMeasure instanceof SVGTSpanElement) {
|
|
||||||
textMeasure.textContent = text;
|
|
||||||
return {
|
|
||||||
width: textMeasure.getBBox().width,
|
|
||||||
height: textMeasure.getBBox().height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createGraph(data, rememberedSelection) {
|
createGraph(data, rememberedSelection) {
|
||||||
var g = this;
|
this.graph = new Graph(data);
|
||||||
g.nodes = [];
|
for (const n of this.graph.nodes()) {
|
||||||
g.nodeMap = [];
|
n.visible = n.cfg && (!this.state.hideDead || n.isLive());
|
||||||
data.nodes.forEach(function (n, i) {
|
|
||||||
n.__proto__ = GNode.prototype;
|
|
||||||
n.visible = false;
|
|
||||||
n.x = 0;
|
|
||||||
n.y = 0;
|
|
||||||
if (typeof n.pos === "number") {
|
|
||||||
// Backwards compatibility.
|
|
||||||
n.sourcePosition = { scriptOffset: n.pos, inliningId: -1 };
|
|
||||||
}
|
|
||||||
n.rank = MAX_RANK_SENTINEL;
|
|
||||||
n.inputs = [];
|
|
||||||
n.outputs = [];
|
|
||||||
n.rpo = -1;
|
|
||||||
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
|
|
||||||
// Every control node is a CFG node.
|
|
||||||
n.cfg = n.control;
|
|
||||||
g.nodeMap[n.id] = n;
|
|
||||||
n.displayLabel = n.getDisplayLabel();
|
|
||||||
n.labelbbox = g.measureText(n.displayLabel);
|
|
||||||
n.typebbox = g.measureText(n.getDisplayType());
|
|
||||||
var innerwidth = Math.max(n.labelbbox.width, n.typebbox.width);
|
|
||||||
n.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
|
|
||||||
NODE_INPUT_WIDTH);
|
|
||||||
var innerheight = Math.max(n.labelbbox.height, n.typebbox.height);
|
|
||||||
n.normalheight = innerheight + 20;
|
|
||||||
g.nodes.push(n);
|
|
||||||
});
|
|
||||||
data.edges.forEach((e: any) => {
|
|
||||||
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);
|
|
||||||
if (e.type == 'control') {
|
|
||||||
// Every source of a control edge is a CFG node.
|
|
||||||
s.cfg = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
g.nodes.forEach(function (n, i) {
|
|
||||||
n.visible = n.cfg && (!g.state.hideDead || n.isLive());
|
|
||||||
if (rememberedSelection != undefined && rememberedSelection.has(nodeToStringKey(n))) {
|
if (rememberedSelection != undefined && rememberedSelection.has(nodeToStringKey(n))) {
|
||||||
n.visible = true;
|
n.visible = true;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
g.forEachEdge((e: Edge) => {
|
this.graph.forEachEdge((e: Edge) => {
|
||||||
e.visible = e.type == 'control' && e.source.visible && e.target.visible;
|
e.visible = e.type == 'control' && e.source.visible && e.target.visible;
|
||||||
});
|
});
|
||||||
g.layoutGraph();
|
this.layoutGraph();
|
||||||
g.updateGraphVisibility();
|
this.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
connectVisibleSelectedNodes() {
|
connectVisibleSelectedNodes() {
|
||||||
@ -385,8 +295,9 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateInputAndOutputBubbles() {
|
updateInputAndOutputBubbles() {
|
||||||
var g = this;
|
const view = this;
|
||||||
var s = g.visibleBubbles;
|
const g = this.graph;
|
||||||
|
const s = this.visibleBubbles;
|
||||||
s.classed("filledBubbleStyle", function (c) {
|
s.classed("filledBubbleStyle", function (c) {
|
||||||
var components = this.id.split(',');
|
var components = this.id.split(',');
|
||||||
if (components[0] == "ib") {
|
if (components[0] == "ib") {
|
||||||
@ -417,7 +328,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
if (components[0] == "ob") {
|
if (components[0] == "ob") {
|
||||||
var from = g.nodeMap[components[1]];
|
var from = g.nodeMap[components[1]];
|
||||||
var x = from.getOutputX();
|
var x = from.getOutputX();
|
||||||
var y = g.getNodeHeight(from) + DEFAULT_NODE_BUBBLE_RADIUS;
|
var y = from.getNodeHeight(view.state.showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
var transform = "translate(" + x + "," + y + ")";
|
var transform = "translate(" + x + "," + y + ")";
|
||||||
this.setAttribute('transform', transform);
|
this.setAttribute('transform', transform);
|
||||||
}
|
}
|
||||||
@ -425,12 +336,11 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachSelection(s) {
|
attachSelection(s) {
|
||||||
const graph = this;
|
|
||||||
if (!(s instanceof Set)) return;
|
if (!(s instanceof Set)) return;
|
||||||
graph.selectionHandler.clear();
|
this.selectionHandler.clear();
|
||||||
const selected = graph.nodes.filter((n) =>
|
const selected = [...this.graph.nodes((n) =>
|
||||||
s.has(graph.state.selection.stringKey(n)) && (!graph.state.hideDead || n.isLive()));
|
s.has(this.state.selection.stringKey(n)) && (!this.state.hideDead || n.isLive()))];
|
||||||
graph.selectionHandler.select(selected, true);
|
this.selectionHandler.select(selected, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
detachSelection() {
|
detachSelection() {
|
||||||
@ -438,80 +348,77 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectAllNodes() {
|
selectAllNodes() {
|
||||||
var graph = this;
|
|
||||||
if (!d3.event.shiftKey) {
|
if (!d3.event.shiftKey) {
|
||||||
graph.state.selection.clear();
|
this.state.selection.clear();
|
||||||
}
|
}
|
||||||
const allVisibleNodes = graph.nodes.filter((n) => n.visible);
|
const allVisibleNodes = [...this.graph.nodes((n) => n.visible)];
|
||||||
graph.state.selection.select(allVisibleNodes, true);
|
this.state.selection.select(allVisibleNodes, true);
|
||||||
graph.updateGraphVisibility();
|
this.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutAction(graph) {
|
layoutAction(graph: GraphView) {
|
||||||
graph.layoutGraph();
|
graph.layoutGraph();
|
||||||
graph.updateGraphVisibility();
|
graph.updateGraphVisibility();
|
||||||
graph.viewWholeGraph();
|
graph.viewWholeGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
showAllAction(graph) {
|
showAllAction(view: GraphView) {
|
||||||
graph.nodes.forEach((n: GNode) => {
|
for (const n of view.graph.nodes()) {
|
||||||
n.visible = !graph.state.hideDead || n.isLive();
|
n.visible = !view.state.hideDead || n.isLive();
|
||||||
});
|
};
|
||||||
graph.forEachEdge((e: Edge) => {
|
view.graph.forEachEdge((e: Edge) => {
|
||||||
e.visible = e.source.visible || e.target.visible;
|
e.visible = e.source.visible || e.target.visible;
|
||||||
});
|
});
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
graph.viewWholeGraph();
|
view.viewWholeGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleHideDead(graph) {
|
toggleHideDead(view: GraphView) {
|
||||||
graph.state.hideDead = !graph.state.hideDead;
|
view.state.hideDead = !view.state.hideDead;
|
||||||
if (graph.state.hideDead) graph.hideDead();
|
if (view.state.hideDead) view.hideDead();
|
||||||
var element = document.getElementById('toggle-hide-dead');
|
var element = document.getElementById('toggle-hide-dead');
|
||||||
element.classList.toggle('button-input-toggled', graph.state.hideDead);
|
element.classList.toggle('button-input-toggled', view.state.hideDead);
|
||||||
}
|
}
|
||||||
|
|
||||||
hideDead() {
|
hideDead() {
|
||||||
const graph = this;
|
for (const n of this.graph.nodes()) {
|
||||||
graph.nodes.filter(function (n) {
|
|
||||||
if (!n.isLive()) {
|
if (!n.isLive()) {
|
||||||
n.visible = false;
|
n.visible = false;
|
||||||
graph.state.selection.select([n], false);
|
this.state.selection.select([n], false);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
graph.updateGraphVisibility();
|
this.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
hideUnselectedAction(graph) {
|
hideUnselectedAction(view: GraphView) {
|
||||||
graph.nodes.forEach(function (n) {
|
for (const n of view.graph.nodes()) {
|
||||||
if (!graph.state.selection.isSelected(n)) {
|
if (!view.state.selection.isSelected(n)) {
|
||||||
n.visible = false;
|
n.visible = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
hideSelectedAction(graph) {
|
hideSelectedAction(view: GraphView) {
|
||||||
graph.nodes.forEach(function (n) {
|
for (const n of view.graph.nodes()) {
|
||||||
if (graph.state.selection.isSelected(n)) {
|
if (view.state.selection.isSelected(n)) {
|
||||||
n.visible = false;
|
n.visible = false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
graph.selectionHandler.clear();
|
view.selectionHandler.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomSelectionAction(graph) {
|
zoomSelectionAction(view: GraphView) {
|
||||||
graph.viewSelection();
|
view.viewSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTypesAction(graph) {
|
toggleTypesAction(view: GraphView) {
|
||||||
graph.toggleTypes();
|
view.toggleTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchInputAction(searchBar, e: KeyboardEvent) {
|
searchInputAction(searchBar, e: KeyboardEvent) {
|
||||||
const graph = this;
|
|
||||||
if (e.keyCode == 13) {
|
if (e.keyCode == 13) {
|
||||||
graph.selectionHandler.clear();
|
this.selectionHandler.clear();
|
||||||
var query = searchBar.value;
|
var query = searchBar.value;
|
||||||
window.sessionStorage.setItem("lastSearch", query);
|
window.sessionStorage.setItem("lastSearch", query);
|
||||||
if (query.length == 0) return;
|
if (query.length == 0) return;
|
||||||
@ -519,38 +426,38 @@ export class GraphView extends View implements PhaseView {
|
|||||||
var reg = new RegExp(query);
|
var reg = new RegExp(query);
|
||||||
var filterFunction = function (n) {
|
var filterFunction = function (n) {
|
||||||
return (reg.exec(n.getDisplayLabel()) != null ||
|
return (reg.exec(n.getDisplayLabel()) != null ||
|
||||||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
|
(this.state.showTypes && reg.exec(n.getDisplayType())) ||
|
||||||
(reg.exec(n.getTitle())) ||
|
(reg.exec(n.getTitle())) ||
|
||||||
reg.exec(n.opcode) != null);
|
reg.exec(n.opcode) != null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selection = graph.nodes.filter(
|
const selection = this.graph.nodes((n) => {
|
||||||
function (n, i) {
|
if ((e.ctrlKey || n.visible) && filterFunction(n)) {
|
||||||
if ((e.ctrlKey || n.visible) && filterFunction(n)) {
|
if (e.ctrlKey) n.visible = true;
|
||||||
if (e.ctrlKey) n.visible = true;
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
graph.selectionHandler.select(selection, true);
|
this.selectionHandler.select(selection, true);
|
||||||
graph.connectVisibleSelectedNodes();
|
this.connectVisibleSelectedNodes();
|
||||||
graph.updateGraphVisibility();
|
this.updateGraphVisibility();
|
||||||
searchBar.blur();
|
searchBar.blur();
|
||||||
graph.viewSelection();
|
this.viewSelection();
|
||||||
}
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
svgKeyDown() {
|
svgKeyDown() {
|
||||||
var state = this.state;
|
const view = this;
|
||||||
var graph = this;
|
const state = this.state;
|
||||||
|
const graph = this.graph;
|
||||||
|
|
||||||
// Don't handle key press repetition
|
// Don't handle key press repetition
|
||||||
if (state.lastKeyDown !== -1) return;
|
if (state.lastKeyDown !== -1) return;
|
||||||
|
|
||||||
var showSelectionFrontierNodes = function (inEdges, filter, select) {
|
var showSelectionFrontierNodes = function (inEdges, filter, select) {
|
||||||
var frontier = graph.getNodeFrontier(state.selection, inEdges, filter);
|
var frontier = view.getNodeFrontier(state.selection, inEdges, filter);
|
||||||
if (frontier != undefined && frontier.size) {
|
if (frontier != undefined && frontier.size) {
|
||||||
if (select) {
|
if (select) {
|
||||||
if (!d3.event.shiftKey) {
|
if (!d3.event.shiftKey) {
|
||||||
@ -558,7 +465,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
state.selection.select(frontier, true);
|
state.selection.select(frontier, true);
|
||||||
}
|
}
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
}
|
}
|
||||||
allowRepetition = false;
|
allowRepetition = false;
|
||||||
}
|
}
|
||||||
@ -616,7 +523,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
break;
|
break;
|
||||||
case 65:
|
case 65:
|
||||||
// 'a'
|
// 'a'
|
||||||
graph.selectAllNodes();
|
view.selectAllNodes();
|
||||||
allowRepetition = false;
|
allowRepetition = false;
|
||||||
break;
|
break;
|
||||||
case 38:
|
case 38:
|
||||||
@ -634,7 +541,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
break;
|
break;
|
||||||
case 83:
|
case 83:
|
||||||
// 's'
|
// 's'
|
||||||
graph.selectOrigins();
|
view.selectOrigins();
|
||||||
break;
|
break;
|
||||||
case 191:
|
case 191:
|
||||||
// '/'
|
// '/'
|
||||||
@ -658,7 +565,12 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
layoutGraph() {
|
layoutGraph() {
|
||||||
console.time("layoutGraph");
|
console.time("layoutGraph");
|
||||||
layoutNodeGraph(this);
|
layoutNodeGraph(this.graph, this.state.showTypes);
|
||||||
|
const [[width, height], extent] = this.graph.redetermineGraphBoundingBox(this.state.showTypes);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.panZoom.translateExtent(extent);
|
||||||
|
this.minScale();
|
||||||
console.timeEnd("layoutGraph");
|
console.timeEnd("layoutGraph");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -668,7 +580,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
let phase = null;
|
let phase = null;
|
||||||
for (const n of state.selection) {
|
for (const n of state.selection) {
|
||||||
if (n.origin) {
|
if (n.origin) {
|
||||||
const node = this.nodeMap[n.origin.nodeId];
|
const node = this.graph.nodeMap[n.origin.nodeId];
|
||||||
origins.push(node);
|
origins.push(node);
|
||||||
phase = n.origin.phase;
|
phase = n.origin.phase;
|
||||||
}
|
}
|
||||||
@ -684,13 +596,14 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
// call to propagate changes to graph
|
// call to propagate changes to graph
|
||||||
updateGraphVisibility() {
|
updateGraphVisibility() {
|
||||||
let graph = this;
|
const view = this;
|
||||||
let state = graph.state;
|
const graph = this.graph;
|
||||||
|
const state = this.state;
|
||||||
|
|
||||||
var filteredEdges = [...graph.filteredEdges(function (e) {
|
var filteredEdges = [...graph.filteredEdges(function (e) {
|
||||||
return e.source.visible && e.target.visible;
|
return e.source.visible && e.target.visible;
|
||||||
})];
|
})];
|
||||||
const selEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>("path").data(filteredEdges, edgeToStr);
|
const selEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>("path").data(filteredEdges, edgeToStr);
|
||||||
|
|
||||||
// remove old links
|
// remove old links
|
||||||
selEdges.exit().remove();
|
selEdges.exit().remove();
|
||||||
@ -704,9 +617,9 @@ export class GraphView extends View implements PhaseView {
|
|||||||
.on("click", function (edge) {
|
.on("click", function (edge) {
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
if (!d3.event.shiftKey) {
|
if (!d3.event.shiftKey) {
|
||||||
graph.selectionHandler.clear();
|
view.selectionHandler.clear();
|
||||||
}
|
}
|
||||||
graph.selectionHandler.select([edge.source, edge.target], true);
|
view.selectionHandler.select([edge.source, edge.target], true);
|
||||||
})
|
})
|
||||||
.attr("adjacentToHover", "false")
|
.attr("adjacentToHover", "false")
|
||||||
.classed('value', function (e) {
|
.classed('value', function (e) {
|
||||||
@ -727,8 +640,8 @@ export class GraphView extends View implements PhaseView {
|
|||||||
newAndOldEdges.classed('hidden', (e) => !e.isVisible());
|
newAndOldEdges.classed('hidden', (e) => !e.isVisible());
|
||||||
|
|
||||||
// select existing nodes
|
// select existing nodes
|
||||||
const filteredNodes = graph.nodes.filter(n => n.visible);
|
const filteredNodes = [...graph.nodes(n => n.visible)];
|
||||||
const allNodes = graph.visibleNodes.selectAll<SVGGElement, GNode>("g");
|
const allNodes = view.visibleNodes.selectAll<SVGGElement, GNode>("g");
|
||||||
const selNodes = allNodes.data(filteredNodes, nodeToStr);
|
const selNodes = allNodes.data(filteredNodes, nodeToStr);
|
||||||
|
|
||||||
// remove old nodes
|
// remove old nodes
|
||||||
@ -747,36 +660,36 @@ export class GraphView extends View implements PhaseView {
|
|||||||
.classed("simplified", function (n) { return n.isSimplified(); })
|
.classed("simplified", function (n) { return n.isSimplified(); })
|
||||||
.classed("machine", function (n) { return n.isMachine(); })
|
.classed("machine", function (n) { return n.isMachine(); })
|
||||||
.on('mouseenter', function (node) {
|
.on('mouseenter', function (node) {
|
||||||
const visibleEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>('path');
|
const visibleEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>('path');
|
||||||
const adjInputEdges = visibleEdges.filter(e => { return e.target === node; });
|
const adjInputEdges = visibleEdges.filter(e => { return e.target === node; });
|
||||||
const adjOutputEdges = visibleEdges.filter(e => { return e.source === node; });
|
const adjOutputEdges = visibleEdges.filter(e => { return e.source === node; });
|
||||||
adjInputEdges.attr('relToHover', "input");
|
adjInputEdges.attr('relToHover', "input");
|
||||||
adjOutputEdges.attr('relToHover', "output");
|
adjOutputEdges.attr('relToHover', "output");
|
||||||
const adjInputNodes = adjInputEdges.data().map(e => e.source);
|
const adjInputNodes = adjInputEdges.data().map(e => e.source);
|
||||||
const visibleNodes = graph.visibleNodes.selectAll<SVGGElement, GNode>("g");
|
const visibleNodes = view.visibleNodes.selectAll<SVGGElement, GNode>("g");
|
||||||
const input = visibleNodes.data<GNode>(adjInputNodes, nodeToStr)
|
const input = visibleNodes.data<GNode>(adjInputNodes, nodeToStr)
|
||||||
.attr('relToHover', "input");
|
.attr('relToHover', "input");
|
||||||
const adjOutputNodes = adjOutputEdges.data().map(e => e.target);
|
const adjOutputNodes = adjOutputEdges.data().map(e => e.target);
|
||||||
const output = visibleNodes.data<GNode>(adjOutputNodes, nodeToStr)
|
const output = visibleNodes.data<GNode>(adjOutputNodes, nodeToStr)
|
||||||
.attr('relToHover', "output");
|
.attr('relToHover', "output");
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
})
|
})
|
||||||
.on('mouseleave', function (node) {
|
.on('mouseleave', function (node) {
|
||||||
const visibleEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>('path');
|
const visibleEdges = view.visibleEdges.selectAll<SVGPathElement, Edge>('path');
|
||||||
const adjEdges = visibleEdges.filter(e => { return e.target === node || e.source === node; });
|
const adjEdges = visibleEdges.filter(e => { return e.target === node || e.source === node; });
|
||||||
adjEdges.attr('relToHover', "none");
|
adjEdges.attr('relToHover', "none");
|
||||||
const adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
|
const adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
|
||||||
const visibleNodes = graph.visibleNodes.selectAll<SVGPathElement, GNode>("g");
|
const visibleNodes = view.visibleNodes.selectAll<SVGPathElement, GNode>("g");
|
||||||
const nodes = visibleNodes.data(adjNodes, nodeToStr)
|
const nodes = visibleNodes.data(adjNodes, nodeToStr)
|
||||||
.attr('relToHover', "none");
|
.attr('relToHover', "none");
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
})
|
})
|
||||||
.on("click", (d) => {
|
.on("click", (d) => {
|
||||||
if (!d3.event.shiftKey) graph.selectionHandler.clear();
|
if (!d3.event.shiftKey) view.selectionHandler.clear();
|
||||||
graph.selectionHandler.select([d], undefined);
|
view.selectionHandler.select([d], undefined);
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
})
|
})
|
||||||
.call(graph.drag)
|
.call(view.drag)
|
||||||
|
|
||||||
newGs.append("rect")
|
newGs.append("rect")
|
||||||
.attr("rx", 10)
|
.attr("rx", 10)
|
||||||
@ -785,7 +698,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
return d.getTotalNodeWidth();
|
return d.getTotalNodeWidth();
|
||||||
})
|
})
|
||||||
.attr('height', function (d) {
|
.attr('height', function (d) {
|
||||||
return graph.getNodeHeight(d);
|
return d.getNodeHeight(view.state.showTypes);
|
||||||
})
|
})
|
||||||
|
|
||||||
function appendInputAndOutputBubbles(g, d) {
|
function appendInputAndOutputBubbles(g, d) {
|
||||||
@ -811,13 +724,13 @@ export class GraphView extends View implements PhaseView {
|
|||||||
var visible = !edge.isVisible();
|
var visible = !edge.isVisible();
|
||||||
node.setInputVisibility(components[2], visible);
|
node.setInputVisibility(components[2], visible);
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (d.outputs.length != 0) {
|
if (d.outputs.length != 0) {
|
||||||
var x = d.getOutputX();
|
const x = d.getOutputX();
|
||||||
var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS;
|
const y = d.getNodeHeight(view.state.showTypes) + DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
var s = g.append('circle')
|
g.append('circle')
|
||||||
.classed("filledBubbleStyle", function (c) {
|
.classed("filledBubbleStyle", function (c) {
|
||||||
return d.areAnyOutputsVisible() == 2;
|
return d.areAnyOutputsVisible() == 2;
|
||||||
})
|
})
|
||||||
@ -835,7 +748,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
.on("click", function (d) {
|
.on("click", function (d) {
|
||||||
d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
|
d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
|
||||||
d3.event.stopPropagation();
|
d3.event.stopPropagation();
|
||||||
graph.updateGraphVisibility();
|
view.updateGraphVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -879,7 +792,7 @@ export class GraphView extends View implements PhaseView {
|
|||||||
const newAndOldNodes = newGs.merge(selNodes);
|
const newAndOldNodes = newGs.merge(selNodes);
|
||||||
|
|
||||||
newAndOldNodes.select<SVGTextElement>('.type').each(function (d) {
|
newAndOldNodes.select<SVGTextElement>('.type').each(function (d) {
|
||||||
this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
|
this.setAttribute('visibility', view.state.showTypes ? 'visible' : 'hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
newAndOldNodes
|
newAndOldNodes
|
||||||
@ -889,15 +802,15 @@ export class GraphView extends View implements PhaseView {
|
|||||||
})
|
})
|
||||||
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
|
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
|
||||||
.select('rect')
|
.select('rect')
|
||||||
.attr('height', function (d) { return graph.getNodeHeight(d); });
|
.attr('height', function (d) { return d.getNodeHeight(view.state.showTypes); });
|
||||||
|
|
||||||
graph.visibleBubbles = d3.selectAll('circle');
|
view.visibleBubbles = d3.selectAll('circle');
|
||||||
|
|
||||||
graph.updateInputAndOutputBubbles();
|
view.updateInputAndOutputBubbles();
|
||||||
|
|
||||||
graph.maxGraphX = graph.maxGraphNodeX;
|
graph.maxGraphX = graph.maxGraphNodeX;
|
||||||
newAndOldEdges.attr("d", function (edge) {
|
newAndOldEdges.attr("d", function (edge) {
|
||||||
return edge.generatePath(graph);
|
return edge.generatePath(graph, view.state.showTypes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,22 +847,22 @@ export class GraphView extends View implements PhaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewSelection() {
|
viewSelection() {
|
||||||
var graph = this;
|
var view = this;
|
||||||
var minX, maxX, minY, maxY;
|
var minX, maxX, minY, maxY;
|
||||||
var hasSelection = false;
|
var hasSelection = false;
|
||||||
graph.visibleNodes.selectAll<SVGGElement, GNode>("g").each(function (n) {
|
view.visibleNodes.selectAll<SVGGElement, GNode>("g").each(function (n) {
|
||||||
if (graph.state.selection.isSelected(n)) {
|
if (view.state.selection.isSelected(n)) {
|
||||||
hasSelection = true;
|
hasSelection = true;
|
||||||
minX = minX ? Math.min(minX, n.x) : n.x;
|
minX = minX ? Math.min(minX, n.x) : n.x;
|
||||||
maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
|
maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
|
||||||
n.x + n.getTotalNodeWidth();
|
n.x + n.getTotalNodeWidth();
|
||||||
minY = minY ? Math.min(minY, n.y) : n.y;
|
minY = minY ? Math.min(minY, n.y) : n.y;
|
||||||
maxY = maxY ? Math.max(maxY, n.y + graph.getNodeHeight(n)) :
|
maxY = maxY ? Math.max(maxY, n.y + n.getNodeHeight(view.state.showTypes)) :
|
||||||
n.y + graph.getNodeHeight(n);
|
n.y + n.getNodeHeight(view.state.showTypes);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (hasSelection) {
|
if (hasSelection) {
|
||||||
graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
|
view.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
|
||||||
maxX + NODE_INPUT_WIDTH, maxY + 60,
|
maxX + NODE_INPUT_WIDTH, maxY + 60,
|
||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
@ -971,6 +884,6 @@ export class GraphView extends View implements PhaseView {
|
|||||||
|
|
||||||
viewWholeGraph() {
|
viewWholeGraph() {
|
||||||
this.panZoom.scaleTo(this.svg, 0);
|
this.panZoom.scaleTo(this.svg, 0);
|
||||||
this.panZoom.translateTo(this.svg, this.minGraphX + this.width / 2, this.minGraphY + this.height / 2)
|
this.panZoom.translateTo(this.svg, this.graph.minGraphX + this.width / 2, this.graph.minGraphY + this.height / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
tools/turbolizer/src/graph.ts
Normal file
127
tools/turbolizer/src/graph.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { GNode, MINIMUM_NODE_OUTPUT_APPROACH, NODE_INPUT_WIDTH } from "./node";
|
||||||
|
import { MAX_RANK_SENTINEL } from "./constants";
|
||||||
|
import { alignUp, measureText } from "./util";
|
||||||
|
import { Edge, MINIMUM_EDGE_SEPARATION } from "./edge";
|
||||||
|
|
||||||
|
export class Graph {
|
||||||
|
nodeMap: Array<GNode>;
|
||||||
|
minGraphX: number;
|
||||||
|
maxGraphX: number;
|
||||||
|
minGraphY: number;
|
||||||
|
maxGraphY: number;
|
||||||
|
maxGraphNodeX: number;
|
||||||
|
maxBackEdgeNumber: number;
|
||||||
|
|
||||||
|
constructor(data: any) {
|
||||||
|
this.nodeMap = [];
|
||||||
|
|
||||||
|
this.minGraphX = 0;
|
||||||
|
this.maxGraphX = 1;
|
||||||
|
this.minGraphY = 0;
|
||||||
|
this.maxGraphY = 1;
|
||||||
|
|
||||||
|
data.nodes.forEach((n) => {
|
||||||
|
n.__proto__ = GNode.prototype;
|
||||||
|
n.visible = false;
|
||||||
|
n.x = 0;
|
||||||
|
n.y = 0;
|
||||||
|
if (typeof n.pos === "number") {
|
||||||
|
// Backwards compatibility.
|
||||||
|
n.sourcePosition = { scriptOffset: n.pos, inliningId: -1 };
|
||||||
|
}
|
||||||
|
n.rank = MAX_RANK_SENTINEL;
|
||||||
|
n.inputs = [];
|
||||||
|
n.outputs = [];
|
||||||
|
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
|
||||||
|
// Every control node is a CFG node.
|
||||||
|
n.cfg = n.control;
|
||||||
|
this.nodeMap[n.id] = n;
|
||||||
|
n.displayLabel = n.getDisplayLabel();
|
||||||
|
n.labelbbox = measureText(n.displayLabel);
|
||||||
|
const typebbox = measureText(n.getDisplayType());
|
||||||
|
const innerwidth = Math.max(n.labelbbox.width, typebbox.width);
|
||||||
|
n.width = alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
|
||||||
|
NODE_INPUT_WIDTH);
|
||||||
|
const innerheight = Math.max(n.labelbbox.height, typebbox.height);
|
||||||
|
n.normalheight = innerheight + 20;
|
||||||
|
});
|
||||||
|
|
||||||
|
data.edges.forEach((e: any) => {
|
||||||
|
var t = this.nodeMap[e.target];
|
||||||
|
var s = this.nodeMap[e.source];
|
||||||
|
var newEdge = new Edge(t, e.index, s, e.type);
|
||||||
|
t.inputs.push(newEdge);
|
||||||
|
s.outputs.push(newEdge);
|
||||||
|
if (e.type == 'control') {
|
||||||
|
// Every source of a control edge is a CFG node.
|
||||||
|
s.cfg = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*nodes(p = (n: GNode) => true) {
|
||||||
|
for (const node of this.nodeMap) {
|
||||||
|
if (!node || !p(node)) continue;
|
||||||
|
yield node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*filteredEdges(p: (e: Edge) => boolean) {
|
||||||
|
for (const node of this.nodes()) {
|
||||||
|
for (const edge of node.inputs) {
|
||||||
|
if (p(edge)) yield edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachEdge(p: (e: Edge) => void) {
|
||||||
|
for (const node of this.nodeMap) {
|
||||||
|
if (!node) continue;
|
||||||
|
for (const edge of node.inputs) {
|
||||||
|
p(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redetermineGraphBoundingBox(showTypes: boolean): [[number, number], [[number, number], [number, number]]] {
|
||||||
|
this.minGraphX = 0;
|
||||||
|
this.maxGraphNodeX = 1;
|
||||||
|
this.maxGraphX = undefined; // see below
|
||||||
|
this.minGraphY = 0;
|
||||||
|
this.maxGraphY = 1;
|
||||||
|
|
||||||
|
for (const node of this.nodes()) {
|
||||||
|
if (!node.visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.x < this.minGraphX) {
|
||||||
|
this.minGraphX = node.x;
|
||||||
|
}
|
||||||
|
if ((node.x + node.getTotalNodeWidth()) > this.maxGraphNodeX) {
|
||||||
|
this.maxGraphNodeX = node.x + node.getTotalNodeWidth();
|
||||||
|
}
|
||||||
|
if ((node.y - 50) < this.minGraphY) {
|
||||||
|
this.minGraphY = node.y - 50;
|
||||||
|
}
|
||||||
|
if ((node.y + node.getNodeHeight(showTypes) + 50) > this.maxGraphY) {
|
||||||
|
this.maxGraphY = node.y + node.getNodeHeight(showTypes) + 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.maxGraphX = this.maxGraphNodeX +
|
||||||
|
this.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
|
||||||
|
|
||||||
|
const width = (this.maxGraphX - this.minGraphX);
|
||||||
|
const height = this.maxGraphY - this.minGraphY;
|
||||||
|
|
||||||
|
const extent: [[number, number], [number, number]] = [
|
||||||
|
[this.minGraphX - width / 2, this.minGraphY - height / 2],
|
||||||
|
[this.maxGraphX + width / 2, this.maxGraphY + height / 2]
|
||||||
|
];
|
||||||
|
|
||||||
|
return [[width, height], extent];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import {NodeOrigin} from "../src/source-resolver"
|
import { NodeOrigin } from "../src/source-resolver"
|
||||||
import {MINIMUM_EDGE_SEPARATION, Edge} from "../src/edge"
|
import { MINIMUM_EDGE_SEPARATION, Edge } from "../src/edge"
|
||||||
|
|
||||||
export const DEFAULT_NODE_BUBBLE_RADIUS = 12;
|
export const DEFAULT_NODE_BUBBLE_RADIUS = 12;
|
||||||
export const NODE_INPUT_WIDTH = 50;
|
export const NODE_INPUT_WIDTH = 50;
|
||||||
@ -42,6 +42,7 @@ export class GNode {
|
|||||||
labelbbox: { width: number, height: number };
|
labelbbox: { width: number, height: number };
|
||||||
visitOrderWithinRank: number;
|
visitOrderWithinRank: number;
|
||||||
cfg: boolean;
|
cfg: boolean;
|
||||||
|
normalheight: number;
|
||||||
|
|
||||||
isControl() {
|
isControl() {
|
||||||
return this.control;
|
return this.control;
|
||||||
@ -158,8 +159,15 @@ export class GNode {
|
|||||||
return this.y - MINIMUM_NODE_INPUT_APPROACH -
|
return this.y - MINIMUM_NODE_INPUT_APPROACH -
|
||||||
(index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS
|
(index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS
|
||||||
}
|
}
|
||||||
getOutputApproach(graph) {
|
getNodeHeight(showTypes:boolean): number {
|
||||||
return this.y + this.outputApproach + graph.getNodeHeight(this) +
|
if (showTypes) {
|
||||||
|
return this.normalheight + this.labelbbox.height;
|
||||||
|
} else {
|
||||||
|
return this.normalheight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getOutputApproach(showTypes:boolean) {
|
||||||
|
return this.y + this.outputApproach + this.getNodeHeight(showTypes) +
|
||||||
+ DEFAULT_NODE_BUBBLE_RADIUS;
|
+ DEFAULT_NODE_BUBBLE_RADIUS;
|
||||||
}
|
}
|
||||||
getInputX(index) {
|
getInputX(index) {
|
||||||
|
@ -114,3 +114,14 @@ export function isIterable(obj: any): obj is Iterable<any> {
|
|||||||
export function alignUp(raw:number, multiple:number):number {
|
export function alignUp(raw:number, multiple:number):number {
|
||||||
return Math.floor((raw + multiple - 1) / multiple) * multiple;
|
return Math.floor((raw + multiple - 1) / multiple) * multiple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function measureText(text: string) {
|
||||||
|
const textMeasure = document.getElementById('text-measure');
|
||||||
|
if (textMeasure instanceof SVGTSpanElement) {
|
||||||
|
textMeasure.textContent = text;
|
||||||
|
return {
|
||||||
|
width: textMeasure.getBBox().width,
|
||||||
|
height: textMeasure.getBBox().height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@
|
|||||||
"src/lang-disassembly.ts",
|
"src/lang-disassembly.ts",
|
||||||
"src/node.ts",
|
"src/node.ts",
|
||||||
"src/edge.ts",
|
"src/edge.ts",
|
||||||
|
"src/graph.ts",
|
||||||
"src/source-resolver.ts",
|
"src/source-resolver.ts",
|
||||||
"src/selection.ts",
|
"src/selection.ts",
|
||||||
"src/selection-broker.ts",
|
"src/selection-broker.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user