Author: yusaku
Date: Wed Jan 30 00:52:50 2013
New Revision: 1440231
URL: http://svn.apache.org/viewvc?rev=1440231&view=rev
Log:
AMBARI-1268. Improve DAG UI. (billie via yusaku)
Modified:
incubator/ambari/trunk/CHANGES.txt
incubator/ambari/trunk/ambari-web/app/views/main/apps/item/dag_view.js
incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js
incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css
Modified: incubator/ambari/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/incubator/ambari/trunk/CHANGES.txt?rev=1440231&r1=1440230&r2=1440231&view=diff
==============================================================================
--- incubator/ambari/trunk/CHANGES.txt (original)
+++ incubator/ambari/trunk/CHANGES.txt Wed Jan 30 00:52:50 2013
@@ -36,6 +36,8 @@ Trunk (unreleased changes):
IMPROVEMENTS
+ AMBARI-1268. Improve DAG UI. (billie via yusaku)
+
AMBARI-1289. App page: remove old ode and fix test mode. (srimanth via
yusaku)
Modified: incubator/ambari/trunk/ambari-web/app/views/main/apps/item/dag_view.js
URL:
http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/main/apps/item/dag_view.js?rev=1440231&r1=1440230&r2=1440231&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/main/apps/item/dag_view.js
(original)
+++ incubator/ambari/trunk/ambari-web/app/views/main/apps/item/dag_view.js Wed
Jan 30 00:52:50 2013
@@ -44,7 +44,9 @@ App.MainAppsItemDagView = Em.View.extend
'status' : item.get('status') == 'SUCCESS',
'info' : [],
'input' : item.get('input'),
- 'output' : item.get('output')
+ 'output' : item.get('output'),
+ 'submitTime' : item.get('submit_time'),
+ 'elapsedTime' : item.get('elapsed_time')
})
});
return result;
@@ -130,9 +132,8 @@ App.MainAppsItemDagView = Em.View.extend
var dagSchema = this.get('controller.content.workflowContext');
var jobs = this.get('jobs');
this.resizeModal();
- var graph = new DagViewer(false, 'dag_viewer')
- .setPhysicalParametrs(this.$().width(), 300, -800, 0.01)
+ var graph = new DagViewer('dag_viewer')
.setData(dagSchema, jobs)
- .drawDag(10, 20, 100);
+ .drawDag(this.$().width(), 300, 20);
}
});
Modified:
incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js
URL:
http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js?rev=1440231&r1=1440230&r2=1440231&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js
(original)
+++ incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js
Wed Jan 30 00:52:50 2013
@@ -1,390 +1,54 @@
/*
- * D3 Sankey diagram
- * another type to display graph
- */
-d3.sankey = function () {
- var sankey = {},
- nodeWidth = 24,
- nodePadding = 8,
- size = [1, 1],
- nodes = [],
- links = [],
- overlapLinksAtSources = false,
- overlapLinksAtTargets = false,
- minValue = 1;
-
- sankey.nodeWidth = function (_) {
- if (!arguments.length) return nodeWidth;
- nodeWidth = +_;
- return sankey;
- };
-
- sankey.nodePadding = function (_) {
- if (!arguments.length) return nodePadding;
- nodePadding = +_;
- return sankey;
- };
-
- sankey.nodes = function (_) {
- if (!arguments.length) return nodes;
- nodes = _;
- return sankey;
- };
-
- sankey.links = function (_) {
- if (!arguments.length) return links;
- links = _;
- return sankey;
- };
-
- sankey.size = function (_) {
- if (!arguments.length) return size;
- size = _;
- return sankey;
- };
-
- sankey.overlapLinksAtSources = function (_) {
- if (!arguments.length) return overlapLinksAtSources;
- overlapLinksAtSources = _;
- return sankey;
- };
-
- sankey.overlapLinksAtTargets = function (_) {
- if (!arguments.length) return overlapLinksAtTargets;
- overlapLinksAtTargets = _;
- return sankey;
- };
-
- sankey.minValue = function (_) {
- if (!arguments.length) return minValue;
- minValue = _;
- return sankey;
- };
-
- sankey.layout = function (iterations) {
- computeNodeLinks();
- computeNodeValues();
- computeNodeBreadths();
- computeNodeDepths(iterations);
- computeLinkDepths();
- return sankey;
- };
-
- sankey.relayout = function () {
- computeLinkDepths();
- return sankey;
- };
-
- sankey.link = function () {
- var curvature = .5;
-
- function link(d) {
- var x0 = d.source.x + d.source.dx,
- x1 = d.target.x,
- xi = d3.interpolateNumber(x0, x1),
- x2 = xi(curvature),
- x3 = xi(1 - curvature),
- y0 = d.source.y + (overlapLinksAtSources ? 0 : d.sy) + d.dy / 2,
- y1 = d.target.y + (overlapLinksAtTargets ? 0 : d.ty) + d.dy / 2;
- return "M" + x0 + "," + y0
- + "C" + x2 + "," + y0
- + " " + x3 + "," + y1
- + " " + x1 + "," + y1;
- }
-
- link.curvature = function (_) {
- if (!arguments.length) return curvature;
- curvature = +_;
- return link;
- };
-
- return link;
- };
-
- // Populate the sourceLinks and targetLinks for each node.
- // Also, if the source and target are not objects, assume they are indices.
- function computeNodeLinks() {
- nodes.forEach(function (node) {
- node.sourceLinks = [];
- node.targetLinks = [];
- });
- links.forEach(function (link) {
- var source = link.source,
- target = link.target;
- if (typeof source === "number") source = link.source =
nodes[link.source];
- if (typeof target === "number") target = link.target =
nodes[link.target];
- source.sourceLinks.push(link);
- target.targetLinks.push(link);
- if ("value" in link)
- link.value = Math.max(link.value, minValue);
- else
- link.value = minValue;
- });
- }
-
- // Compute the value (size) of each node by summing the associated links.
- function computeNodeValues() {
- nodes.forEach(function (node) {
- if ("value" in node)
- node.value = Math.max(node.value, minValue);
- else
- node.value = minValue;
- if (node.sourceLinks.length > 0) {
- if (overlapLinksAtSources)
- node.value = Math.max(node.value, d3.max(node.sourceLinks, value));
- else
- node.value = Math.max(node.value, d3.sum(node.sourceLinks, value));
- }
- if (node.targetLinks.length > 0) {
- if (overlapLinksAtTargets)
- node.value = Math.max(node.value, d3.max(node.targetLinks, value));
- else
- node.value = Math.max(node.value, d3.sum(node.targetLinks, value));
- }
- });
- }
-
- // Iteratively assign the breadth (x-position) for each node.
- // Nodes are assigned the maximum breadth of incoming neighbors plus one;
- // nodes with no incoming links are assigned breadth zero, while
- // nodes with no outgoing links are assigned the maximum breadth.
- function computeNodeBreadths() {
- var remainingNodes = nodes,
- nextNodes,
- x = 0;
-
- while (remainingNodes.length) {
- nextNodes = [];
- remainingNodes.forEach(function (node) {
- node.x = x;
- node.dx = nodeWidth;
- node.sourceLinks.forEach(function (link) {
- nextNodes.push(link.target);
- });
- });
- remainingNodes = nextNodes;
- ++x;
- }
-
- //
- moveSinksRight(x);
- scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
- }
-
- function moveSourcesRight() {
- nodes.forEach(function (node) {
- if (!node.targetLinks.length) {
- node.x = d3.min(node.sourceLinks, function (d) {
- return d.target.x;
- }) - 1;
- }
- });
- }
-
- function moveSinksRight(x) {
- nodes.forEach(function (node) {
- if (!node.sourceLinks.length) {
- node.x = x - 1;
- }
- });
- }
-
- function scaleNodeBreadths(kx) {
- nodes.forEach(function (node) {
- node.x *= kx;
- });
- }
-
- function computeNodeDepths(iterations) {
- var nodesByBreadth = d3.nest()
- .key(function (d) {
- return d.x;
- })
- .sortKeys(d3.ascending)
- .entries(nodes)
- .map(function (d) {
- return d.values;
- });
-
- //
- initializeNodeDepth();
- resolveCollisions();
- for (var alpha = 1; iterations > 0; --iterations) {
- relaxRightToLeft(alpha *= .99);
- resolveCollisions();
- relaxLeftToRight(alpha);
- resolveCollisions();
- }
-
- function initializeNodeDepth() {
- var ky = d3.min(nodesByBreadth, function (nodes) {
- return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes,
value);
- });
-
- nodesByBreadth.forEach(function (nodes) {
- nodes.forEach(function (node, i) {
- node.y = i;
- node.dy = node.value * ky;
- });
- });
-
- links.forEach(function (link) {
- link.dy = link.value * ky;
- });
- }
-
- function relaxLeftToRight(alpha) {
- nodesByBreadth.forEach(function (nodes, breadth) {
- nodes.forEach(function (node) {
- if (node.targetLinks.length) {
- var y = d3.sum(node.targetLinks, weightedSource) /
d3.sum(node.targetLinks, value);
- node.y += (y - center(node)) * alpha;
- }
- });
- });
-
- function weightedSource(link) {
- return center(link.source) * link.value;
- }
- }
-
- function relaxRightToLeft(alpha) {
- nodesByBreadth.slice().reverse().forEach(function (nodes) {
- nodes.forEach(function (node) {
- if (node.sourceLinks.length) {
- var y = d3.sum(node.sourceLinks, weightedTarget) /
d3.sum(node.sourceLinks, value);
- node.y += (y - center(node)) * alpha;
- }
- });
- });
-
- function weightedTarget(link) {
- return center(link.target) * link.value;
- }
- }
-
- function resolveCollisions() {
- nodesByBreadth.forEach(function (nodes) {
- var node,
- dy,
- y0 = 0,
- n = nodes.length,
- i;
-
- // Push any overlapping nodes down.
- nodes.sort(ascendingDepth);
- for (i = 0; i < n; ++i) {
- node = nodes[i];
- dy = y0 - node.y;
- if (dy > 0) node.y += dy;
- y0 = node.y + node.dy + nodePadding;
- }
-
- // If the bottommost node goes outside the bounds, push it back up.
- dy = y0 - nodePadding - size[1];
- if (dy > 0) {
- y0 = node.y -= dy;
-
- // Push any overlapping nodes back up.
- for (i = n - 2; i >= 0; --i) {
- node = nodes[i];
- dy = node.y + node.dy + nodePadding - y0;
- if (dy > 0) node.y -= dy;
- y0 = node.y;
- }
- }
- });
- }
-
- function ascendingDepth(a, b) {
- return a.y - b.y;
- }
- }
-
- function computeLinkDepths() {
- nodes.forEach(function (node) {
- node.sourceLinks.sort(ascendingTargetDepth);
- node.targetLinks.sort(ascendingSourceDepth);
- });
- nodes.forEach(function (node) {
- var sy = 0, ty = 0;
- node.sourceLinks.forEach(function (link) {
- link.sy = sy;
- sy += link.dy;
- });
- node.targetLinks.forEach(function (link) {
- link.ty = ty;
- ty += link.dy;
- });
- });
-
- function ascendingSourceDepth(a, b) {
- return a.source.y - b.source.y;
- }
-
- function ascendingTargetDepth(a, b) {
- return a.target.y - b.target.y;
- }
- }
-
- function center(node) {
- return node.y + node.dy / 2;
- }
-
- function value(link) {
- return link.value;
- }
-
- return sankey;
-};
-/*
* Example usage:
*
- * var dv = new DagViewer(false,'pig_5')
- * .setPhysicalParametrs(width,height[,charge,gravity])
- * .setData(dagSchema [,jobsData])
- * .drawDag([nodeSize,largeNodeSize,linkDistance]);
+ * var dv = new DagViewer('pig_5')
+ * .setData(workflowData,jobsData)
+ * .drawDag(svgWidth,svgHeight,nodeHeight);
*/
-function DagViewer(type, domId) {
- // initialize variables and force layout
+function DagViewer(domId) {
+ // initialize variables
this._nodes = new Array();
this._links = new Array();
this._numNodes = 0;
- this._type = type;
this._id = domId;
}
-DagViewer.prototype.setPhysicalParametrs = function (w, h, charge, gravity) {
- this._w = w;
- this._h = h;
- this._gravity = gravity || 0.1;
- this._charge = charge || -1000;
- this._force = d3.layout.force()
- .size([w, h])
- .gravity(this._gravity)
- .charge(this._charge);
- return this;
-}
-//set workflow schema
+// set workflow schema and job data
DagViewer.prototype.setData = function (wfData, jobData) {
// create map from entity names to nodes
var existingNodes = new Array();
var jobData = (jobData) ? jobData : new Array();
+ var minStartTime = 0;
+ if (jobData.length > 0)
+ minStartTime = jobData[0].submitTime;
+ var maxFinishTime = 0;
// iterate through job data
for (var i = 0; i < jobData.length; i++) {
+ minStartTime = Math.min(minStartTime, jobData[i].submitTime);
+ maxFinishTime = Math.max(maxFinishTime, jobData[i].submitTime +
jobData[i].elapsedTime);
this._addNode(existingNodes, jobData[i].entityName, jobData[i]);
}
+ this._minStartTime = minStartTime;
+ this._maxFinishTime = maxFinishTime;
var dag = eval('(' + wfData + ')').dag;
+ this._sourceMarker = new Array();
+ this._targetMarker = new Array();
+ this._sourceMap = new Array();
// for each source node in the context, create links between it and its
target nodes
for (var source in dag) {
- var sourceNode = this._getNode(source, existingNodes);
+ var sourceNode = null;
+ if (source in existingNodes)
+ sourceNode = existingNodes[source];
for (var i = 0; i < dag[source].length; i++) {
- var targetNode = this._getNode(dag[source][i], existingNodes);
+ var targetNode = null;
+ if (dag[source][i] in existingNodes)
+ targetNode = existingNodes[dag[source][i]];
this._addLink(sourceNode, targetNode);
}
}
return this;
}
+
// add a node to the nodes array and to a provided map of entity names to nodes
DagViewer.prototype._addNode = function (existingNodes, entityName, node) {
existingNodes[entityName] = node;
@@ -394,272 +58,284 @@ DagViewer.prototype._addNode = function
// add a link between sourceNode and targetNode
DagViewer.prototype._addLink = function (sourceNode, targetNode) {
+ // if source or target is null, add marker indicating unsubmitted job and
return
+ if (sourceNode==null) {
+ if (targetNode==null)
+ return;
+ this._sourceMarker.push(targetNode);
+ return;
+ }
+ if (targetNode==null) {
+ this._targetMarker.push(sourceNode);
+ return;
+ }
+ // add link between nodes
var status = false;
if (sourceNode.status && targetNode.status)
status = true;
this._links.push({"source":sourceNode, "target":targetNode, "status":status,
"value":sourceNode.output});
+ // add source to map of targets to sources
+ if (!(targetNode.name in this._sourceMap))
+ this._sourceMap[targetNode.name] = new Array();
+ this._sourceMap[targetNode.name].push(sourceNode);
}
-// get the node for an entity name, or add it if it doesn't exist
-// called after job nodes have all been added
-DagViewer.prototype._getNode = function (entityName, existingNodes) {
- if (!(entityName in existingNodes))
- this._addNode(existingNodes, entityName, { "name":entityName,
"status":false, "input":1, "output":1});
- return existingNodes[entityName];
-}
// display the graph
-DagViewer.prototype.drawDag = function (nodeSize, largeNodeSize, linkDistance)
{
- this._nodeSize = nodeSize || 18;
- this._largeNodeSize = largeNodeSize || 30;
- this._linkDistance = linkDistance || 100;
- // add new display to specified div
- this._svg = d3.select("div#" + this._id).append("svg:svg")
- .attr("width", this._w)
- .attr("height", this._h);
- // add sankey diagram or graph depending on type
- if (this._type)
- this._addSankey();
- else
- this._addDag();
+// rules of thumb: nodeHeight = 20, labelFontSize = 14, maxLabelWidth = 180
+// nodeHeight = 15, labelFontSize = 10, maxLabelWidth = 120
+// nodeHeight = 40, labelFontSize = 20, maxLabelWidth = 260
+// nodeHeight = 30, labelFontSize = 16
+DagViewer.prototype.drawDag = function (svgw, svgh, nodeHeight, labelFontSize,
maxLabelWidth, axisPadding, svgPadding) {
+ this._addTimelineGraph(svgw, svgh, nodeHeight || 20, labelFontSize || 14,
maxLabelWidth || 180, axisPadding || 30, svgPadding || 20);
return this;
}
-//draw the graph
-DagViewer.prototype._addDag = function () {
- var w = this._w;
- var h = this._h;
- var nodeSize = this._nodeSize;
- var largeNodeSize = this._largeNodeSize;
- var linkDistance = this._linkDistance;
- // add nodes and links to force layout
- this._force.nodes(this._nodes)
- .links(this._links)
- .linkDistance(this._linkDistance);
+// draw timeline graph
+DagViewer.prototype._addTimelineGraph = function (svgw, svgh, nodeHeight,
labelFontSize, maxLabelWidth, axisPadding, svgPadding) {
+ // want to avoid having unnecessary scrollbars, so we need to size the div
slightly larger than the svg
+ svgw = svgw - svgPadding;
+
+ var margin = {"top":10, "bottom":10, "left":30, "right":30};
+ var w = svgw - margin.left - margin.right;
+
+ var startTime = this._minStartTime;
+ var elapsedTime = this._maxFinishTime - this._minStartTime;
+ var x = d3.time.scale()
+ .domain([0, elapsedTime])
+ .range([0, w]);
+
+ // process nodes and determine their x and y positions, width and height
+ var minNodeSpacing = nodeHeight/2;
+ var ends = new Array();
+ var maxIndex = 0;
+ this._nodes = this._nodes.sort(function(a,b){return
a.name.localeCompare(b.name);});
+ for (var i = 0; i < this._numNodes; i++) {
+ var d = this._nodes[i];
+ d.x = x(d.submitTime-startTime);
+ d.w = x(d.elapsedTime+d.submitTime-startTime) - x(d.submitTime-startTime);
+ if (d.w < nodeHeight/2) {
+ d.w = nodeHeight/2;
+ if (d.x + d.w > w)
+ d.x = w - d.w;
+ }
+ var effectiveX = d.x
+ var effectiveWidth = d.w;
+ if (d.w < maxLabelWidth) {
+ effectiveWidth = maxLabelWidth;
+ if (d.x + effectiveWidth > w)
+ effectiveX = w - effectiveWidth;
+ else if (d.x > 0)
+ effectiveX = d.x+(d.w-maxLabelWidth)/2;
+ }
+ // select "lane" (slot for y-position) for this node
+ // starting at the slot above the node's closest source node (or 0, if
none exists)
+ // and moving down until a slot is found that has no nodes within
minNodeSpacing of this node
+ // excluding slots that contain more than one source of this node
+ var index = 0;
+ var rejectIndices = new Array();
+ if (d.name in this._sourceMap) {
+ var sources = this._sourceMap[d.name];
+ var closestSource = sources[0];
+ var indices = new Array();
+ for (var j = 0; j < sources.length; j++) {
+ if (sources[j].index in indices)
+ rejectIndices[sources[j].index] = true;
+ indices[sources[j].index] = true;
+ if (sources[j].submitTime + sources[j].elapsedTime >
closestSource.submitTime + closestSource.elapsedTime)
+ closestSource = sources[j];
+ }
+ index = Math.max(0, closestSource.index-1);
+ }
+ while ((index in ends) && ((index in rejectIndices) ||
(ends[index]+minNodeSpacing >= effectiveX))) {
+ index++
+ }
+ ends[index] = Math.max(effectiveX + effectiveWidth);
+ maxIndex = Math.max(maxIndex, index);
+ d.y = index*2*nodeHeight + axisPadding;
+ d.h = nodeHeight;
+ d.index = index;
+ }
+
+ var h = 2*axisPadding + 2*nodeHeight*(maxIndex+1);
+ d3.select("div#" + this._id)
+
.attr("style","width:"+(svgw+svgPadding)+"px;height:"+Math.min(svgh,h+margin.top+margin.bottom+svgPadding)+"px;overflow:auto;padding:none;");
+ svgh = h + margin.top + margin.bottom;
+ var svg = d3.select("div#" + this._id).append("svg:svg")
+ .attr("width", svgw+"px")
+ .attr("height", svgh+"px");
+ var svgg = svg.append("g")
+ .attr("transform", "translate("+margin.left+","+margin.top+")");
+
+ // create axes
+ var x = d3.time.scale()
+ .domain([0, elapsedTime])
+ .range([0, w]);
+ var tickFormatter = function(x) {
+ d = x.getTime();
+ if (d==0) { return "0" }
+ var seconds = Math.floor(parseInt(d) / 1000);
+ if ( seconds < 60 )
+ return seconds + "s";
+ var minutes = Math.floor(seconds / 60);
+ if ( minutes < 60 ) {
+ var x = seconds - 60*minutes;
+ return minutes + "m" + (x==0 ? "" : " " + x + "s");
+ }
+ var hours = Math.floor(minutes / 60);
+ if ( hours < 24 ) {
+ var x = minutes - 60*hours;
+ return hours + "h" + (x==0 ? "" : " " + x + "m");
+ }
+ var days = Math.floor(hours / 24);
+ if ( days < 7 ) {
+ var x = hours - 24*days;
+ return days + "d " + (x==0 ? "" : " " + x + "h");
+ }
+ var weeks = Math.floor(days / 7);
+ var x = days - 7*weeks;
+ return weeks + "w " + (x==0 ? "" : " " + x + "d");
+ };
+ var topAxis = d3.svg.axis()
+ .scale(x)
+ .orient("bottom")
+ .tickFormat(tickFormatter);
+ var bottomAxis = d3.svg.axis()
+ .scale(x)
+ .orient("top")
+ .tickFormat(tickFormatter);
+ svgg.append("g")
+ .attr("class", "x axis")
+ .call(topAxis);
+ svgg.append("g")
+ .attr("class", "x axis")
+ .call(bottomAxis)
+ .attr("transform", "translate(0,"+h+")");
+
+ // create a rectangle for each node
+ var boxes = svgg.append("svg:g").selectAll("rect")
+ .data(this._nodes)
+ .enter().append("svg:rect")
+ .attr("x", function(d) { return d.x; } )
+ .attr("y", function(d) { return d.y; } )
+ .attr("width", function(d) { return d.w; } )
+ .attr("height", function(d) { return d.h; } )
+ .attr("class", function (d) {
+ return "node " + (d.status ? " finished" : "");
+ })
+ .attr("id", function (d) {
+ return d.name;
+ });
+
// defs for arrowheads marked as to whether they link finished jobs or not
- this._svg.append("svg:defs").selectAll("marker")
+ svgg.append("svg:defs").selectAll("arrowmarker")
.data(["finished", "unfinished"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
- .attr("refX", nodeSize + 10)
- .attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
- .attr("d", "M0,-5L10,0L0,5");
+ .attr("d", "M0,-3L8,0L0,3");
+ // defs for unsubmitted node marker
+ svgg.append("svg:defs").selectAll("circlemarker")
+ .data(["circle"])
+ .enter().append("svg:marker")
+ .attr("id", String)
+ .attr("viewBox", "-2 -2 18 18")
+ .attr("markerWidth", 10)
+ .attr("markerHeight", 10)
+ .attr("refX", 10)
+ .attr("refY", 5)
+ .attr("orient", "auto")
+ .append("svg:circle")
+ .attr("cx", 5)
+ .attr("cy", 5)
+ .attr("r", 5);
+
+ // create dangling links representing unsubmitted jobs
+ var markerWidth = nodeHeight/2;
+ var sourceMarker = svgg.append("svg:g").selectAll("line")
+ .data(this._sourceMarker)
+ .enter().append("svg:line")
+ .attr("x1", function(d) { return d.x - markerWidth; } )
+ .attr("x2", function(d) { return d.x; } )
+ .attr("y1", function(d) { return d.y; } )
+ .attr("y2", function(d) { return d.y + 3; } )
+ .attr("class", "source mark")
+ .attr("marker-start", "url(#circle)");
+ var targetMarker = svgg.append("svg:g").selectAll("line")
+ .data(this._targetMarker)
+ .enter().append("svg:line")
+ .attr("x1", function(d) { return d.x + d.w + markerWidth; } )
+ .attr("x2", function(d) { return d.x + d.w; } )
+ .attr("y1", function(d) { return d.y + d.h; } )
+ .attr("y2", function(d) { return d.y + d.h - 3; } )
+ .attr("class", "target mark")
+ .attr("marker-start", "url(#circle)");
// create links between the nodes
- var lines = this._svg.append("svg:g").selectAll("line")
+ var lines = svgg.append("svg:g").selectAll("path")
.data(this._links)
- .enter().append("svg:line")
+ .enter().append("svg:path")
+ .attr("d", function(d) {
+ var s = d.source;
+ var t = d.target;
+ var x1 = s.x + s.w;
+ var x2 = t.x;
+ var y1 = s.y;
+ var y2 = t.y;
+ if (y1==y2) {
+ y1 += s.h/2;
+ y2 += t.h/2;
+ } else if (y1 < y2) {
+ y1 += s.h;
+ } else {
+ y2 += t.h;
+ }
+ return "M "+x1+" "+y1+" L "+((x2+x1)/2)+" "+((y2+y1)/2)+" L "+x2+" "+y2;
+ } )
.attr("class", function (d) {
return "link" + (d.status ? " finished" : "");
})
- .attr("marker-end", function (d) {
+ .attr("marker-mid", function (d) {
return "url(#" + (d.status ? "finished" : "unfinished") + ")";
});
-
- // create a circle for each node
- var circles = this._svg.append("svg:g").selectAll("circle")
- .data(this._nodes)
- .enter().append("svg:circle")
- .attr("r", nodeSize)
- .attr("class", function (d) {
- return "node " + (d.status ? " finished" : "");
- })
- .attr("id", function (d) {
- return d.name;
- })
- .on("dblclick", click)
- .call(this._force.drag);
-
+
// create text group for each node label
- var text = this._svg.append("svg:g").selectAll("g")
+ var text = svgg.append("svg:g").selectAll("g")
.data(this._nodes)
.enter().append("svg:g");
-
+
// add a shadow copy of the node label (will have a lighter color and thicker
- // stroke for legibility
+ // stroke for legibility)
text.append("svg:text")
- .attr("x", nodeSize + 3)
- .attr("y", ".31em")
- .attr("class", "shadow")
+ .attr("x", function(d) {
+ var goal = d.x + d.w/2;
+ var halfLabel = maxLabelWidth/2;
+ if (goal < halfLabel) return halfLabel; else if (goal >
w-halfLabel) return w-halfLabel;
+ return goal;
+ } )
+ .attr("y", function(d) { return d.y + d.h + labelFontSize; } )
+ .attr("class", "joblabel shadow")
+ .attr("style", "font: "+labelFontSize+"px sans-serif")
.text(function (d) {
return d.name;
});
-
+
// add the main node label
text.append("svg:text")
- .attr("x", nodeSize + 3)
- .attr("y", ".31em")
+ .attr("x", function(d) {
+ var goal = d.x + d.w/2;
+ var halfLabel = maxLabelWidth/2;
+ if (goal < halfLabel) return halfLabel;
+ else if (goal > w-halfLabel) return w-halfLabel;
+ return goal;
+ } )
+ .attr("y", function(d) { return d.y + d.h + labelFontSize; } )
+ .attr("class", "joblabel")
+ .attr("style", "font: "+labelFontSize+"px sans-serif")
.text(function (d) {
return d.name;
});
-
- // add mouseover actions
- this._addMouseoverSelection(circles);
-
- // start the force layout
- this._force.on("tick", tick)
- .start();
-
- // on force tick, adjust positions of nodes, links, and text
- function tick() {
- circles.attr("transform", function (d) {
- if (d.x < largeNodeSize) d.x = largeNodeSize;
- if (d.y < largeNodeSize) d.y = largeNodeSize;
- if (d.x > w - largeNodeSize) d.x = w - largeNodeSize;
- if (d.y > h - largeNodeSize) d.y = h - largeNodeSize;
- return "translate(" + d.x + "," + d.y + ")";
- });
-
- lines.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
- });
-
- text.attr("transform", function (d) {
- return "translate(" + d.x + "," + d.y + ")";
- });
- }
-
- // on double click, fix node in place or release it
- function click() {
- d3.select(this).attr("fixed", function (d) {
- if (d.fixed) {
- d.fixed = false
- } else {
- d.fixed = true
- }
- return d.fixed;
- });
- }
-}
-//define mouseover action on nodes
-DagViewer.prototype._addMouseoverSelection = function (nodes) {
- var nodeSize = this._nodeSize;
- var largeNodeSize = this._largeNodeSize;
- // on mouseover, change size of node
- nodes.on("mouseover", function (d) {
- d3.select(this).transition().attr("r", largeNodeSize);
- })
- .on("mouseout", function (d) {
- d3.select(this).transition().attr("r", nodeSize);
- });
-}
-//draw Sankey diagram
-DagViewer.prototype._addSankey = function () {
- var w = this._w;
- var h = this._h;
-
- // add svg group
- var svgg = this._svg.append("g");
-
- var color = d3.scale.category20();
-
- // create sankey
- var sankey = d3.sankey()
- .nodeWidth(15)
- .nodePadding(10)
- .size([w, h * 0.67]);
-
- // get sankey links
- var spath = sankey.link();
-
- // set sankey nodes and links and calculate their positions and sizes
- sankey
- .nodes(this._nodes)
- .links(this._links)
- .overlapLinksAtSources(true)
- .layout(32);
-
- // create links and set their attributes
- var slink = svgg.append("g").selectAll(".link")
- .data(this._links)
- .enter().append("path")
- .attr("class", "slink")
- .attr("d", spath)
- .style("stroke-width", function (d) {
- return Math.max(1, d.dy);
- })
- .sort(function (a, b) {
- return b.dy - a.dy;
- });
-
- // add mouseover text to links
- slink.append("title")
- .text(function (d) {
- return d.source.name + " - " + d.target.name + ": " + d.value;
- });
-
- // create node groups, set their attributes, and enable vertical dragging
- var snode = svgg.append("g").selectAll(".node")
- .data(this._nodes)
- .enter().append("g")
- .attr("class", "snode")
- .attr("transform", function (d) {
- return "translate(" + d.x + "," + d.y + ")";
- })
- .call(d3.behavior.drag()
- .origin(function (d) {
- return d;
- })
- .on("dragstart", function () {
- this.parentNode.appendChild(this);
- })
- .on("drag", dragmove));
-
- // add rectangles to node groups
- snode.append("rect")
- .attr("height", function (d) {
- return d.dy;
- })
- .attr("width", sankey.nodeWidth())
- .style("fill", function (d) {
- return d.color = color(d.name.replace(/ .*/, ""));
- })
- .style("stroke", function (d) {
- return d3.rgb(d.color).darker(2);
- })
- .append("title")
- .text(function (d) {
- return "info" in d ? d.info.join("\n") : d.name;
- });
-
- // add node labels
- snode.append("text")
- .attr("x", -6)
- .attr("y", function (d) {
- return d.dy / 2;
- })
- .attr("dy", ".35em")
- .attr("text-anchor", "end")
- .attr("transform", null)
- .text(function (d) {
- return d.name;
- })
- .filter(function (d) {
- return d.x < w / 2;
- })
- .attr("x", 6 + sankey.nodeWidth())
- .attr("text-anchor", "start");
-
- // add mouseover actions
- this._addMouseoverSelection(snode);
-
- // enable vertical dragging with recalculation of link placement
- function dragmove(d) {
- d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y =
Math.max(0, Math.min(h - d.dy, d3.event.y))) + ")");
- sankey.relayout();
- slink.attr("d", spath);
- }
}
Modified: incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css
URL:
http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css?rev=1440231&r1=1440230&r2=1440231&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css (original)
+++ incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css Wed Jan 30
00:52:50 2013
@@ -6,69 +6,57 @@
width: 100%;
}
-#dag_viewer line.link {
+#dag_viewer .axis path,
+#dag_viewer .axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
+#dag_viewer line.link,
+#dag_viewer path.link {
fill: none;
stroke: #666;
stroke-width: 2.5px;
}
+#dag_viewer line.link.finished,
+#dag_viewer path.link.finished {
+ stroke: #444;
+}
+
#dag_viewer marker#finished {
- fill: green;
+ fill: #444;
}
-#dag_viewer line.link.finished {
- stroke: green;
+#dag_viewer marker#circle {
+ fill: #666;
+ stroke: none;
}
-#dag_viewer circle {
+#dag_viewer line.source.mark,
+#dag_viewer line.target.mark {
+ stroke: #666;
+ stroke-width: 2.5px;
+}
+
+#dag_viewer rect {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
-#dag_viewer circle.finished {
- fill: green;
+#dag_viewer rect.finished {
+ fill: #69BE28;
}
-#dag_viewer text {
- font: 20px sans-serif;
+#dag_viewer text.joblabel {
pointer-events: none;
-}
-
-#dag_viewer text.hover {
- font: 10px sans-serif;
-}
-
-#dag_viewer text.hover.shadow {
- stroke: #fff;
- stroke-width: 4px;
- stroke-opacity: .8;
+ text-anchor: middle;
}
#dag_viewer text.shadow {
stroke: #fff;
- stroke-width: 4px;
+ stroke-width: 3px;
stroke-opacity: .8;
}
-
-#dag_viewer .snode rect {
- cursor: move;
- fill-opacity: .9;
- shape-rendering: crispEdges;
-}
-
-#dag_viewer .snode text {
- pointer-events: none;
- text-shadow: 0 1px 0 #fff;
-}
-
-#dag_viewer .slink {
- fill: none;
- stroke: #000;
- stroke-opacity: .2;
-}
-
-#dag_viewer .slink:hover {
- stroke-opacity: .5;
-}
-