Przeglądaj źródła

AMBARI-1268. Improve DAG UI. (billie via yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/trunk@1440231 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako 12 lat temu
rodzic
commit
d6c91996bc

+ 2 - 0
CHANGES.txt

@@ -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)
 

+ 5 - 4
ambari-web/app/views/main/apps/item/dag_view.js

@@ -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);
   }
 });

+ 264 - 588
ambari-web/vendor/scripts/workflow_visualization.js

@@ -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 (existingNodes, entityName, node) {
 
 // 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);
-  }
 }

+ 30 - 42
ambari-web/vendor/styles/cubism.css

@@ -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;
-}
-