diff --git a/src/traceView/branch-navigator.js b/src/traceView/branch-navigator.js index 331ad06c..0e8d0389 100644 --- a/src/traceView/branch-navigator.js +++ b/src/traceView/branch-navigator.js @@ -323,4 +323,4 @@ export class BranchNavigator{ this.eventAggregator.publish("branchNavigatorChange", {isVisible: true}); } } -} \ No newline at end of file +} diff --git a/src/visualization/call-graph.js b/src/visualization/call-graph.js index c06949bb..534601cf 100644 --- a/src/visualization/call-graph.js +++ b/src/visualization/call-graph.js @@ -1,7 +1,7 @@ /*global d3*/ -import {Vertex} from "./vertex.js" export class CallGraph { - currentDirection = "down";// or "right" + currentDirection = "down"; // or "right" + // defines direction that graph displays (top to bottom or left to right) directionManager = { right: { nodeRenderer: function translateRight(d) { @@ -38,428 +38,368 @@ export class CallGraph { errorMessage: null }; + this.rootNode = { + name: "Program", + branch: 1, + count: 1, + parent: null, + ranges: [], + children: [], + visible: true, + pinned: true + } } - prepareFx() - { - let self = this ; - this.formatTraceFx = function makeTree(trace = this.trace) - { - if(!trace) - return ; - //the text starts at line 0 by default, plus one to match natural line numbers - let map = {} ; - let funcs = [] ; - // - funcs = self.findFuncs( trace ) ; - - for( let i = 0 ; i < funcs.length ; i++ ) - map[ funcs[i].name ] = funcs[ i ] ; //try with adjacency list - - map = self.makeMatrixList( funcs , map ) ; - // - let rootsList = [] ; //list of functions that arent called - for( let key in map ) - { - //console.log( key ) ; - if( map[ key ].parents.length === 0 ) - rootsList.push( map[ key ] ) ; - } - // - // console.log( "funcs" ) ; - // console.log( funcs ) ; - // console.log( "map" ) ; - // console.log( map ) ; - - // console.log( map[ "alpha()" ].children[ 1 ] === map[ "alpha()" ].children[ 2 ] ) ; - //turns matrix into tree - - let masterHead = new Vertex("Program", "Program"); - - // rootsList.map(function(e) { - // masterHead.children.push(e); - // }) - - // return rootsList[ 0 ] ; - return rootsList[0]; - }; - - this.renderFx = function renderFx(formattedTrace, divElement, query, queryType, aceUtils, aceMarkerManager) { - if (!formattedTrace){ - return; + prepareFx() { + let self = this; + + this.formatTraceFx = function(trace, traceHelper) { + if(!traceHelper || !traceHelper.branches) { + return null; + } + + function createBranch(template, prev, branch) { + let newBranch = {}; + newBranch.name = String(template.branch) + "/" + String(template.count); + newBranch.branch = branch; + newBranch.count = template.count; + if(prev.length === 0) { + newBranch.parent = null; } - if(query !== null && (query == undefined || query.trim() === "")) { - query = null; + else { + newBranch.parent = prev[0]; + prev[0].children.push(newBranch); } - function scrubLeaves(root, hasLeaves=0) { - if(root === undefined || root.children === undefined) { - return hasLeaves; + newBranch.ranges = [template.entry.range]; + newBranch.children = []; + newBranch.visible = true; + newBranch.pinned = false; + return newBranch; + } + + let branches = []; + for(let i = 0; i < traceHelper.branches.length; i++) { + if(traceHelper.branches[i] != undefined) { + branches.push(createBranch(traceHelper.branches[i], branches.slice(-1), traceHelper.branches[i].branch)); } + } + return branches; + } - let children = root.children; + this.renderFx = function renderFx(branches, divElement, query, queryType, aceUtils, aceMarkerManager) { + if (!branches || branches.length === 0) { + return; + } - for(let i = 0; i < children.length; i++) { - if(children[i].children.length === 0 && !children[i].name.includes(query)) { - hasLeaves++; - root.children.splice(i, 1); - } - hasLeaves += scrubLeaves(children[i], hasLeaves); - } - return hasLeaves; - } + console.log(branches) - function scrubTree(root) { - while(scrubLeaves(root)) { + self.removeUnpinned(); + self.addToGraph(branches);//self.createBranchHierarchy(branches)); - }; - } + if(query == undefined || query.trim() === "") { + query = null; + } - function makeQuery() { - scrubTree(formattedTrace); - } + // sets visibility of nodes that match or have a direct path to a match to true, false otherwise + function makeQuery(root=self.rootNode) { + let children = root.children; - if(query !== null && queryType === "functions") { - makeQuery(); + for(let i = 0; i < children.length; i++) { + if(children[i].name.includes(query)) { + let currentNode = children[i]; + while(currentNode.parent) { + currentNode.visible = true; + currentNode = currentNode.parent; + } + } + else { + children[i].visible = false; + } + makeQuery(children[i]); } + } + + if(query !== null && queryType === "functions") { + makeQuery(); + } - d3.select(divElement).html(""); + d3.select(divElement).html(""); - let margin = {top: 20, right: 20, bottom: 30, left: 40}, - width = 400 - margin.left - margin.right, - height = 250 - margin.top - margin.bottom; + let width = $("#right-splitter").width(); + let height = $(".tab-content").height(); - let rectWidth = 100, - rectHeight = 40; + let rectWidth = 100; + let rectHeight = 40; - let tree = d3.tree() - .nodeSize([160, 200]); + let tree = d3.tree() + .nodeSize([160, 80]); - let diagonal = self.directionManager[self.currentDirection].linkRenderer; + let diagonal = self.directionManager[self.currentDirection].linkRenderer; - let nodeRenderer = self.directionManager[self.currentDirection].nodeRenderer; + let nodeRenderer = self.directionManager[self.currentDirection].nodeRenderer; - d3.select(divElement).select("svg").remove(); + d3.select(divElement).select("svg").remove(); - let svg = d3.select(divElement).append("svg") - .attr("width", width) - .attr("height", height) - .attr("position","relative") - .call(d3.zoom() + let svg = d3.select(divElement).append("svg") + .attr("width", width) + .attr("height", height) + .attr("position","relative") + .call(d3.zoom() + .scaleExtent([0.3, 2]) .on("zoom", function () { svg.attr("transform", function() { - let devent = d3.event.transform; - return "translate(" + devent.x + ", " + devent.y + ") scale(" + devent.k +")"; + let d3event = d3.event.transform; + return "translate(" + d3event.x + ", " + d3event.y + ") scale(" + d3event.k +")"; }); })) - .append("g"); - - svg.attr("transform","translate(100,0)"); - - let root = d3.hierarchy(formattedTrace), - nodes = root.descendants(), - links = root.descendants().slice(1); - - tree(root); - let link = svg.selectAll(".link") - .data(links) - .enter() - .append("g") - .attr("class", "link"); - - link.append("line") - .attr("x1", function(d) { return d.parent.x; }) - .attr("y1", function(d) { return !d.parent.data.name.includes(query) ? d.parent.y + rectHeight/2 : d.parent.y + rectHeight; }) - .attr("x2", function(d) { return d.x; }) - .attr("y2", function(d) { return !d.data.name.includes(query) ? d.y + rectHeight/2 : d.y; }) - .style("fill","none") - .style("stroke","#ccc") - .style("stroke-width","1.5px"); - - // link.append("text") - // .attr("class","num_text") - // .attr("x", function(d) { - // return (d.x + d.parent.x)/2; - // }) - // .attr("y", function(d) { - // return (d.y + d.parent.y + rectHeight)/2; - // }) - // .attr("text-anchor", "middle") - // .text(function (d) { - // return d.data.childCalls;//Math.floor((Math.random() * 10) + 1); - // }) - // .style("font","10px sans-serif"); - - function showHoverText(d) { - d3.select(this).append("text") - .attr("class", "hover") - .attr("transform", function(d) { - return "translate(5, -5)"; - }) - .text(d.data.name); - highlight(d); - } - - function hideHoverText(d) { - d3.select(this).select("text.hover").remove(); - unhighlight(d); - } - - function highlight(d) { - aceUtils.updateAceMarkers(aceMarkerManager, d.data.ranges); - } - - function unhighlight(d) { - aceUtils.updateAceMarkers(aceMarkerManager, []); - } + .append("g"); + + // resize svg upon window resize + $(window).resize(function() { + width = $("#right-splitter").width(); + height = $(".tab-content").height(); + d3.select(divElement).select("svg").attr("width", width); + d3.select(divElement).select("svg").attr("height", height); + centerNodes(); + }); + + let root = d3.hierarchy(self.rootNode); + let nodes = root.descendants(); + let links = root.descendants().slice(1); + + tree(root); + let link = svg.selectAll(".link") + .data(links) + .enter() + .append("g") + .attr("class", "link"); + + link.append("line") + .attr("x1", function(d) { return d.parent.x; }) + .attr("y1", function(d) { return !d.parent.data.name.includes(query) ? d.parent.y + rectHeight/2 : d.parent.y + rectHeight; }) + .attr("x2", function(d) { return d.x; }) + .attr("y2", function(d) { return !d.data.name.includes(query) ? d.y + rectHeight/2 : d.y; }) + .style("fill","none") + .style("stroke","#ccc") + .style("stroke-width","1.5px"); + + // displays name of node when hovered + function showHoverText(d) { + d3.select(this).append("text") + .attr("class", "hover") + .attr("transform", function(d) { + return "translate(5, -5)"; + }) + .text(d.data.name); + highlight(d); + } - // console.log('nodes') - // console.log(node) - let node = svg.selectAll(".node") - .data(nodes) - .enter().append("g"); + function hideHoverText(d) { + d3.select(this).select("text.hover").remove(); + unhighlight(d); + } - let multiParents = node.filter(function (d, i) { - return d.data.parents.length > 1; - }); + function highlight(d) { + aceUtils.updateAceMarkers(aceMarkerManager, d.data.ranges); + } - let parentPairs = []; - - multiParents.each(function(d) { - for(let i = 1; i < d.data.parents.length; i++) { - let p; - node.filter(function (d2, i2) { return d2.data.id === d.data.parents[i].id; }).each(function(pNode) { - p = pNode; - }) - parentPairs.push({ - parent: p, - child: d - }); - } - }); + function unhighlight(d) { + aceUtils.updateAceMarkers(aceMarkerManager, []); + } - parentPairs.forEach(function(multiPair) { - link.append("line") - .attr("class", "additionalParentLink") - .attr("x1", multiPair.parent.x) - .attr("y1", !multiPair.parent.data.name.includes(query) ? multiPair.parent.y + rectHeight/2 : multiPair.parent.y + rectHeight ) - .attr("x2", multiPair.child.x) - .attr("y2", !multiPair.child.data.name.includes(query) ? multiPair.child.y + rectHeight/2 : multiPair.child.y) - .style("fill","none") - .style("stroke","#ccc") - .style("shape-rendering", "geometricPrecision") - .style("stroke-width","1.5px") - }) - - node.attr("class", "node") - .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); }) - .attr("transform", nodeRenderer) - .style("font","10px sans-serif"); - - let filteredNodes = node.filter(function(d, i) { - if(queryType === "functions") { - return query === null || d.data.name.includes(query) || i === 0; - } - else { - return true; // TODO support other query types - } - }); + let node = svg.selectAll(".node") + .data(nodes) + .enter().append("g"); - filteredNodes.append("rect") - .attr("width", rectWidth) - .attr("height", rectHeight) - .attr("transform", "translate(" + (-1 * rectWidth/2) + ",0)") - .style("fill","#fff") - .style("stroke","steelblue") - .style("stroke-width","1.5px"); - - let regNodes = node.filter(function(d, i) { - if(queryType === "functions") { - return query !== null && i !== 0 && !d.data.name.includes(query); - } - else { - return false; // TODO support other query types - } - }); + node.attr("class", "node") + .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); }) + .attr("transform", nodeRenderer) + .style("font","10px sans-serif"); - regNodes.on("mouseover", showHoverText) - .on("mouseout", hideHoverText); + // remove nodes of visibility false + if(query !== null) { + node = node.filter(function(d, i) { + if(queryType === "functions") { + return d.data.visible === true; + } + else { + return true; // TODO support other query types + } + }); + } - filteredNodes.on("mouseover",highlight) - .on("mouseout", unhighlight); + // selection of nodes that match query + let matchedNodes = node.filter(function(d, i) { + if(queryType === "functions") { + return query === null || d.data.name.includes(query) || i === 0; + } + else { + return true; // TODO support other query types + } + }); + + matchedNodes.append("rect") + .attr("width", rectWidth) + .attr("height", rectHeight) + .attr("transform", "translate(" + (-1 * rectWidth/2) + ",0)") + .style("fill","#fff") + .style("stroke","steelblue") + .style("stroke-width","1.5px"); + + // selection of nodes that do not match query, but have a direct path to a match + let regNodes = node.filter(function(d, i) { + if(queryType === "functions") { + return query !== null && i !== 0 && !d.data.name.includes(query); + } + else { + return false; // TODO support other query types + } + }); + + regNodes.on("mouseover", showHoverText) + .on("mouseout", hideHoverText); + + matchedNodes.on("mouseover",highlight) + .on("mouseout", unhighlight); + + regNodes.append("circle") + .attr("r", 6) + .attr("transform", "translate(0," + rectHeight/2 + ")"); + + matchedNodes.append("text") + .attr("dy", 14.5) + .attr("text-anchor", "middle") + .text(function(d) { return d.data.name; }); + + // for all nodes, sets fill of pin to blue if node is pinned, white otherwise + function updatePins() { + matchedNodes.selectAll("circle").remove(); + matchedNodes.filter(function(d) { + return d.data.pinned === false; + }) + .append("circle") + .attr("r", 4) + .on("click", function(d) { + self.togglePinOn(d.data); + updatePins(); + }) + .style("stroke", "steelblue") + .style("stroke-width", 2) + .style("fill", "white"); + + matchedNodes.filter(function(d) { + return d.data.pinned === true; + }) + .append("circle") + .attr("r", 4) + .on("click", function(d) { + self.togglePinOff(d.data); + updatePins(); + }) + .style("stroke", "steelblue") + .style("stroke-width", 2) + .style("fill", "steelblue"); + centerNodes(); + } - regNodes.append("circle") - .attr("r", 6) - .attr("transform", "translate(0," + rectHeight/2 + ")"); + updatePins(); - filteredNodes.append("text") - .attr("dy", 22.5) - .attr("text-anchor", "middle") - .text(function(d) { return d.data.name; }); - } + // centers all content within svg + function centerNodes() { + svg.selectAll(".node").selectAll("*").attr("transform","translate(" + (width/2 - rectWidth/2) + ",5)"); + svg.selectAll(".node").selectAll("text").attr("transform","translate(" + width/2 + ",5)"); + svg.selectAll(".link").attr("transform","translate(" + width/2 + ",5)"); + } + } + } - // update(); - d3.select(self.frameElement).style("height", 200 + "px"); + // creates hierarchy from array, for any given node, the following node is its child + createBranchHierarchy(branches) { + branches[branches.length-1].parent = branches[branches.length-2]; + for(let i = 0; i < branches.length-1; i++) { + branches[i].children = [branches[i+1]]; + if(i !== 0) { + branches[i].parent = branches[i-1]; + } + } + return branches; } + // new branch is added to the graph TODO: slice off top if branches are equal + addToGraph(branches) { + console.log(this.rootNode.children) + let targetParent = this.rootNode; + let currentChildren; + for(let i = 0; i < branches.length; i++) { + currentChildren = branches.splice(i); + if(!this.branchExists(currentChildren[0])) { + let connectedParent = this.branchExists(targetParent); + currentChildren[0].parent = connectedParent; + connectedParent.children.push(currentChildren[0]); + return; + } + targetParent = currentChildren[0]; + } + } - findFuncs( trace ) - { - let self = this ; - let funcs = [] - let doesFuncExist = {} ; - let doesCallExist = {} ; - let lastBlockRange = null ; - let callfuncs = []; - // - for( let index = 1 ; index < trace.timeline.length - 1 ; index++ ) //precomputes all the funcs - { - let step = self.scrubStep( trace.timeline[ index ] ) ; - // - switch( step.type ) - { - case "BlockStatement" : - lastBlockRange = step.range ; - break ; - // - case "FunctionData" : - if( !doesFuncExist[ step.id ] ) - { - if( !lastBlockRange ) - funcs.push( new Vertex( step.type , step.id , [{range:step.range}] , step.value , step.text ) ) ; - else - { - funcs.push( new Vertex( step.type , step.id , [{range:lastBlockRange}] , step.value , step.text ) ) ; - lastBlockRange = null ; - } - doesFuncExist[ step.id ] = true ; - } - else - { + branchExists(branch) { + let found = null; + let self = this; + function search(currentBranch=self.rootNode) { + console.log(self.rootNode.children) + if(self.areBranchesEqual(branch, currentBranch)) { + found = currentBranch; + return; + } + for(let i = 0; i < currentBranch.children.length; i++) { + console.log("eyyy") + search(currentBranch.children[i]); + } + } + search(); + return found; + } - } - break ; - // - case "CallExpression" : - let found = false; - for(let i = 0; i < callfuncs.length; i++) { - if(callfuncs[i] === step.id) { - found = true; - } - } - if( !found || step.id.includes(".")) { - let currentVertex = new Vertex( step.type , step.id , [{range:step.range}] , null , step.text ); - funcs.push( currentVertex ) ; - callfuncs.push(step.id) - } - else { - for(let i = 0; i < funcs.length; i++) { - if(funcs[i].name === step.id) { - funcs[i].ranges.push({range:step.range}) - } - } - } + // all nodes that are not pinned to the graph are removed + removeUnpinned(currentBranch=this.rootNode) { + for(let i = 0; i < currentBranch.children.length; i++) { + if(!currentBranch.children[i].pinned) { + currentBranch.children.splice(i, 1); + } + this.removeUnpinned(currentBranch.children[i]); + } + } - default: {} - - } - } - return funcs ; - } - - makeMatrixList( funcs , map ) - { - // console.log(map) - let self = this ; - for( let index1 = 0 ; index1 < funcs.length ; index1++ ) - { - for( let index2 = 0 ; index2 < funcs.length ; index2++ ) - { - - if( funcs[ index2 ].type === "FunctionData" ) - { - - if( index1 !== index2 && self.isRangeInRange( funcs[ index1 ].ranges[0].range , funcs[ index2 ].ranges[0].range ) ) - { - let mom = funcs[ index2 ].name ; - let child = funcs[ index1 ].name ; - - if( !map[ mom ].childCalls[ child ] ) - { - map[ mom ].children.push( map[ child ] ) ; - if( funcs[ index1 ].type !== "CallExpression" ) - map[ child ].parents.push( map[ mom ] ) ; - // - map[ mom ].childCalls[ child ] = [ "testing" ] ; - } - else - { - map[ mom ].childCalls[ child ].push( "testing" ) ; - } - - } - // - // if( index1 !== index2 && funcs[index1].type === "CallExpression" - // && funcs[index1].text.indexOf( funcs[index2].name.replace( /[()""]/g , "" ) ) ) - // { - // let mom = funcs[ index2 ].name ; - // let child = funcs[ index1 ].name ; - // - // console.log( "callback found" ) ; - // map[ mom ].children.push( map[ child ] ) ; - // map[ child ].parents.push( map[ mom ] ) ; - // map[ child ].isCallback = true ; - // } - } - } - } - return map ; - } - - // - //helpers - // - - scrubStep( step ) - { - if( step !== null ) - { - if( step.text !== null ) - { - step.text = step.text.replace( /"/g , "" ) ; //scrubs for " - } - if( step.id !== null ) - { - step.id = step.id.replace( /[()""]/g , "" ) + "()" ; - - } - if( step.type !== null ) - { - step.type = step.type.replace( /"/g , "" ) ; - } - } - return step ; - } - - isRangeInRange(isRange, inRange) //be careful here! - { - if( isRange.start.row > inRange.start.row && isRange.end.row < inRange.end.row ) - return true ; - - if( isRange.start.row === inRange.start.row || isRange.end.row === inRange.end.row ) - { - if( isRange.start.row === inRange.start.row ) - if( isRange.start.column < inRange.start.column ) - return false ; - if( isRange.end.row === inRange.end.row ) - if( isRange.end.column > inRange.end.column ) - return false ; - return true ; - } - - return false ; - } + // branchToArray(branch) { + // let array = []; + // let currentBranch = branch; + // while(currentBranch.children.length) { + // array.push(currentBranch.children[0]); + // currentBranch = currentBranch.children[0]; + // } + // return array; + // } + + // pins a node and all of its direct ancestors + togglePinOn(branch) { + let currentBranch = branch; + while(currentBranch.parent) { + currentBranch.pinned = true; + currentBranch = currentBranch.parent; + } + } + // unpins a node and all of its descendants + togglePinOff(currentBranch) { + currentBranch.pinned = false; + for(let i = 0; i < currentBranch.children.length; i++) { + this.togglePinOff(currentBranch.children[i]); + } + } + areBranchesEqual(branch1, branch2) { + return branch1.name === branch2.name; } +} diff --git a/src/visualization/vertex.js b/src/visualization/vertex.js deleted file mode 100644 index 005f41ef..00000000 --- a/src/visualization/vertex.js +++ /dev/null @@ -1,15 +0,0 @@ -export class Vertex -{ - constructor( type , name , ranges , values , text ) - { - this.type = type ; - this.name = name ; - this.ranges = ranges ; - this.values = values ; - this.parents = [] ; - this.children = [] ; - this.childCalls = []; - this.isCallback = false ; - this.text = text ; - } -} diff --git a/src/visualization/visualization.js b/src/visualization/visualization.js index 34cb29a1..83a477c6 100644 --- a/src/visualization/visualization.js +++ b/src/visualization/visualization.js @@ -3,7 +3,7 @@ export class Visualization { constructor(index, eventAggregator, config) { this.id = "seecoderun-visualization-"+ index; this.buttonId = "seecoderun-visualization-"+ index+"-button"; - this.contentId = "seecoderun-visualization-"+ index+"-content"; + this.contentId = "seePanelBody"; // "seecoderun-visualization-"+ index+"-content"; this.eventAggregator = eventAggregator; this.styleClass = config.config.styleClass; this.title = config.config.title; @@ -15,6 +15,7 @@ export class Visualization { this.hasError = false; this.requestSelectionRange = this.getSelectionRange; this.traceHelper = null; + this.query = null; this.queryType = null; @@ -32,7 +33,7 @@ export class Visualization { if(!this.trace){ console.log(`No trace found when rendering visualization #${this.id}`); } - let formattedTrace = this.formatTrace(this.trace); + let formattedTrace = this.formatTrace(this.trace, this.traceHelper); this.render(formattedTrace, `#${this.contentId}`, this.query, this.queryType, this.aceUtils, this.aceMarkerManager); } @@ -58,6 +59,16 @@ export class Visualization { this.renderVisualization(); }); + ea.subscribe('traceNavigationChange', payload => { + let self = this; + console.log(payload) + //without timeout, at least one value of attribute "branch" in the payload is undefined + setTimeout( function(){ + this.traceHelper = payload; + self.renderVisualization(); + }, 1000) + }); + ea.publish('searchBoxStateRequest'); }