diff options
Diffstat (limited to 'deps/v8/tools/turbolizer/src/graph-view.ts')
-rw-r--r-- | deps/v8/tools/turbolizer/src/graph-view.ts | 952 |
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) + } +} |