summaryrefslogtreecommitdiff
path: root/deps/v8/tools/turbolizer/src/graph-view.ts
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/tools/turbolizer/src/graph-view.ts')
-rw-r--r--deps/v8/tools/turbolizer/src/graph-view.ts952
1 files changed, 952 insertions, 0 deletions
diff --git a/deps/v8/tools/turbolizer/src/graph-view.ts b/deps/v8/tools/turbolizer/src/graph-view.ts
new file mode 100644
index 0000000000..9e136f8225
--- /dev/null
+++ b/deps/v8/tools/turbolizer/src/graph-view.ts
@@ -0,0 +1,952 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as d3 from "d3"
+import {layoutNodeGraph} from "./graph-layout.js"
+import {MAX_RANK_SENTINEL} from "./constants.js"
+import {GNode, nodeToStr, isNodeInitiallyVisible} from "./node.js"
+import {NODE_INPUT_WIDTH, MINIMUM_NODE_OUTPUT_APPROACH} from "./node.js"
+import {DEFAULT_NODE_BUBBLE_RADIUS} from "./node.js"
+import {Edge, edgeToStr} from "./edge.js"
+import {View, PhaseView} from "./view.js"
+import {MySelection} from "./selection.js"
+import {partial, alignUp} from "./util.js"
+
+function nodeToStringKey(n) {
+ return "" + n.id;
+}
+
+interface GraphState {
+ showTypes: boolean;
+ selection: MySelection;
+ mouseDownNode: any;
+ justDragged: boolean,
+ justScaleTransGraph: boolean,
+ lastKeyDown: number,
+ hideDead: boolean
+}
+
+export class GraphView extends View implements PhaseView {
+ divElement: d3.Selection<any, any, any, any>;
+ svg: d3.Selection<any, any, any, any>;
+ showPhaseByName: (string) => void;
+ state: GraphState;
+ nodes: Array<GNode>;
+ edges: Array<any>;
+ selectionHandler: NodeSelectionHandler;
+ graphElement: d3.Selection<any, any, any, any>;
+ visibleNodes: d3.Selection<any, GNode, any, any>;
+ visibleEdges: d3.Selection<any, Edge, any, any>;
+ minGraphX: number;
+ maxGraphX: number;
+ minGraphY: number;
+ maxGraphY: number;
+ width: number;
+ height: number;
+ maxGraphNodeX: number;
+ drag: d3.DragBehavior<any, GNode, GNode>;
+ panZoom: d3.ZoomBehavior<SVGElement, any>;
+ nodeMap: Array<any>;
+ visibleBubbles: d3.Selection<any, any, any, any>;
+ transitionTimout: number;
+
+ createViewElement() {
+ const pane = document.createElement('div');
+ pane.setAttribute('id', "graph");
+ return pane;
+ }
+
+ constructor(id, broker, showPhaseByName: (string) => void) {
+ super(id);
+ var graph = this;
+ this.showPhaseByName = showPhaseByName;
+ this.divElement = d3.select(this.divNode);
+ const svg = this.divElement.append("svg").attr('version', '1.1')
+ .attr("width", "100%")
+ .attr("height", "100%");
+ svg.on("click", function (d) {
+ graph.selectionHandler.clear();
+ });
+ graph.svg = svg;
+
+ graph.nodes = [];
+ graph.edges = [];
+
+ graph.minGraphX = 0;
+ graph.maxGraphX = 1;
+ graph.minGraphY = 0;
+ graph.maxGraphY = 1;
+
+ graph.state = {
+ selection: null,
+ mouseDownNode: null,
+ justDragged: false,
+ justScaleTransGraph: false,
+ lastKeyDown: -1,
+ showTypes: false,
+ hideDead: false
+ };
+
+ this.selectionHandler = {
+ clear: function () {
+ graph.state.selection.clear();
+ broker.broadcastClear(this);
+ graph.updateGraphVisibility();
+ },
+ select: function (nodes, selected) {
+ let locations = [];
+ for (const node of nodes) {
+ if (node.sourcePosition) {
+ locations.push(node.sourcePosition);
+ }
+ if (node.origin && node.origin.bytecodePosition) {
+ locations.push({ bytecodePosition: node.origin.bytecodePosition });
+ }
+ }
+ graph.state.selection.select(nodes, selected);
+ broker.broadcastSourcePositionSelect(this, locations, selected);
+ graph.updateGraphVisibility();
+ },
+ brokeredNodeSelect: function (locations, selected) {
+ let selection = graph.nodes
+ .filter(function (n) {
+ return locations.has(nodeToStringKey(n))
+ && (!graph.state.hideDead || n.isLive());
+ });
+ graph.state.selection.select(selection, selected);
+ // Update edge visibility based on selection.
+ graph.nodes.forEach((n) => {
+ if (graph.state.selection.isSelected(n)) n.visible = true;
+ });
+ graph.edges.forEach(function (e) {
+ e.visible = e.visible ||
+ (graph.state.selection.isSelected(e.source) && graph.state.selection.isSelected(e.target));
+ });
+ graph.updateGraphVisibility();
+ },
+ brokeredClear: function () {
+ graph.state.selection.clear();
+ graph.updateGraphVisibility();
+ }
+ };
+ broker.addNodeHandler(this.selectionHandler);
+
+ graph.state.selection = new MySelection(nodeToStringKey);
+
+ const defs = svg.append('svg:defs');
+ defs.append('svg:marker')
+ .attr('id', 'end-arrow')
+ .attr('viewBox', '0 -4 8 8')
+ .attr('refX', 2)
+ .attr('markerWidth', 2.5)
+ .attr('markerHeight', 2.5)
+ .attr('orient', 'auto')
+ .append('svg:path')
+ .attr('d', 'M0,-4L8,0L0,4');
+
+ this.graphElement = svg.append("g");
+ graph.visibleEdges = this.graphElement.append("g");
+ graph.visibleNodes = this.graphElement.append("g");
+
+ graph.drag = d3.drag<any, GNode, GNode>()
+ .on("drag", function (d) {
+ d.x += d3.event.dx;
+ d.y += d3.event.dy;
+ graph.updateGraphVisibility();
+ });
+
+
+ d3.select("#layout").on("click", partial(this.layoutAction, graph));
+ d3.select("#show-all").on("click", partial(this.showAllAction, graph));
+ d3.select("#toggle-hide-dead").on("click", partial(this.toggleHideDead, graph));
+ d3.select("#hide-unselected").on("click", partial(this.hideUnselectedAction, graph));
+ d3.select("#hide-selected").on("click", partial(this.hideSelectedAction, graph));
+ d3.select("#zoom-selection").on("click", partial(this.zoomSelectionAction, graph));
+ d3.select("#toggle-types").on("click", partial(this.toggleTypesAction, graph));
+
+ // listen for key events
+ d3.select(window).on("keydown", function (e) {
+ graph.svgKeyDown.call(graph);
+ }).on("keyup", function () {
+ graph.svgKeyUp.call(graph);
+ });
+
+ function zoomed() {
+ if (d3.event.shiftKey) return false;
+ graph.graphElement.attr("transform", d3.event.transform);
+ }
+
+ const zoomSvg = d3.zoom<SVGElement, any>()
+ .scaleExtent([0.2, 40])
+ .on("zoom", zoomed)
+ .on("start", function () {
+ if (d3.event.shiftKey) return;
+ d3.select('body').style("cursor", "move");
+ })
+ .on("end", function () {
+ d3.select('body').style("cursor", "auto");
+ });
+
+ svg.call(zoomSvg).on("dblclick.zoom", null);
+
+ graph.panZoom = zoomSvg;
+
+ }
+
+
+ static get selectedClass() {
+ return "selected";
+ }
+ static get rectClass() {
+ return "nodeStyle";
+ }
+ static get activeEditId() {
+ return "active-editing";
+ }
+ static get nodeRadius() {
+ return 50;
+ }
+
+ getNodeHeight(d): number {
+ if (this.state.showTypes) {
+ return d.normalheight + d.labelbbox.height;
+ } else {
+ return d.normalheight;
+ }
+ }
+
+ getEdgeFrontier(nodes, inEdges, edgeFilter) {
+ let frontier = new Set();
+ for (const n of nodes) {
+ var edges = inEdges ? n.inputs : n.outputs;
+ var edgeNumber = 0;
+ edges.forEach(function (edge) {
+ if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
+ frontier.add(edge);
+ }
+ ++edgeNumber;
+ });
+ }
+ return frontier;
+ }
+
+ getNodeFrontier(nodes, inEdges, edgeFilter) {
+ let graph = this;
+ var frontier = new Set();
+ var newState = true;
+ var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
+ // Control key toggles edges rather than just turning them on
+ if (d3.event.ctrlKey) {
+ edgeFrontier.forEach(function (edge) {
+ if (edge.visible) {
+ newState = false;
+ }
+ });
+ }
+ edgeFrontier.forEach(function (edge) {
+ edge.visible = newState;
+ if (newState) {
+ var node = inEdges ? edge.source : edge.target;
+ node.visible = true;
+ frontier.add(node);
+ }
+ });
+ graph.updateGraphVisibility();
+ if (newState) {
+ return frontier;
+ } else {
+ return undefined;
+ }
+ }
+
+ initializeContent(data, rememberedSelection) {
+ this.createGraph(data, rememberedSelection);
+ if (rememberedSelection != null) {
+ this.attachSelection(rememberedSelection);
+ this.connectVisibleSelectedNodes();
+ this.viewSelection();
+ } else {
+ this.viewWholeGraph();
+ }
+ }
+
+ deleteContent() {
+ if (this.visibleNodes) {
+ this.nodes = [];
+ this.edges = [];
+ this.nodeMap = [];
+ this.updateGraphVisibility();
+ }
+ };
+
+ measureText(text) {
+ const textMeasure = document.getElementById('text-measure') as SVGTSpanElement;
+ textMeasure.textContent = text;
+ return {
+ width: textMeasure.getBBox().width,
+ height: textMeasure.getBBox().height,
+ };
+ }
+
+ createGraph(data, rememberedSelection) {
+ var g = this;
+ g.nodes = [];
+ g.nodeMap = [];
+ 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;
+ 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);
+ });
+ g.edges = [];
+ data.edges.forEach(function (e, i) {
+ var t = g.nodeMap[e.target];
+ var s = g.nodeMap[e.source];
+ var newEdge = new Edge(t, e.index, s, e.type);
+ t.inputs.push(newEdge);
+ s.outputs.push(newEdge);
+ g.edges.push(newEdge);
+ if (e.type == 'control') {
+ s.cfg = true;
+ }
+ });
+ g.nodes.forEach(function (n, i) {
+ n.visible = isNodeInitiallyVisible(n) && (!g.state.hideDead || n.isLive());
+ if (rememberedSelection != undefined) {
+ if (rememberedSelection.has(nodeToStringKey(n))) {
+ n.visible = true;
+ }
+ }
+ });
+ g.updateGraphVisibility();
+ g.layoutGraph();
+ g.updateGraphVisibility();
+ g.viewWholeGraph();
+ }
+
+ connectVisibleSelectedNodes() {
+ var graph = this;
+ for (const n of graph.state.selection) {
+ n.inputs.forEach(function (edge) {
+ if (edge.source.visible && edge.target.visible) {
+ edge.visible = true;
+ }
+ });
+ n.outputs.forEach(function (edge) {
+ if (edge.source.visible && edge.target.visible) {
+ edge.visible = true;
+ }
+ });
+ }
+ }
+
+ updateInputAndOutputBubbles() {
+ var g = this;
+ var s = g.visibleBubbles;
+ s.classed("filledBubbleStyle", function (c) {
+ var components = this.id.split(',');
+ if (components[0] == "ib") {
+ var edge = g.nodeMap[components[3]].inputs[components[2]];
+ return edge.isVisible();
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
+ }
+ }).classed("halfFilledBubbleStyle", function (c) {
+ var components = this.id.split(',');
+ if (components[0] == "ib") {
+ var edge = g.nodeMap[components[3]].inputs[components[2]];
+ return false;
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
+ }
+ }).classed("bubbleStyle", function (c) {
+ var components = this.id.split(',');
+ if (components[0] == "ib") {
+ var edge = g.nodeMap[components[3]].inputs[components[2]];
+ return !edge.isVisible();
+ } else {
+ return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
+ }
+ });
+ s.each(function (c) {
+ var components = this.id.split(',');
+ if (components[0] == "ob") {
+ var from = g.nodeMap[components[1]];
+ var x = from.getOutputX();
+ var y = g.getNodeHeight(from) + DEFAULT_NODE_BUBBLE_RADIUS;
+ var transform = "translate(" + x + "," + y + ")";
+ this.setAttribute('transform', transform);
+ }
+ });
+ }
+
+ attachSelection(s) {
+ const graph = this;
+ if (!(s instanceof Set)) return;
+ graph.selectionHandler.clear();
+ const selected = graph.nodes.filter((n) =>
+ s.has(graph.state.selection.stringKey(n)) && (!graph.state.hideDead || n.isLive()));
+ graph.selectionHandler.select(selected, true);
+ }
+
+ detachSelection() {
+ return this.state.selection.detachSelection();
+ }
+
+ selectAllNodes() {
+ var graph = this;
+ if (!d3.event.shiftKey) {
+ graph.state.selection.clear();
+ }
+ const allVisibleNodes = graph.nodes.filter((n) => n.visible);
+ graph.state.selection.select(allVisibleNodes, true);
+ graph.updateGraphVisibility();
+ }
+
+ layoutAction(graph) {
+ graph.updateGraphVisibility();
+ graph.layoutGraph();
+ graph.updateGraphVisibility();
+ graph.viewWholeGraph();
+ }
+
+ showAllAction(graph) {
+ graph.nodes.forEach(function (n) {
+ n.visible = !graph.state.hideDead || n.isLive();
+ });
+ graph.edges.forEach(function (e) {
+ e.visible = !graph.state.hideDead || (e.source.isLive() && e.target.isLive());
+ });
+ graph.updateGraphVisibility();
+ graph.viewWholeGraph();
+ }
+
+ toggleHideDead(graph) {
+ graph.state.hideDead = !graph.state.hideDead;
+ if (graph.state.hideDead) graph.hideDead();
+ var element = document.getElementById('toggle-hide-dead');
+ element.classList.toggle('button-input-toggled', graph.state.hideDead);
+ }
+
+ hideDead() {
+ const graph = this;
+ graph.nodes.filter(function (n) {
+ if (!n.isLive()) {
+ n.visible = false;
+ graph.state.selection.select([n], false);
+ }
+ })
+ graph.updateGraphVisibility();
+ }
+
+ hideUnselectedAction(graph) {
+ graph.nodes.forEach(function (n) {
+ if (!graph.state.selection.isSelected(n)) {
+ n.visible = false;
+ }
+ });
+ graph.updateGraphVisibility();
+ }
+
+ hideSelectedAction(graph) {
+ graph.nodes.forEach(function (n) {
+ if (graph.state.selection.isSelected(n)) {
+ n.visible = false;
+ }
+ });
+ graph.selectionHandler.clear();
+ }
+
+ zoomSelectionAction(graph) {
+ graph.viewSelection();
+ }
+
+ toggleTypesAction(graph) {
+ graph.toggleTypes();
+ }
+
+ searchInputAction(searchBar, e: KeyboardEvent) {
+ const graph = this;
+ if (e.keyCode == 13) {
+ graph.selectionHandler.clear();
+ var query = searchBar.value;
+ window.sessionStorage.setItem("lastSearch", query);
+ if (query.length == 0) return;
+
+ var reg = new RegExp(query);
+ var filterFunction = function (n) {
+ return (reg.exec(n.getDisplayLabel()) != null ||
+ (graph.state.showTypes && reg.exec(n.getDisplayType())) ||
+ (reg.exec(n.getTitle())) ||
+ reg.exec(n.opcode) != null);
+ };
+
+ const selection = graph.nodes.filter(
+ function (n, i) {
+ if ((e.ctrlKey || n.visible) && filterFunction(n)) {
+ if (e.ctrlKey) n.visible = true;
+ return true;
+ }
+ return false;
+ });
+
+ graph.selectionHandler.select(selection, true);
+ graph.connectVisibleSelectedNodes();
+ graph.updateGraphVisibility();
+ searchBar.blur();
+ graph.viewSelection();
+ }
+ e.stopPropagation();
+ }
+
+ svgKeyDown() {
+ var state = this.state;
+ var graph = this;
+
+ // Don't handle key press repetition
+ if (state.lastKeyDown !== -1) return;
+
+ var showSelectionFrontierNodes = function (inEdges, filter, select) {
+ var frontier = graph.getNodeFrontier(state.selection, inEdges, filter);
+ if (frontier != undefined && frontier.size) {
+ if (select) {
+ if (!d3.event.shiftKey) {
+ state.selection.clear();
+ }
+ state.selection.select(frontier, true);
+ }
+ graph.updateGraphVisibility();
+ }
+ allowRepetition = false;
+ }
+
+ var allowRepetition = true;
+ var eventHandled = true; // unless the below switch defaults
+ switch (d3.event.keyCode) {
+ case 49:
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ // '1'-'9'
+ showSelectionFrontierNodes(true,
+ (edge, index) => { return index == (d3.event.keyCode - 49); },
+ false);
+ break;
+ case 97:
+ case 98:
+ case 99:
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ // 'numpad 1'-'numpad 9'
+ showSelectionFrontierNodes(true,
+ (edge, index) => { return index == (d3.event.keyCode - 97); },
+ false);
+ break;
+ case 67:
+ // 'c'
+ showSelectionFrontierNodes(d3.event.altKey,
+ (edge, index) => { return edge.type == 'control'; },
+ true);
+ break;
+ case 69:
+ // 'e'
+ showSelectionFrontierNodes(d3.event.altKey,
+ (edge, index) => { return edge.type == 'effect'; },
+ true);
+ break;
+ case 79:
+ // 'o'
+ showSelectionFrontierNodes(false, undefined, false);
+ break;
+ case 73:
+ // 'i'
+ showSelectionFrontierNodes(true, undefined, false);
+ break;
+ case 65:
+ // 'a'
+ graph.selectAllNodes();
+ allowRepetition = false;
+ break;
+ case 38:
+ case 40: {
+ showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
+ break;
+ }
+ case 82:
+ // 'r'
+ if (!d3.event.ctrlKey) {
+ this.layoutAction(this);
+ } else {
+ eventHandled = false;
+ }
+ break;
+ case 83:
+ // 's'
+ graph.selectOrigins();
+ break;
+ case 191:
+ // '/'
+ document.getElementById("search-input").focus();
+ break;
+ default:
+ eventHandled = false;
+ break;
+ }
+ if (eventHandled) {
+ d3.event.preventDefault();
+ }
+ if (!allowRepetition) {
+ state.lastKeyDown = d3.event.keyCode;
+ }
+ }
+
+ svgKeyUp() {
+ this.state.lastKeyDown = -1
+ };
+
+ layoutGraph() {
+ layoutNodeGraph(this);
+ }
+
+ selectOrigins() {
+ const state = this.state;
+ const origins = [];
+ let phase = null;
+ for (const n of state.selection) {
+ if (n.origin) {
+ const node = this.nodeMap[n.origin.nodeId];
+ origins.push(node);
+ phase = n.origin.phase;
+ }
+ }
+ if (origins.length) {
+ state.selection.clear();
+ state.selection.select(origins, true);
+ if (phase) {
+ this.showPhaseByName(phase);
+ }
+ }
+ }
+
+ // call to propagate changes to graph
+ updateGraphVisibility() {
+ let graph = this;
+ let state = graph.state;
+
+ var filteredEdges = graph.edges.filter(function (e) {
+ return e.isVisible();
+ });
+ const selEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>("path").data(filteredEdges, edgeToStr);
+
+ // remove old links
+ selEdges.exit().remove();
+
+ // add new paths
+ selEdges.enter()
+ .append('path')
+ .style('marker-end', 'url(#end-arrow)')
+ .classed('hidden', function (e) {
+ return !e.isVisible();
+ })
+ .attr("id", function (edge) { return "e," + edge.stringID(); })
+ .on("click", function (edge) {
+ d3.event.stopPropagation();
+ if (!d3.event.shiftKey) {
+ graph.selectionHandler.clear();
+ }
+ graph.selectionHandler.select([edge.source, edge.target], true);
+ })
+ .attr("adjacentToHover", "false");
+
+ // Set the correct styles on all of the paths
+ selEdges.classed('value', function (e) {
+ return e.type == 'value' || e.type == 'context';
+ }).classed('control', function (e) {
+ return e.type == 'control';
+ }).classed('effect', function (e) {
+ return e.type == 'effect';
+ }).classed('frame-state', function (e) {
+ return e.type == 'frame-state';
+ }).attr('stroke-dasharray', function (e) {
+ if (e.type == 'frame-state') return "10,10";
+ return (e.type == 'effect') ? "5,5" : "";
+ });
+
+ // select existing nodes
+ const filteredNodes = graph.nodes.filter(n => n.visible);
+ const allNodes = graph.visibleNodes.selectAll<SVGGElement, GNode>("g");
+ const selNodes = allNodes.data(filteredNodes, nodeToStr);
+
+ // remove old nodes
+ selNodes.exit().remove();
+
+ // add new nodes
+ var newGs = selNodes.enter()
+ .append("g");
+
+ newGs.classed("turbonode", function (n) { return true; })
+ .classed("control", function (n) { return n.isControl(); })
+ .classed("live", function (n) { return n.isLive(); })
+ .classed("dead", function (n) { return !n.isLive(); })
+ .classed("javascript", function (n) { return n.isJavaScript(); })
+ .classed("input", function (n) { return n.isInput(); })
+ .classed("simplified", function (n) { return n.isSimplified(); })
+ .classed("machine", function (n) { return n.isMachine(); })
+ .on('mouseenter', function (node) {
+ const visibleEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>('path');
+ const adjInputEdges = visibleEdges.filter(e => { return e.target === node; });
+ const adjOutputEdges = visibleEdges.filter(e => { return e.source === node; });
+ adjInputEdges.attr('relToHover', "input");
+ adjOutputEdges.attr('relToHover', "output");
+ const adjInputNodes = adjInputEdges.data().map(e => e.source);
+ const visibleNodes = graph.visibleNodes.selectAll<SVGGElement, GNode>("g");
+ const input = visibleNodes.data<GNode>(adjInputNodes, nodeToStr)
+ .attr('relToHover', "input");
+ const adjOutputNodes = adjOutputEdges.data().map(e => e.target);
+ const output = visibleNodes.data<GNode>(adjOutputNodes, nodeToStr)
+ .attr('relToHover', "output");
+ graph.updateGraphVisibility();
+ })
+ .on('mouseleave', function (node) {
+ const visibleEdges = graph.visibleEdges.selectAll<SVGPathElement, Edge>('path');
+ const adjEdges = visibleEdges.filter(e => { return e.target === node || e.source === node; });
+ adjEdges.attr('relToHover', "none");
+ const adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
+ const visibleNodes = graph.visibleNodes.selectAll<SVGPathElement, GNode>("g");
+ const nodes = visibleNodes.data(adjNodes, nodeToStr)
+ .attr('relToHover', "none");
+ graph.updateGraphVisibility();
+ })
+ .on("click", (d) => {
+ if (!d3.event.shiftKey) graph.selectionHandler.clear();
+ graph.selectionHandler.select([d], undefined);
+ d3.event.stopPropagation();
+ })
+ .call(graph.drag)
+
+ newGs.append("rect")
+ .attr("rx", 10)
+ .attr("ry", 10)
+ .attr('width', function (d) {
+ return d.getTotalNodeWidth();
+ })
+ .attr('height', function (d) {
+ return graph.getNodeHeight(d);
+ })
+
+ function appendInputAndOutputBubbles(g, d) {
+ for (var i = 0; i < d.inputs.length; ++i) {
+ var x = d.getInputX(i);
+ var y = -DEFAULT_NODE_BUBBLE_RADIUS;
+ var s = g.append('circle')
+ .classed("filledBubbleStyle", function (c) {
+ return d.inputs[i].isVisible();
+ })
+ .classed("bubbleStyle", function (c) {
+ return !d.inputs[i].isVisible();
+ })
+ .attr("id", "ib," + d.inputs[i].stringID())
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+ .attr("transform", function (d) {
+ return "translate(" + x + "," + y + ")";
+ })
+ .on("click", function (d) {
+ var components = this.id.split(',');
+ var node = graph.nodeMap[components[3]];
+ var edge = node.inputs[components[2]];
+ var visible = !edge.isVisible();
+ node.setInputVisibility(components[2], visible);
+ d3.event.stopPropagation();
+ graph.updateGraphVisibility();
+ });
+ }
+ if (d.outputs.length != 0) {
+ var x = d.getOutputX();
+ var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS;
+ var s = g.append('circle')
+ .classed("filledBubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 2;
+ })
+ .classed("halFilledBubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 1;
+ })
+ .classed("bubbleStyle", function (c) {
+ return d.areAnyOutputsVisible() == 0;
+ })
+ .attr("id", "ob," + d.id)
+ .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
+ .attr("transform", function (d) {
+ return "translate(" + x + "," + y + ")";
+ })
+ .on("click", function (d) {
+ d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
+ d3.event.stopPropagation();
+ graph.updateGraphVisibility();
+ });
+ }
+ }
+
+ newGs.each(function (d) {
+ appendInputAndOutputBubbles(d3.select(this), d);
+ });
+
+ newGs.each(function (d) {
+ d3.select(this).append("text")
+ .classed("label", true)
+ .attr("text-anchor", "right")
+ .attr("dx", 5)
+ .attr("dy", 5)
+ .append('tspan')
+ .text(function (l) {
+ return d.getDisplayLabel();
+ })
+ .append("title")
+ .text(function (l) {
+ return d.getTitle();
+ })
+ if (d.type != undefined) {
+ d3.select(this).append("text")
+ .classed("label", true)
+ .classed("type", true)
+ .attr("text-anchor", "right")
+ .attr("dx", 5)
+ .attr("dy", d.labelbbox.height + 5)
+ .append('tspan')
+ .text(function (l) {
+ return d.getDisplayType();
+ })
+ .append("title")
+ .text(function (l) {
+ return d.getType();
+ })
+ }
+ });
+
+ const newAndOldNodes = newGs.merge(selNodes);
+
+ newAndOldNodes.select<SVGTextElement>('.type').each(function (d) {
+ this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
+ });
+
+ newAndOldNodes
+ .classed("selected", function (n) {
+ if (state.selection.isSelected(n)) return true;
+ return false;
+ })
+ .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
+ .select('rect')
+ .attr('height', function (d) { return graph.getNodeHeight(d); });
+
+ graph.visibleBubbles = d3.selectAll('circle');
+
+ graph.updateInputAndOutputBubbles();
+
+ graph.maxGraphX = graph.maxGraphNodeX;
+ selEdges.attr("d", function (edge) {
+ return edge.generatePath(graph);
+ });
+ }
+
+ getSvgViewDimensions() {
+ return [this.container.clientWidth, this.container.clientHeight];
+ }
+
+ getSvgExtent(): [[number, number], [number, number]] {
+ return [[0, 0], [this.container.clientWidth, this.container.clientHeight]];
+ }
+
+ minScale() {
+ const graph = this;
+ const dimensions = this.getSvgViewDimensions();
+ const minXScale = dimensions[0] / (2 * graph.width);
+ const minYScale = dimensions[1] / (2 * graph.height);
+ const minScale = Math.min(minXScale, minYScale);
+ this.panZoom.scaleExtent([minScale, 40]);
+ return minScale;
+ }
+
+ onresize() {
+ const trans = d3.zoomTransform(this.svg.node());
+ const ctrans = this.panZoom.constrain()(trans, this.getSvgExtent(), this.panZoom.translateExtent())
+ this.panZoom.transform(this.svg, ctrans)
+ }
+
+ toggleTypes() {
+ var graph = this;
+ graph.state.showTypes = !graph.state.showTypes;
+ var element = document.getElementById('toggle-types');
+ element.classList.toggle('button-input-toggled', graph.state.showTypes);
+ graph.updateGraphVisibility();
+ }
+
+ viewSelection() {
+ var graph = this;
+ var minX, maxX, minY, maxY;
+ var hasSelection = false;
+ graph.visibleNodes.selectAll<SVGGElement, GNode>("g").each(function (n) {
+ if (graph.state.selection.isSelected(n)) {
+ hasSelection = true;
+ minX = minX ? Math.min(minX, n.x) : n.x;
+ maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
+ n.x + n.getTotalNodeWidth();
+ minY = minY ? Math.min(minY, n.y) : n.y;
+ maxY = maxY ? Math.max(maxY, n.y + graph.getNodeHeight(n)) :
+ n.y + graph.getNodeHeight(n);
+ }
+ });
+ if (hasSelection) {
+ graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
+ maxX + NODE_INPUT_WIDTH, maxY + 60,
+ true);
+ }
+ }
+
+ viewGraphRegion(minX, minY, maxX, maxY, transition) {
+ const [width, height] = this.getSvgViewDimensions();
+ const dx = maxX - minX;
+ const dy = maxY - minY;
+ const x = (minX + maxX) / 2;
+ const y = (minY + maxY) / 2;
+ const scale = Math.min(width / (1.1 * dx), height / (1.1 * dy));
+ const transform = d3.zoomIdentity.translate(1500, 100).scale(0.75);
+ this.svg
+ .transition().duration(300).call(this.panZoom.translateTo, x, y)
+ .transition().duration(300).call(this.panZoom.scaleTo, scale)
+ .transition().duration(300).call(this.panZoom.translateTo, x, y);
+ }
+
+ viewWholeGraph() {
+ this.panZoom.scaleTo(this.svg, 0);
+ this.panZoom.translateTo(this.svg, this.minGraphX + this.width / 2, this.minGraphY + this.height / 2)
+ }
+}