Repository: incubator-htrace Updated Branches: refs/heads/master 3dc369af4 -> b65464022
HTRACE-142. Details page: Update graph Project: http://git-wip-us.apache.org/repos/asf/incubator-htrace/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-htrace/commit/b6546402 Tree: http://git-wip-us.apache.org/repos/asf/incubator-htrace/tree/b6546402 Diff: http://git-wip-us.apache.org/repos/asf/incubator-htrace/diff/b6546402 Branch: refs/heads/master Commit: b654640223289f5e35ebcb1d25de08aaecd7ff22 Parents: 3dc369a Author: Abraham Elmahrek <[email protected]> Authored: Sat Feb 28 16:30:30 2015 -0800 Committer: Abraham Elmahrek <[email protected]> Committed: Thu Apr 2 15:55:29 2015 -0700 ---------------------------------------------------------------------- htrace-core/src/web/app/setup.js | 159 ++++++------ htrace-core/src/web/app/views/graph/graph.js | 283 +++++++++++++--------- htrace-core/src/web/lib/css/main.css | 11 +- 3 files changed, 261 insertions(+), 192 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/b6546402/htrace-core/src/web/app/setup.js ---------------------------------------------------------------------- diff --git a/htrace-core/src/web/app/setup.js b/htrace-core/src/web/app/setup.js index 3c32118..adb100c 100644 --- a/htrace-core/src/web/app/setup.js +++ b/htrace-core/src/web/app/setup.js @@ -45,76 +45,77 @@ var Router = Backbone.Marionette.AppRouter.extend({ }, "search": function(query) { - var top = new app.SearchView(); - app.root.app.show(top); - - top.controls.show(new app.SearchControlsView({ - "collection": this.spansCollection - })); - top.main.show(new Backgrid.Grid({ - "collection": this.spansCollection, - "columns": [{ - "label": "Begin", - "cell": Backgrid.Cell.extend({ - className: "begin-cell", - formatter: { - fromRaw: function(rawData, model) { - var beginMs = model.get("beginTime") - return moment(beginMs).format('YYYY/MM/DD HH:mm:ss,SSS'); - }, - toRaw: function(formattedData, model) { - return formattedData // data entry not supported for this cell + app.root.app.show(new app.SearchView()); + app.root.app.currentView.controls.show( + new app.SearchControlsView({ + "collection": this.spansCollection + })); + app.root.app.currentView.main.show( + new Backgrid.Grid({ + "collection": this.spansCollection, + "columns": [{ + "label": "Begin", + "cell": Backgrid.Cell.extend({ + className: "begin-cell", + formatter: { + fromRaw: function(rawData, model) { + var beginMs = model.get("beginTime") + return moment(beginMs).format('YYYY/MM/DD HH:mm:ss,SSS'); + }, + toRaw: function(formattedData, model) { + return formattedData // data entry not supported for this cell + } } - } - }), - "editable": false, - "sortable": false - }, { - "name": "spanId", - "label": "ID", - "cell": "string", - "editable": false, - "sortable": false - }, { - "name": "processId", - "label": "processId", - "cell": "string", - "editable": false, - "sortable": false - }, { - "label": "Duration", - "cell": Backgrid.Cell.extend({ - className: "duration-cell", - formatter: { - fromRaw: function(rawData, model) { - return model.duration() + " ms" - }, - toRaw: function(formattedData, model) { - return formattedData // data entry not supported for this cell + }), + "editable": false, + "sortable": false + }, { + "name": "spanId", + "label": "ID", + "cell": "string", + "editable": false, + "sortable": false + }, { + "name": "processId", + "label": "processId", + "cell": "string", + "editable": false, + "sortable": false + }, { + "label": "Duration", + "cell": Backgrid.Cell.extend({ + className: "duration-cell", + formatter: { + fromRaw: function(rawData, model) { + return model.duration() + " ms" + }, + toRaw: function(formattedData, model) { + return formattedData // data entry not supported for this cell + } } + }), + "editable": false, + "sortable": false + }, { + "name": "description", + "label": "Description", + "cell": "string", + "editable": false, + "sortable": false + }], + "row": Backgrid.Row.extend({ + "events": { + "click": "details" + }, + "details": function() { + Backbone.history.navigate("!/spans/" + this.model.get("spanId"), {"trigger": true}); } - }), - "editable": false, - "sortable": false - }, { - "name": "description", - "label": "Description", - "cell": "string", - "editable": false, - "sortable": false - }], - "row": Backgrid.Row.extend({ - "events": { - "click": "details" - }, - "details": function() { - Backbone.history.navigate("!/spans/" + this.model.get("spanId"), {"trigger": true}); - } - }) - })); - top.pagination.show(new Backgrid.Extension.Paginator({ - collection: this.spansCollection, - })); + }) + })); + app.root.app.currentView.pagination.show( + new Backgrid.Extension.Paginator({ + collection: this.spansCollection, + })); }, "span": function(id) { @@ -127,17 +128,21 @@ var Router = Backbone.Marionette.AppRouter.extend({ return; } - var top = new app.DetailsView(); - app.root.app.show(top); - top.span.show(new app.SpanDetailsView({ - "model": span - })); - top.content.show(new app.GraphView({ + var graphView = new app.GraphView({ "collection": this.spansCollection, - "spanId": id, - "el": top.content.$el[0], - "id": "span-" + id - })); + "id": "span-graph" + }); + + graphView.on("update:span", function(d) { + app.root.app.currentView.span.show( + new app.SpanDetailsView({ + "model": d.span + })); + }); + + app.root.app.show(new app.DetailsView()); + app.root.app.currentView.content.show(graphView); + app.root.app.currentView.content.currentView.setSpanId(id); }, "swimlane": function(id, lim) { http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/b6546402/htrace-core/src/web/app/views/graph/graph.js ---------------------------------------------------------------------- diff --git a/htrace-core/src/web/app/views/graph/graph.js b/htrace-core/src/web/app/views/graph/graph.js index def16d3..7b4f89e 100644 --- a/htrace-core/src/web/app/views/graph/graph.js +++ b/htrace-core/src/web/app/views/graph/graph.js @@ -18,42 +18,43 @@ */ app.GraphView = Backbone.View.extend({ - "MAX_NODE_SIZE": 100, - "MIN_NODE_SIZE": 30, - initialize: function(options) { options = options || {}; - _.bindAll(this, "render"); - this.collection.bind('change', this.render); if (!options.id) { console.error("GraphView requires argument 'id' to uniquely identify this graph."); return; } - if (!options.el) { - console.error("GraphView requires argument 'el' to bind the graph to."); - return; - } - if (!options.spanId) { - console.error("GraphView requires argument 'spanId' as a start point."); - return; - } - this.spanId = options.spanId; - - window.force = this.force - = d3.layout.force().size([1000, 1000]) - .alpha(0.1) - .linkDistance(this.MAX_NODE_SIZE*2) - .charge(-600) - .linkDistance(150) - .linkStrength(1) - .friction(0.2) - // .gravity(0.1) + _.bindAll(this, "render"); + this.collection.bind('change', this.render); + + var links = this.links = []; + var linkTable = this.linkTable = {}; + var nodes = this.nodes = []; + var nodeTable = this.nodeTable = {}; + var force = this.force + = d3.layout.force().size([$(window).width(), $(window).height() * 3/4]) + .linkDistance($(window).height() / 5) + .charge(-120) + .gravity(0) ; + force.nodes(nodes) + .links(links); force.on("tick", function(e) { var root = d3.select("#" + options.id); + + if (!root.node()) { + return; + } + + var selectedDatum = root.select(".selected").datum(); + + // center selected node + root.select("svg").attr("width", $(root.node()).width()); + selectedDatum.x = root.select("svg").attr("width") / 2; + selectedDatum.y = root.select("svg").attr("height") / 2; // Push sources up and targets down to form a weak tree. var k = 10 * e.alpha; @@ -62,142 +63,200 @@ app.GraphView = Backbone.View.extend({ d.target.y += k; }); - // set selected node in the middle. - // var selectedNode = force.nodes()[0]; - // selectedNode.x = 500; - // selectedNode.y = 500; - var nodes = root.selectAll(".node").data(force.nodes()); nodes.select("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); nodes.select("text") - .attr("x", function(d) { return d.x; }) + .attr("x", function(d) { return d.x - this.getComputedTextLength() / 2; }) .attr("y", function(d) { return d.y; }); root.selectAll(".link").data(force.links()) - .attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }); + .attr("d", function(d) { + var start = {}, + end = {}, + angle = Math.atan2((d.target.x - d.source.x), (d.target.y - d.source.y)); + start.x = d.source.x + d.source.r * Math.sin(angle); + end.x = d.target.x - d.source.r * Math.sin(angle); + start.y = d.source.y + d.source.r * Math.cos(angle); + end.y = d.target.y - d.source.r * Math.cos(angle); + return "M" + start.x + " " + start.y + + " L" + end.x + " " + end.y; + }); }); }, - parents: function(span) { - var collection = this.collection; - return _(span.get("parents")).map(function(parentSpanId) { - collection.findWhere({ - "spanId": parentSpanId - }); - }); - }, + updateLinksAndNodes: function() { + if (!this.spanId) { + return; + } - children: function(span) { - var spanId = span.get("spanId"); - return this.collection.filter(function(model) { - return _(model.get("parents")).contains(spanId); - }); - }, + var $this = this, collection = this.collection; - linksAndNodes: function() { - var links = [], - nodes = []; var selectedSpan = this.collection.findWhere({ "spanId": this.spanId }); - var parents = this.parents(selectedSpan); - var children = this.children(selectedSpan); - - var group = 0; - var spanToNode = (function() { - var xmap = {}; - return function(span, level) { - return { - "name": span.get("description"), - "span": span + + var findChildren = function(span) { + var spanId = span.get("spanId"); + var spans = collection.filter(function(model) { + return _(model.get("parents")).contains(spanId); + }); + return _(spans).reject(function(span) { + return span == null; + }); + }; + var findParents = function(span) { + var spans = _(span.get("parents")).map(function(parentSpanId) { + return collection.findWhere({ + "spanId": parentSpanId + }); + }); + return _(spans).reject(function(span) { + return span == null; + }); + }; + var spanToNode = function(span, level) { + var table = $this.nodeTable; + if (!(span.get("spanId") in table)) { + table[span.get("spanId")] = { + "name": span.get("spanId"), + "span": span, + "level": level, + "group": 0, + "x": parseInt($this.svg.attr('width')) / 2, + "y": 250 + level * 50 }; + $this.nodes.push(table[span.get("spanId")]); } - })(); + + return table[span.get("spanId")]; + }; var createLink = function(source, target) { - return { - "source": source, - "target": target - }; + var table = $this.linkTable; + var name = source.span.get("spanId") + "-" + target.span.get("spanId"); + if (!(name in table)) { + table[name] = { + "source": source, + "target": target + }; + $this.links.push(table[name]); + } + + return table[name]; }; + var parents = [], children = []; var selectedSpanNode = spanToNode(selectedSpan, 1); - nodes.push(selectedSpanNode); + Array.prototype.push.apply(parents, findParents(selectedSpan)); _(parents).each(function(span) { - var node = spanToNode(span, 0); - nodes.push(node); - links.push(createLink(node, selectedSpanNode)); + Array.prototype.push.apply(parents, findParents(span)); + createLink(spanToNode(span, 0), selectedSpanNode) }); + Array.prototype.push.apply(children, findChildren(selectedSpan)); _(children).each(function(span) { - var node = spanToNode(span, 2); - nodes.push(node); - links.push(createLink(selectedSpanNode, node)); + Array.prototype.push.apply(children, findChildren(span)); + createLink(selectedSpanNode, spanToNode(span, 2)) }); - - return { - "links": links, - "nodes": nodes - }; }, renderLinks: function(selection) { - selection.append("line") - .attr("class", "link"); + var path = selection.enter().append("path") + .classed("link", true) + .style("marker-end", "url(#suit)"); + selection.exit().remove(); return selection; }, renderNodes: function(selection) { - var MAX_NODE_SIZE = this.MAX_NODE_SIZE, - MIN_NODE_SIZE = this.MIN_NODE_SIZE; - var g = selection.append("g").attr("class", "node"); - g.append("circle") + var $this = this; + var g = selection.enter().append("g").attr("class", "node"); + var circle = g.append("circle") .attr("r", function(d) { - var reduced = Math.log(d.span.duration()); + if (!d.radius) { + d.r = Math.log(d.span.duration()); - if (reduced > MAX_NODE_SIZE) { - return MAX_NODE_SIZE; - } + if (d.r > app.GraphView.MAX_NODE_SIZE) { + d.r = app.GraphView.MAX_NODE_SIZE; + } - if (reduced < MIN_NODE_SIZE) { - return MIN_NODE_SIZE; + if (d.r < app.GraphView.MIN_NODE_SIZE) { + d.r = app.GraphView.MIN_NODE_SIZE; + } } - return reduced; + return d.r; }); var text = g.append("text").text(function(d) { - return d.name; + return d.span.get("description"); + }); + + selection.exit().remove(); + + circle.on("click", function(d) { + $this.setSpanId(d.name); }); + + selection.classed("selected", null); + selection.filter(function(d) { + return d.span.get("spanId") == $this.spanId; + }).classed("selected", true); return selection; }, + setSpanId: function(spanId) { + var $this = this; + this.spanId = spanId; + + this.updateLinksAndNodes(); + + this.renderNodes( + this.svg.selectAll(".node") + .data(this.force.nodes(), function(d) { + return d.name; + })); + + this.renderLinks( + this.svg.selectAll(".link") + .data(this.force.links(), function(d) { + return d.source.name + "-" + d.target.name; + })); + + this.force.start(); + + Backbone.history.navigate("!/spans/" + spanId); + this.trigger("update:span", {"span": this.collection.findWhere({ + "spanId": spanId + })}); + }, + render: function() { - var svg = d3.select(this.$el[0]).append("svg") - .attr("height", 1000) - .attr("width", 1000) - .attr("id", this.id); - var data = this.linksAndNodes(); - - this.force - .nodes(data.nodes) - .links(data.links) - .start(); - - var link = this.renderLinks( - svg.selectAll(".link") - .data(this.force.links()) - .enter()); - - var node = this.renderNodes( - svg.selectAll(".node") - .data(this.force.nodes()) - .enter()); + this.svg = d3.select(this.$el[0]).append("svg"); + this.svg.attr("height", 500) + .attr("width", $(window).width()) + .attr("id", this.id); + + // Arrows + this.svg.append("defs").selectAll("marker") + .data(["suit", "licensing", "resolved"]) + .enter().append("marker") + .attr("id", function(d) { return d; }) + .attr("viewBox", "0 -5 10 10") + .attr("refX", 25) + .attr("refY", 0) + .attr("markerWidth", 6) + .attr("markerHeight", 6) + .attr("orient", "auto") + .append("path") + .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") + .style("stroke", "#4679BD") + .style("opacity", "0.6"); return this; } }); + +app.GraphView.MAX_NODE_SIZE = 150; +app.GraphView.MIN_NODE_SIZE = 50; http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/b6546402/htrace-core/src/web/lib/css/main.css ---------------------------------------------------------------------- diff --git a/htrace-core/src/web/lib/css/main.css b/htrace-core/src/web/lib/css/main.css index c0a8f3e..1875b60 100644 --- a/htrace-core/src/web/lib/css/main.css +++ b/htrace-core/src/web/lib/css/main.css @@ -29,12 +29,17 @@ li.span { /* Graph */ svg .node circle { - stroke: #006600; + stroke: #000000; stroke-width: 3; - fill: #00CC00; + fill: #D8BFD8; +} + +svg .selected circle { + stroke-width: 3; + fill: #DA70D6; } svg .link { stroke: black; stroke-width: 3; -} \ No newline at end of file +}
