You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2013/03/08 01:30:37 UTC

svn commit: r1454191 - in /incubator/ambari/trunk: ./ ambari-web/app/ ambari-web/app/mappers/ ambari-web/app/styles/ ambari-web/app/templates/main/apps/item/ ambari-web/app/utils/ ambari-web/app/views/main/apps/item/ ambari-web/vendor/scripts/ ambari-w...

Author: yusaku
Date: Fri Mar  8 00:30:36 2013
New Revision: 1454191

URL: http://svn.apache.org/r1454191
Log:
AMBARI-1491. Add task plots to job swimlane diagram. (yusaku)

Modified:
    incubator/ambari/trunk/CHANGES.txt
    incubator/ambari/trunk/ambari-web/app/mappers/jobs_mapper.js
    incubator/ambari/trunk/ambari-web/app/messages.js
    incubator/ambari/trunk/ambari-web/app/styles/apps.less
    incubator/ambari/trunk/ambari-web/app/templates/main/apps/item/bar.hbs
    incubator/ambari/trunk/ambari-web/app/utils/graph.js
    incubator/ambari/trunk/ambari-web/app/views/main/apps/item/bar_view.js
    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=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/CHANGES.txt (original)
+++ incubator/ambari/trunk/CHANGES.txt Fri Mar  8 00:30:36 2013
@@ -105,6 +105,8 @@ Trunk (unreleased changes):
 
  IMPROVEMENTS
 
+ AMBARI-1491. Add task plots to job swimlane diagram. (billie via yusaku)
+
  AMBARI-1584. Stack Upgrade Wizard - integrate host progress popup.
  (yusaku)
 

Modified: incubator/ambari/trunk/ambari-web/app/mappers/jobs_mapper.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/mappers/jobs_mapper.js?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/mappers/jobs_mapper.js (original)
+++ incubator/ambari/trunk/ambari-web/app/mappers/jobs_mapper.js Fri Mar  8 00:30:36 2013
@@ -71,6 +71,23 @@ App.jobTimeLineMapper = App.QuickDataMap
   }
 });
 
+App.taskTimeLineMapper = App.QuickDataMapper.create({
+  model: null, //model will be set outside of mapper
+  config:{
+    allmap:'map',
+    allshuffle:'shuffle',
+    allreduce:'reduce'
+  },
+  map:function (json) {
+    var job = this.get('model'); // @model App.MainAppsItemBarView
+    var parseResult = this.parseIt(json, this.config);
+
+    $.each(parseResult, function (field, value) {
+      job.set(field, value);
+    });
+  }
+});
+
 App.jobTasksMapper = App.QuickDataMapper.create({
   model: null, //model will be set outside of mapper
   config:{
@@ -88,4 +105,4 @@ App.jobTasksMapper = App.QuickDataMapper
       job.set(field, value);
     });
   }
-});
\ No newline at end of file
+});

Modified: incubator/ambari/trunk/ambari-web/app/messages.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/messages.js?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/messages.js (original)
+++ incubator/ambari/trunk/ambari-web/app/messages.js Fri Mar  8 00:30:36 2013
@@ -926,10 +926,8 @@ Em.I18n.translations = {
   'apps.filters.nothingToShow': 'No jobs to display',
   'apps.dagCharts.popup':'Job Charts',
   'apps.dagCharts.popup.job': 'Job',
-  'apps.dagCharts.popup.dag':'DAG',
-  'apps.dagCharts.popup.tasks':'Timeline & Tasks',
-  'apps.dagCharts.popup.tasks.timeline':'Job Timeline',
-  'apps.dagCharts.popup.tasks.tasks':'Job Tasks',
+  'apps.dagCharts.popup.dag':'Job Timeline',
+  'apps.dagCharts.popup.tasks':'Job Tasks',
   'apps.isRunning.popup.title':'Is running',
   'apps.isRunning.popup.content':'Job is running now',
 
@@ -940,4 +938,4 @@ Em.I18n.translations = {
   'menu.item.jobs':'Jobs',
   'menu.item.admin':'Admin'
 
-};
\ No newline at end of file
+};

Modified: incubator/ambari/trunk/ambari-web/app/styles/apps.less
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/styles/apps.less?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/styles/apps.less (original)
+++ incubator/ambari/trunk/ambari-web/app/styles/apps.less Fri Mar  8 00:30:36 2013
@@ -389,6 +389,17 @@
     }
   }
 
+  #job_tasks .axis line,
+  #job_tasks .axis path {
+    fill: none;
+    stroke: #000;
+    shape-rendering: crispEdges;
+  }
+  #job_tasks text.axislabel {
+    pointer-events: none;
+    text-anchor: middle;
+  }
+
   ul.nav-tabs{
     margin-bottom: 0;
   }
@@ -470,4 +481,4 @@
   }
 }
 
-/*Big modal window end*/
\ No newline at end of file
+/*Big modal window end*/

Modified: incubator/ambari/trunk/ambari-web/app/templates/main/apps/item/bar.hbs
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/templates/main/apps/item/bar.hbs?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/templates/main/apps/item/bar.hbs (original)
+++ incubator/ambari/trunk/ambari-web/app/templates/main/apps/item/bar.hbs Fri Mar  8 00:30:36 2013
@@ -16,42 +16,5 @@
 * limitations under the License.
 }}
 
-<div class="btn-group">
-  <button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
-    {{t apps.dagCharts.popup.job}} {{view.activeJob.id}} - {{view.activeJob.run.id}}
-    <span class="caret"></span>
-  </button>
-  <ul class="dropdown-menu">
-    {{#each job in view.content}}
-    <li><a {{action "selectJob" job target="view"}} href="javascript:void(null)">{{t apps.dagCharts.popup.job}} {{job.id}} - {{job.run.id}}</a></li>
-    {{/each}}
-  </ul>
-</div>
-<div></div>
-<div id="graph1" class="pull-left">
-  <div class="pull-left">
-    <div id="graph1_desc" class="graph_desc">
-      <h4>{{t apps.dagCharts.popup.tasks.timeline}}</h4>
-    </div>
-    <div id="y-axis"></div>
-    <div id="chart"></div>
-    <div id="timeline1"></div>
-  </div>
-  <div id="legend_container" class="pull-left">
-    <div id="legend"></div>
-  </div>
-</div>
-<div id="graph2" class="pull-right">
-  <div class="pull-left">
-    <div id="graph2_desc" class="graph_desc">
-      <h4>{{t apps.dagCharts.popup.tasks.tasks}}</h4>
-    </div>
-    <div id="y-axis2"></div>
-    <div id="job_tasks"></div>
-    <div id="timeline2"></div>
-  </div>
-  <div id="tasks_legend_container" class="pull-left">
-    <div id="tasks_legend"></div>
-  </div>
-</div>
-<div class="clearfix"></div>
\ No newline at end of file
+<div id="job_tasks"></div>
+<div class="clearfix"></div>

Modified: incubator/ambari/trunk/ambari-web/app/utils/graph.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/utils/graph.js?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/utils/graph.js (original)
+++ incubator/ambari/trunk/ambari-web/app/utils/graph.js Fri Mar  8 00:30:36 2013
@@ -18,195 +18,63 @@
 
 
 module.exports = {
-  uniformSeries: function () {
-    var series_min_length = 100000000;
-    for (i=0; i<arguments.length; i++) {
-      if (arguments[i].length < series_min_length) {
-        series_min_length = arguments[i].length;
-      }
-    }
-    for (i=0; i<arguments.length; i++) {
-      if (arguments[i].length > series_min_length) {
-        arguments[i].length = series_min_length;
-      }
-    }
+  durationFormatter:function(d) {
+      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");
   },
-  /**
-   * Get min, max for X and Y for provided series
-   * @param series
-   * @return {Object}
-   */
-  getExtInSeries: function(series) {
-    var maxY = 0;
-    var maxX = 0;
-    var minX = 2147465647; // max timestamp value
-    var minY = 2147465647;
-    if (series.length > 0) {
-      series.forEach(function(item){
-        if (item.y > maxY) {
-          maxY = item.y;
-        }
-        if (item.y < minY) {
-          minY = item.y;
-        }
-        if (item.x > maxX) {
-          maxX = item.x;
-        }
-        if (item.x < minX) {
-          minX = item.x;
-        }
-      });
-    }
-    return {maxX: maxX, minX: minX, maxY: maxY, minY: minY};
+  bytesFormatter:function(y) {
+    if (y >= 1125899906842624)  { return Math.floor(10 * y / 1125899906842624)/10 + " PB" }
+    else if (y >= 1099511627776){ return Math.floor(10 * y / 1099511627776)/10 + " TB" }
+    else if (y >= 1073741824)   { return Math.floor(10 * y / 1073741824)/10 + " GB" }
+    else if (y >= 1048576)      { return Math.floor(10 * y / 1048576)/10 + " MB" }
+    else if (y >= 1024)         { return Math.floor(10 * y / 1024)/10 + " KB" }
+    else                        { return y + " B"}
   },
-  /**
-   * Get min, max for x and Y for all provided series
-   * @param args array of series
-   * @return {Object}
-   */
-  getExtInAllSeries: function(args) {
-    var maxx = [];
-    var minx = [];
-    var maxy = [];
-    var miny = [];
-    for (var i = 0; i < args.length; i++) {
-      var localExt = this.getExtInSeries(args[i]);
-      maxx.push(localExt.maxX);
-      minx.push(localExt.minX);
-      maxy.push(localExt.maxY);
-      miny.push(localExt.minY);
-    }
-    return {
-      maxX: Math.max.apply(null, maxx),
-      minX: Math.min.apply(null, minx),
-      maxY: Math.max.apply(null, maxy),
-      minY: Math.min.apply(null, miny)
-    };
-  },
-  /**
-   * Get coordinates for new circle in the graph
-   * New circle needed to prevent cut on the borders of the graph
-   * List of arguments - series arrays
-   * @return {Object}
-   */
-  getNewCircle: function() {
-    var ext = this.getExtInAllSeries(arguments);
-    var newX;
-    if (ext.minX != 2147465647) {
-      newX = ext.maxX + Math.round((ext.maxX - ext.minX) * 0.2);
-    }
-    else {
-      newX = (new Date()).getTime();
-    }
-    var newY = ext.maxY * 1.2;
-    return {
-      x: newX,
-      y: newY,
-      r: 0,
-      io: 0
-    };
-  },
-  /**
-   *
-   * @param map
-   * @param shuffle
-   * @param reduce
-   * @param w
-   * @param h
-   * @param element
-   * @param legend_id
-   * @param timeline_id
-   */
-  drawJobTimeLine:function (map, shuffle, reduce, w, h, element, legend_id, timeline_id) {
-    map = $.parseJSON(map);
-    shuffle = $.parseJSON(shuffle);
-    reduce = $.parseJSON(reduce);
-    if (!map || !shuffle || !reduce) {
-      console.warn('drawJobTimeLine');
-      return;
-    }
-    this.uniformSeries(map, reduce, shuffle);
-    var ext = this.getExtInAllSeries([map, reduce, shuffle]);
-    var submitTime = ext.minX;
-    var maxX = ext.maxX; // Used on X-axis time stamps
-
-    var graph = new Rickshaw.Graph({
-      width:w,
-      height:h,
-      element:document.querySelector(element),
-      renderer:'area',
-      interpolation: 'step-after',
-      strokeWidth: 2,
-      stroke:true,
-      series:[
-        {
-          data:map,
-          color:'green',
-          name:'maps'
-        },
-        {
-          data:shuffle,
-          color:'lightblue',
-          name:'shuffles'
-        },
-        {
-          data:reduce,
-          color:'steelblue',
-          name:'reduces'
-        }
-      ]
-      }
-    );
-    graph.render();
-
-    var legend = new Rickshaw.Graph.Legend({
-      graph:graph,
-      element:document.getElementById(legend_id)
-    });
-
-    var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
-      graph:graph,
-      legend:legend
-    });
-
-    var order = new Rickshaw.Graph.Behavior.Series.Order({
-      graph:graph,
-      legend:legend
-    });
-
-    var highlight = new Rickshaw.Graph.Behavior.Series.Highlight({
-      graph:graph,
-      legend:legend
-    });
-
-    var xAxis = new Rickshaw.Graph.Axis.Time({
-      graph:graph,
-      timeUnit: {
-        name: 'Custom',
-        seconds: Math.round((maxX - submitTime) / 2),
-        formatter: function(d) { return (new Date(d)).getTime() / 1000 - submitTime + 's'; }
-      }
-    });
-    xAxis.render();
-
-    var yAxis = new Rickshaw.Graph.Axis.Y({
-      orientation: 'left',
-      element: document.querySelector('#y-axis'),
-      graph:graph
-    });
-    yAxis.render();
-
-    var hoverDetail = new Rickshaw.Graph.HoverDetail({
-      graph:graph,
-      yFormatter:function (y) {
-        return Math.floor(y) + " tasks"
-      }
-    });
-
-    /*var annotator = new Rickshaw.Graph.Annotate({
-      graph:graph,
-      //element:document.getElementById(timeline_id)
-    });*/
+  addSeries:function(svgg,series,color,xscale,yscale,margin,startTime,dotInfo) {
+    if (series.length==0) return;
+    var self = this;
+    var g = svgg.append("svg:g").selectAll("g")
+      .data(series)
+      .enter().append("svg:g")
+      .attr("transform", "translate(0,"+margin+")");
+    g.append("svg:circle")
+      .attr("r",function(d) {return d.r;})
+      .attr("cx",function(d) {return xscale(d.x);})
+      .attr("cy",function(d) {return yscale(d.y);})
+      .style("fill",color)
+      .style("fill-opacity",0.8)
+      .style("stroke",d3.interpolateRgb(color, 'black')(0.125))
+      .append("title")
+      .text(function(d) { return dotInfo[Math.round(xscale(d.x))][Math.round(yscale(d.y))]; });
+    g.append("svg:line")
+      .attr("x1", function(d) { return xscale(d.x)+d.r; } )
+      .attr("x2", function(d) { return xscale(d.x+d.y); } )
+      .attr("y1", function(d) { return yscale(d.y); } )
+      .attr("y2", function(d) { return yscale(d.y); } )
+      .style("stroke",d3.interpolateRgb(color, 'black')(0.125))
+      .style("stroke-width",2)
+      .append("title")
+      .text(function(d) { return dotInfo[Math.round(xscale(d.x))][Math.round(yscale(d.y))]; });
   },
   /**
    *
@@ -214,125 +82,103 @@ module.exports = {
    * @param mapRackLocal
    * @param mapOffSwitch
    * @param reduceOffSwitch
-   * @param submitTime
+   * @param startTime
+   * @param endTime
    * @param w
    * @param h
    * @param element
-   * @param legend_id
-   * @param timeline_id
    */
-  drawJobTasks:function (mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, submitTime, w, h, element, legend_id, timeline_id) {
-    mapNodeLocal = $.parseJSON(mapNodeLocal);
-    mapRackLocal = $.parseJSON(mapRackLocal);
-    mapOffSwitch = $.parseJSON(mapOffSwitch);
-    reduceOffSwitch = $.parseJSON(reduceOffSwitch);
-    if (!mapNodeLocal || !mapRackLocal || !mapOffSwitch || !reduceOffSwitch) {
-      console.warn('drawJobTasks');
-      return;
-    }
-    this.uniformSeries(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch);
-    var newC = this.getNewCircle(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch);
-    var ext = this.getExtInAllSeries([mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch]);
-    var maxX = ext.maxX; // Used on X-axis time stamps
-    mapNodeLocal.push(newC);
-    mapRackLocal.push(newC);
-    mapOffSwitch.push(newC);
-    reduceOffSwitch.push(newC);
-    var graph = new Rickshaw.Graph({
-      width:w,
-      height:h,
-      element:document.querySelector(element),
-      renderer:'scatterplot',
-      stroke:true,
-      series:[
-        {
-          data:mapNodeLocal,
-          color:'green',
-          name:'node_local_map'
-        },
-        {
-          data:mapRackLocal,
-          color:'#66B366',
-          name:'rack_local_map'
-        },
-        {
-          data:mapOffSwitch,
-          color:'brown',
-          name:'off_switch_map'
-        },
-        {
-          data:reduceOffSwitch,
-          color:'steelblue',
-          name:'reduce'
-        }
-      ]
-    });
-    graph.render();
-    var legend = new Rickshaw.Graph.Legend({
-      graph:graph,
-      element:document.getElementById(legend_id)
-    });
-
-    var shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
-      graph:graph,
-      legend:legend
-    });
-
-    var order = new Rickshaw.Graph.Behavior.Series.Order({
-      graph:graph,
-      legend:legend
-    });
-
-    var highlight = new Rickshaw.Graph.Behavior.Series.Highlight({
-      graph:graph,
-      legend:legend
-    });
-
-    var ticksTreatment = 'glow';
-
-    var xAxis = new Rickshaw.Graph.Axis.Time({
-      graph:graph,
-      timeUnit: {
-        name: 'Custom',
-        seconds: Math.round((maxX - submitTime) / 2),
-        formatter: function(d) { return (new Date(d)).getTime() / 1000 - submitTime + 's'; }
-      },
-      ticksTreatment:ticksTreatment
-    });
-    xAxis.render();
-
-    var yAxis = new Rickshaw.Graph.Axis.Y({
-      graph:graph,
-      ticksTreatment:ticksTreatment,
-      orientation: 'left',
-      element: document.querySelector('#y-axis2'),
-      tickFormat: function(y) { return y / 1000 + 's' }
-    });
-    yAxis.render();
-
-    var hoverDetail = new Rickshaw.Graph.HoverDetail({
-      graph:graph,
-      xFormatter:function (x) {
-        return (x - submitTime) + 's'
-      },
-      yFormatter:function (y) {
-        return y / 1000 + 's'
-      },
-      formatter:function (series, x, y, formattedX, formattedY, d) {
-        var bytesFormatter = function(y) {
-          if (y >= 1125899906842624)  { return Math.floor(10 * y / 1125899906842624)/10 + " PB" }
-          else if (y >= 1099511627776){ return Math.floor(10 * y / 1099511627776)/10 + " TB" }
-          else if (y >= 1073741824)   { return Math.floor(10 * y / 1073741824)/10 + " GB" }
-          else if (y >= 1048576)      { return Math.floor(10 * y / 1048576)/10 + " MB" }
-          else if (y >= 1024)         { return Math.floor(10 * y / 1024)/10 + " KB" }
-          else                        { return y + " B"}
-        };
-        var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
-        return swatch + d.value.label +
-          '<br>Run-time: ' + formattedY + '<br>Wait-time: ' + formattedX +
-          '<br>I/O: ' + bytesFormatter(d.value.io) + '<br>Status: ' + d.value.status;
-      }
+  drawJobTasks:function (mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, startTime, endTime, svgw, svgh, element) {
+    var rmax = 24; // default value
+    var axisHeight = 24;
+    var margin = {"vertical":10, "horizontal":50};
+    var w = svgw - 2*margin.horizontal;
+    var h = svgh - 2*margin.vertical;
+    var x = d3.time.scale.utc()
+      .domain([startTime, endTime])
+      .range([0, w]);
+    var xrel = d3.time.scale()
+      .domain([0, endTime-startTime])
+      .range([0, w]);
+    // create axes
+    var topAxis = d3.svg.axis()
+      .scale(x)
+      .orient("bottom");
+    var self = this;
+    var bottomAxis = d3.svg.axis()
+      .scale(xrel)
+      .orient("bottom")
+      .tickFormat(function(d) {return self.durationFormatter(d.getTime())});
+
+    var svg = d3.select("div#" + element).append("svg:svg")
+      .attr("width", svgw+"px")
+      .attr("height", svgh+"px");
+    var svgg = svg.append("g")
+      .attr("transform", "translate("+margin.horizontal+","+margin.vertical+")");
+
+    svgg.append("g")
+      .attr("class", "x axis top")
+      .call(topAxis);
+    svgg.append("g")
+      .attr("class", "x axis bottom")
+      .call(bottomAxis)
+      .attr("transform", "translate(0,"+(h-axisHeight)+")");
+
+    var ymax = 0;
+    if (mapNodeLocal.length > 0)
+      ymax = Math.max(ymax, d3.max(mapNodeLocal, function(d) { return d.y; } ));
+    if (mapRackLocal.length > 0)
+      ymax = Math.max(ymax, d3.max(mapRackLocal, function(d) { return d.y; } ));
+    if (mapOffSwitch.length > 0)
+      ymax = Math.max(ymax, d3.max(mapOffSwitch, function(d) { return d.y; } ));
+    if (reduceOffSwitch.length > 0)
+      ymax = Math.max(ymax, d3.max(reduceOffSwitch, function(d) { return d.y; } ));
+
+    var y = d3.scale.linear()
+      .domain([0, ymax])
+      .range([h-2*axisHeight-rmax, 0]);
+
+    var yAxis = d3.svg.axis()
+      .scale(y)
+      .orient("left")
+      .tickFormat(self.durationFormatter);
+ 
+    svgg.append("svg:g")
+      .attr("class", "y axis")
+      .call(yAxis)
+      .attr("transform", "translate(0,"+(axisHeight+rmax)+")")
+      .append("text")
+      .attr("transform", "rotate(-90)")
+      .attr("x", -(h-2*axisHeight-rmax)/2)
+      .attr("y", -margin.horizontal + 11)
+      .attr("class", "axislabel")
+      .text("Task Attempt Duration");
+
+
+    var dotInfo = new Array();
+    var mapDotInfo = function(d) {
+      var thisx = Math.round(x(d.x));
+      var thisy = Math.round(y(d.y));
+      if (!(thisx in dotInfo))
+        dotInfo[thisx] = new Array();
+      var existing = dotInfo[thisx][thisy];
+      var newInfo = d.label + "  \n" +
+          'Run-time: ' + self.durationFormatter(d.y) + '  \nWait-time: ' + self.durationFormatter(d.x-startTime) +
+          '  \nI/O: ' + self.bytesFormatter(d.io) + '  \nStatus: ' + d.status;
+      if (existing)
+        dotInfo[thisx][thisy] = existing + "  \n" + newInfo;
+      else
+        dotInfo[thisx][thisy] = newInfo;
+    };
 
-    });
+    mapNodeLocal.forEach(mapDotInfo);
+    mapRackLocal.forEach(mapDotInfo);
+    mapOffSwitch.forEach(mapDotInfo);
+    reduceOffSwitch.forEach(mapDotInfo);
+
+    this.addSeries(svgg, mapNodeLocal, "green", x, y, axisHeight+rmax, startTime, dotInfo);
+    this.addSeries(svgg, mapRackLocal,'#66B366', x, y, axisHeight+rmax, startTime, dotInfo);
+    this.addSeries(svgg, mapOffSwitch, 'brown', x, y, axisHeight+rmax, startTime, dotInfo);
+    this.addSeries(svgg, reduceOffSwitch, 'steelblue', x, y, axisHeight+rmax, startTime, dotInfo);
   }
 }

Modified: incubator/ambari/trunk/ambari-web/app/views/main/apps/item/bar_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/trunk/ambari-web/app/views/main/apps/item/bar_view.js?rev=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/app/views/main/apps/item/bar_view.js (original)
+++ incubator/ambari/trunk/ambari-web/app/views/main/apps/item/bar_view.js Fri Mar  8 00:30:36 2013
@@ -22,59 +22,24 @@ var graph = require('utils/graph');
 App.MainAppsItemBarView = Em.View.extend({
   elementId:'bars',
   templateName:require('templates/main/apps/item/bar'),
-  width:300,
-  height:210,
-  /**
-   * Jobs list. Sorted by job id
-   */
   content:function () {
-    return this.get('controller.content.jobs').sort(function(a, b) {
-      var jobIdA = a.get('id').toLowerCase(), jobIdB = b.get('id').toLowerCase();
-      if (jobIdA < jobIdB)
-        return -1;
-      if (jobIdA > jobIdB)
-        return 1;
-      return 0;
-    });
+    return this.get('controller.content.jobs');
   }.property('controller.content.jobs'),
-  firstJob:function () {
-    return this.get('content').get('firstObject');
-  }.property('content'),
-  activeJob:null,
-  selectJob:function (event) {
-    this.set('activeJob', event.context);
-
-  },
   onLoad:function () {
     if (!this.get('controller.content.loadAllJobs') || this.get('activeJob')) {
       return;
     }
 
-    this.set('activeJob', this.get('firstJob'));
+    var self = this;
+    Ember.run.next(function(){
+      self.updateTasksView();
+    });
   }.observes('controller.content.loadAllJobs'),
   didInsertElement:function () {
     this.onLoad();
   },
   draw:function () {
-    var self = this;
-    if (!this.get('activeJob')) {
-      return;//when job is not defined
-    }
-
-    var desc1 = $('#graph1_desc');
-    var desc2 = $('#graph2_desc');
-    $('.rickshaw_graph, .rickshaw_legend, .rickshaw_annotation_timeline').html('');
-    if (null == desc1.html() || null == desc2.html()) return;
-    desc1.css('display', 'block');
-    desc2.css('display', 'block');
-
-    this.propertyDidChange('getChartData');
-
-  }.observes('activeJob'),
-
-  map:false,
-  shuffle:false,
-  reduce:false,
+  },
 
   mapNodeLocal:false,
   mapRackLocal:false,
@@ -83,46 +48,21 @@ App.MainAppsItemBarView = Em.View.extend
   submit:false,
   finish:false,
 
-  updateTimeLine:function () {
-    var url = App.testMode ? '/data/apps/jobs/timeline.json' : App.apiPrefix + "/jobhistory/task?jobId=" + this.get('activeJob').get('id') + 
-      "&width=" + this.get('width');
-    var mapper = App.jobTimeLineMapper;
-    mapper.set('model', this);
-    App.HttpClient.get(url, mapper,{
-      complete:function(jqXHR, textStatus) {
-        console.log("updateTimeLine");
-      }
-    });
-  }.observes('getChartData'),
-
   updateTasksView:function () {
-    var url = App.testMode ? '/data/apps/jobs/taskview.json' : App.apiPrefix + "/jobhistory/tasklocality?jobId=" + this.get('activeJob').get('id');
+    var url = App.testMode ? '/data/apps/jobs/taskview.json' : App.apiPrefix + "/jobhistory/tasklocality?workflowId=" + this.get('controller.content.id');
     var mapper = App.jobTasksMapper;
     mapper.set('model', this);
+    var self = this;
     App.HttpClient.get(url, mapper,{
       complete:function(jqXHR, textStatus) {
-        console.log("updateTasksView");
+        self.set('loadJobTasks', true);
       }
     });
-  }.observes('getChartData'),
-
-  drawJobTimeline:function () {
-    var map = JSON.stringify(this.get('map'));
-    var shuffle = JSON.stringify(this.get('shuffle'));
-    var reduce = JSON.stringify(this.get('reduce'));
-    if (!this.get('map') || !this.get('shuffle') || !this.get('reduce')) {return;}
-    $('#chart, #legend, #timeline1, #y-axis').html('');
-    graph.drawJobTimeLine(map, shuffle, reduce, this.get('width'), this.get('height'), '#chart', 'legend', 'timeline1');
-  }.observes('map', 'shuffle', 'reduce'),
+  },
 
   drawJobTasks:function () {
-    var mapNodeLocal = JSON.stringify(this.get('mapNodeLocal'));
-    var mapRackLocal = JSON.stringify(this.get('mapRackLocal'));
-    var mapOffSwitch = JSON.stringify(this.get('mapOffSwitch'));
-    var reduceOffSwitch = JSON.stringify(this.get('reduceOffSwitch'));
     if (!this.get('mapNodeLocal') || !this.get('mapRackLocal') || !this.get('mapOffSwitch') || !this.get('reduceOffSwitch')) {return;}
-    $('#job_tasks, #tasks_legend, #timeline2, #y-axis2').html('');
-    graph.drawJobTasks(mapNodeLocal, mapRackLocal, mapOffSwitch, reduceOffSwitch, this.get('submit'), this.get('width'), this.get('height'), '#job_tasks', 'tasks_legend', 'timeline2');
-  }.observes('mapNodeLocal', 'mapRackLocal', 'mapOffSwitch', 'reduceOffSwitch', 'submit')
+    graph.drawJobTasks(this.get('mapNodeLocal'), this.get('mapRackLocal'), this.get('mapOffSwitch'), this.get('reduceOffSwitch'), this.get('controller.content.startTime'), this.get('controller.content.startTime')+this.get('controller.content.elapsedTime'), this.$().width(), 300, 'job_tasks');
+  }.observes('loadJobTasks')
 
 });

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=1454191&r1=1454190&r2=1454191&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 Fri Mar  8 00:30:36 2013
@@ -23,12 +23,7 @@ App.MainAppsItemDagView = Em.View.extend
   templateName: require('templates/main/apps/item/dag'),
   elementId : 'jobs',
   content:function(){
-    //if(this.get("controller.jobsLoaded") == true)
-   // {
-      return this.get('controller.content.jobs');
-  //  }
-      return this.get('controller.content.jobs');
-  //  }
+    return this.get('controller.content.jobs');
   }.property('controller.content.jobs'),
 
   classNames:['table','dataTable'],
@@ -42,8 +37,7 @@ App.MainAppsItemDagView = Em.View.extend
       result[index] = new Object({
         'name' : item.get('id'),
         'entityName' : item.get('workflow_entity_name'),
-        'status' : item.get('status') == 'SUCCESS',
-        'info' : [],
+        'status' : item.get('status'),
         'input' : item.get('input'),
         'output' : item.get('output'),
         'submitTime' : item.get('submit_time'),
@@ -70,6 +64,7 @@ App.MainAppsItemDagView = Em.View.extend
 
     Ember.run.next(function(){
       self.draw();
+      self.updateTimeline();
     });
 
   }.observes('controller.content.loadAllJobs'),
@@ -101,11 +96,55 @@ App.MainAppsItemDagView = Em.View.extend
     this.onLoad();
   },
 
+  loadTaskTimeline:false,
+  loadJobTimeline:false,
+  map:false,
+  shuffle:false,
+  reduce:false,
+  allmap:false,
+  allshuffle:false,
+  allreduce:false,
+
+  updateTimeline:function () {
+    var url = App.testMode ? '/data/apps/jobs/timeline.json' : App.apiPrefix + "/jobhistory/task?workflowId=" + this.get('controller.content.id') + "&width=" + this.$().width() + "&startTime=" + this.get('controller.content.startTime') + "&endTime=" + (this.get('controller.content.startTime')+this.get('controller.content.elapsedTime'));
+    var mapper = App.jobTimeLineMapper;
+    mapper.set('model', this);
+    var self = this;
+    App.HttpClient.get(url, mapper,{
+      complete:function(jqXHR, textStatus) {
+        self.set('loadJobTimeline', true);
+      }
+    });
+    url = App.testMode ? '/data/apps/jobs/timeline.json' : App.apiPrefix + "/jobhistory/task?width=" + this.$().width() + "&startTime=" + this.get('controller.content.startTime') + "&endTime=" + (this.get('controller.content.startTime')+this.get('controller.content.elapsedTime'));
+    var mapper = App.taskTimeLineMapper;
+    mapper.set('model', this);
+    var self = this;
+    App.HttpClient.get(url, mapper,{
+      complete:function(jqXHR, textStatus) {
+        self.set('loadTaskTimeline', true);
+      }
+    });
+  },
+
+  drawJobTimeline:function () {
+    if (this.get('loadJobTimeline') && this.get('loadTaskTimeline')) {
+      this.daggraph.addTimeSeries([{"name":"allmap","color":"green","values":this.get('allmap')},
+        {"name":"map","color":"green","values":this.get('map')}], 0, "Maps");
+      this.daggraph.addTimeSeries([
+        {"name":"allshuffle","color":"lightblue","values":this.get('allshuffle')},
+        {"name":"allreduce","color":"steelblue","values":this.get("allreduce")},
+        {"name":"shuffle","color":"lightblue","values":this.get('shuffle')},
+        {"name":"reduce","color":"steelblue","values":this.get("reduce")}], 1, "Reduces");
+    }
+  }.observes('loadJobTimeline', 'loadTaskTimeline'),
+
+  daggraph:false,
+
   draw: function(){
     var dagSchema = this.get('controller.content.workflowContext');
     var jobs = this.get('jobs');
     this.resizeModal();
-    var graph = new DagViewer('dag_viewer')
+    this.daggraph = new DagViewer('dag_viewer')
         .setData(dagSchema, jobs)
         .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=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js (original)
+++ incubator/ambari/trunk/ambari-web/vendor/scripts/workflow_visualization.js Fri Mar  8 00:30:36 2013
@@ -11,6 +11,7 @@ function DagViewer(domId) {
   this._links = new Array();
   this._numNodes = 0;
   this._id = domId;
+  this._SUCCESS = "SUCCESS";
 }
 
 // set workflow schema and job data
@@ -24,6 +25,13 @@ DagViewer.prototype.setData = function (
   var maxFinishTime = 0;
   // iterate through job data
   for (var i = 0; i < jobData.length; i++) {
+    jobData[i].info = "jobId:"+jobData[i].name+"  \n"+
+      "nodeName:"+jobData[i].entityName+"  \n"+
+      "status:"+jobData[i].status+"  \n"+
+      "input:"+jobData[i].input+"  \n"+
+      "output:"+jobData[i].output+"  \n"+
+      "startTime:"+(new Date(jobData[i].submitTime).toString())+"  \n"+
+      "duration:"+DagViewer.formatDuration(jobData[i].elapsedTime);
     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]);
@@ -71,7 +79,7 @@ DagViewer.prototype._addLink = function 
   }
   // add link between nodes
   var status = false;
-  if (sourceNode.status && targetNode.status)
+  if (sourceNode.status==this._SUCCESS && targetNode.status==this._SUCCESS)
     status = true;
   this._links.push({"source":sourceNode, "target":targetNode, "status":status, "value":sourceNode.output});
   // add source to map of targets to sources
@@ -85,21 +93,27 @@ DagViewer.prototype._addLink = function 
 //                 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) {
-  this._addTimelineGraph(svgw, svgh, nodeHeight || 20, labelFontSize || 14, maxLabelWidth || 180, axisPadding || 30);
+DagViewer.prototype.drawDag = function (svgw, svgh, nodeHeight, labelFontSize, maxLabelWidth, axisPadding, numExtraSeries, extraSeriesSize) {
+  this._addTimelineGraph(svgw, svgh, nodeHeight || 20, labelFontSize || 14, maxLabelWidth || 180, axisPadding || 30, numExtraSeries || 2, extraSeriesSize || 50);
   return this;
 }
 
 // draw timeline graph
-DagViewer.prototype._addTimelineGraph = function (svgw, svgh, nodeHeight, labelFontSize, maxLabelWidth, axisPadding) {
+DagViewer.prototype._addTimelineGraph = function (svgw, svgh, nodeHeight, labelFontSize, maxLabelWidth, axisPadding, numExtraSeries, extraSeriesSize) {
   svgw = svgw;
-
-  var margin = {"top":10, "bottom":10, "left":30, "right":30};
-  var w = svgw - margin.left - margin.right;
+  this._extraSeriesSize = extraSeriesSize;
+  
+  var margin = {"vertical":10, "horizontal":50};
+  this._margin = margin;
+  var w = svgw - 2*margin.horizontal;
 
   var startTime = this._minStartTime;
   var elapsedTime = this._maxFinishTime - this._minStartTime;
-  var x = d3.time.scale()
+  var x = d3.time.scale.utc()
+    .domain([startTime, startTime+elapsedTime])
+    .range([0, w]);
+  this._x = x;
+  var xrel = d3.time.scale()
     .domain([0, elapsedTime])
     .range([0, w]);
 
@@ -110,8 +124,8 @@ DagViewer.prototype._addTimelineGraph = 
   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);
+    d.x = x(d.submitTime);
+    d.w = x(d.elapsedTime+d.submitTime) - x(d.submitTime);
     if (d.w < nodeHeight/2) {
       d.w = nodeHeight/2;
       if (d.x + d.w > w)
@@ -156,17 +170,19 @@ DagViewer.prototype._addTimelineGraph = 
   }
 
   var h = 2*axisPadding + 2*nodeHeight*(maxIndex+1);
-  var realh = svgh - margin.top - margin.bottom;
+  var realh = svgh - 2*margin.vertical - numExtraSeries*extraSeriesSize;
   var scale = 1;
   if (h > realh)
     scale = realh / h;
-  svgh = Math.min(svgh, h + margin.top + margin.bottom);
+  svgh = Math.min(svgh, h + 2*margin.vertical + numExtraSeries*extraSeriesSize);
+  this._extraSeriesOffset = h + margin.vertical;
   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+") scale("+scale+")");
+    .attr("transform", "translate("+margin.horizontal+","+margin.vertical+") scale("+scale+")");
+  this._svgg = svgg;
   // add an untranslated white rectangle below everything
   // so mouse doesn't have to be over nodes for panning/zooming
   svgg.append("svg:rect")
@@ -177,41 +193,13 @@ DagViewer.prototype._addTimelineGraph = 
     .attr("style", "fill:white;stroke:none");
  
   // 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(d3.time.scale().domain([startTime, startTime+elapsedTime]).range([0, w]))
+    .scale(x)
     .orient("bottom");
   var bottomAxis = d3.svg.axis()
-    .scale(x)
+    .scale(xrel)
     .orient("top")
-    .tickFormat(tickFormatter);
+    .tickFormat(function(x) { return DagViewer.formatDuration(x.getTime()); });
   svgg.append("g")
     .attr("class", "x axis top")
     .call(topAxis);
@@ -221,6 +209,7 @@ DagViewer.prototype._addTimelineGraph = 
     .attr("transform", "translate(0,"+h+")");
   
   // create a rectangle for each node
+  var success = this._SUCCESS;
   var boxes = svgg.append("svg:g").selectAll("rect")
     .data(this._nodes)
     .enter().append("svg:rect")
@@ -229,11 +218,13 @@ DagViewer.prototype._addTimelineGraph = 
     .attr("width", function(d) { return d.w; } )
     .attr("height", function(d) { return d.h; } )
     .attr("class", function (d) {
-      return "node " + (d.status ? " finished" : "");
+      return "node " + (d.status==success ? " finished" : "");
     })
     .attr("id", function (d) {
       return d.name;
-    });
+    })
+    .append("title")
+    .text(function(d) { return d.info; });
   
   // defs for arrowheads marked as to whether they link finished jobs or not
   svgg.append("svg:defs").selectAll("arrowmarker")
@@ -350,8 +341,76 @@ DagViewer.prototype._addTimelineGraph = 
     });
 
   svg.call(d3.behavior.zoom().on("zoom", function() {
-    var left = Math.min(Math.max(d3.event.translate[0]+margin.left, margin.left-w*d3.event.scale*scale), margin.left+w);
-    var top = Math.min(Math.max(d3.event.translate[1]+margin.top, margin.top-h*d3.event.scale*scale), margin.top+h);
+    var left = Math.min(Math.max(d3.event.translate[0]+margin.horizontal, margin.horizontal-w*d3.event.scale*scale), margin.horizontal+w);
+    var top = Math.min(Math.max(d3.event.translate[1]+margin.vertical, margin.vertical-h*d3.event.scale*scale), margin.vertical+h);
     svgg.attr("transform", "translate("+left+","+top+") scale("+(d3.event.scale*scale)+")");
   }));
 }
+
+DagViewer.prototype.addTimeSeries = function (series, position, name) {
+  var offset = this._extraSeriesOffset + this._extraSeriesSize*position;
+  var x = this._x;
+  var ymax = d3.max(series, function(d) {return d3.max(d.values, function(d) { return d.y;} ) } );
+  var y = d3.scale.linear()
+    .domain([0, ymax])
+    .range([this._extraSeriesSize - this._margin.vertical, 0]);
+
+  var yAxis = d3.svg.axis()
+    .scale(y)
+    .ticks(ymax < 4 ? ymax : 4)
+    .orient("left");
+  
+  var line = d3.svg.line()
+     .interpolate("linear")
+     .x(function(d) { return x(d.x*1000); } )
+     .y(function(d) { return y(d.y); } );
+  
+  this._svgg.append("svg:g")
+    .attr("class", "y axis")
+    .call(yAxis)
+    .attr("transform", "translate(0,"+offset+")")
+    .append("text")
+    .attr("transform", "rotate(-90)")
+    .attr("x", -(this._extraSeriesSize - this._margin.vertical)/2)
+    .attr("y", -this._margin.horizontal + 11)
+    .attr("class", "axislabel")
+    .text(name);
+
+  var lines = this._svgg.append("svg:g").selectAll("path")
+    .data(series)
+    .enter().append("svg:path")
+    .attr("d", function(d) { return line(d.values);})
+    .attr("class", function(d) { return d.name;})
+    .attr("style", function(d) {
+      if (d.name.substring(0,3)=="all")
+        return "stroke:"+d3.interpolateRgb(d.color, 'black')(0.125)+";fill:white"; 
+      else
+        return "stroke:"+d3.interpolateRgb(d.color, 'black')(0.125)+";fill:"+d.color; 
+    })
+    .attr("transform", "translate(0,"+offset+")");
+}
+
+DagViewer.formatDuration = function(d) {
+  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");
+};

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=1454191&r1=1454190&r2=1454191&view=diff
==============================================================================
--- incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css (original)
+++ incubator/ambari/trunk/ambari-web/vendor/styles/cubism.css Fri Mar  8 00:30:36 2013
@@ -47,7 +47,8 @@
 }
 
 #dag_viewer rect.finished {
-  fill: #69BE28;
+  fill: green;
+  fill-opacity: 0.8;
 }
 
 #dag_viewer text.joblabel {
@@ -55,8 +56,27 @@
   text-anchor: middle;
 }
 
+#dag_viewer text.axislabel {
+  pointer-events: none;
+  text-anchor: middle;
+}
+
 #dag_viewer text.shadow {
   stroke: #fff;
   stroke-width: 3px;
   stroke-opacity: .8;
 }
+
+.map, .shuffle, .reduce {
+  fill-opacity: 0.8;
+  stroke-width: 2px;
+}
+
+.allmap, .allshuffle, .allreduce {
+  fill: none;
+  stroke-width: 2px;
+}
+
+.y.axis {
+  font-size: 10px;
+}