[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:
Sigurd Schneider 2019-01-04 14:33:57 +01:00 committed by Commit Bot
parent a53332fe35
commit b53dcfd5a6
7 changed files with 316 additions and 302 deletions

View File

@ -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 +

View File

@ -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();
} }

View File

@ -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)
} }
} }

View 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];
}
}

View File

@ -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) {

View File

@ -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,
};
}
}

View File

@ -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",