Showing posts with label Chart. Show all posts
Showing posts with label Chart. Show all posts

January 10, 2012

Sprint Burndown Charts

This article shows how to draw sprint burndown charts using Dojo. Here is the JavaScript code:

// Some globals.
defaultTitleColor = "darkblue";
defaultXAxisTitle = "Days";
defaultYAxisTitle = "Remaining / Verified Tasks";
dojoInitialized = false;
chartArgs = new Array();
holidayDatesArray = new Array();

var themeClasses = new Object();

// Some utility functions.
$.getScript = function(url, callback, cache){
 $.ajax({
   type: "GET",
   url: url,
   success: callback,
   dataType: "script",
   cache: cache
 });
};

var addBusinessDays = function(d, n) {
 d = new Date(d.getTime());
 var day = d.getDay();
 d.setDate(d.getDate() + n + (day === 6 ? 2 : + !day) + (Math.floor((n - 1 + (day % 6 || 1)) / 5) * 2));
 return d;
}

var setDayValues = function (seriesIndex, days) {
 for (var i = 0; i < days; i++) {
  this.data.setValue(i, seriesIndex, "" + (i + 1));
 }
}

var datesEqual = function(a, b) {
   return (!(a > b || b > a));
}

var setDayLabels = function(startDate, days, dayLabels) {
 var daysSkiped = 0;
 for (var i = 0; i < days + 1; i++) {
  if (i == 0) {
   dayLabels.push({value: i + 1, text: ""});
   continue;
  }
  
  // We need to skip the weekends.
  var date = addBusinessDays(new Date(startDate), i - 1 + daysSkiped);
  
  // We need to skip the holidays.
  var isHoliday = false;
  while (holidayDatesArray != null && !isHoliday) {
   for (var j = 0; j < holidayDatesArray.length; j++) {
    if (datesEqual(date, holidayDatesArray[j])) {
     isHoliday = true;
     break;
    }
   }
   
   if (isHoliday) {
    date = addBusinessDays(date, 1);
    daysSkiped++;
    isHoliday = false;
   } else {
    break;
   }
  }

  var label = date.toDateString();
  label = label.substring(label.indexOf(" ") + 1, label.lastIndexOf(" "));
  dayLabels.push({value: i + 1, text: label});
 }
}

// A function to initialize the chart.
var initChart = function(chart, tensionValue, animationDuration, startDate, days, useDayLabels, xAxisTitle, yAxisTitle, theme) {
 var dayLabels = [];
 var xAxisLabelsRotationValue = 0;
 
 // Set the x axis labels. We do this only if the client tell us to do so.
 // Otherwise the default values will be used, which is 1, 2, 3, ...
 if (useDayLabels) {
  xAxisLabelsRotationValue = -15;
  setDayLabels(startDate, days, dayLabels);
 }
 
 // Set the chart theme.
 chart.setTheme(themeClasses[theme.toLowerCase()]);

 // Set the chart plots and axies.
 chart.addPlot("default", {
  type: "Lines", 
  animate: { 
   duration: animationDuration, 
   easing: dojox.fx.easing.linear}, 
  markers: true, 
  tension: tensionValue}
 );
 
 chart.addAxis("x", {
  minorTicks: true, 
  minorTickStep: 1, 
  stroke: "gray", 
  font: "normal normal normal 8pt Tahoma", 
  title: xAxisTitle, 
  titleOrientation: "away", 
  titleFontColor: "gray", 
  labels: dayLabels, 
  maxLabelSize: 80, 
  rotation: xAxisLabelsRotationValue}
 );
 
 chart.addAxis("y", {
  vertical: true, 
  includeZero: true, 
  minorTicks: false, 
  stroke: "gray", 
  font: "normal normal normal 8pt Tahoma", 
  title: yAxisTitle, 
  titleFontColor: "gray"}
 );
}

// Sets the values for ideal burndown.
var getIdealBurndownData = function(days, tasksRemainingInitValue) {
 var values = new Array();
 for (var i = 0; i < days + 1; i++) {
  values.push(tasksRemainingInitValue - (tasksRemainingInitValue / days) * i);
 }
 return values;
}

// Sets the values for a data series.
var plotChartData = function(chart, args) {
 chart.addSeries("Ideal Breakdown", getIdealBurndownData(args.days, args.tasksRemaining[0]));
 chart.addSeries("Remaining Tasks", args.tasksRemaining);
 chart.addSeries("Verified Tasks", args.tasksVerified);
}

// Initializes the holiday dates array from a string array.
var stringsToDates = function(strings, dates) {
 for (var i = 0; i < strings.length; i++) {
  dates.push(new Date(strings[i]));
 }
}

// Draws the burndown chart.
var drawBurndownInternal = function(args) {
 // Initialize the variables.
 var titleColor = args.titleColor;
 if (!titleColor) titleColor = defaultTitleColor;
 
 var chartName = "burndownChart";
 if (args.chartName) chartName = args.chartName;
 
 var legendName = "burndownChartLegend";
 if (args.legendName) legendName = args.legendName;
 
 var xAxisTitle = args.xAxisTitle;
 if (!xAxisTitle) xAxisTitle = defaultXAxisTitle;
 
 var yAxisTitle = args.yAxisTitle;
 if (!yAxisTitle) yAxisTitle = defaultYAxisTitle;

 var tensionValue = "S";
 if (args.curvedLines == false) tensionValue = "";
 
 var animationDuration = 500;
 if (args.animate == false) animationDuration = 0;
 
 var useDayLabels = true;
 if (!isNaN(args.useDayLabels)) useDayLabels = args.useDayLabels;
 
 if (args.holidays != null && args.holidays.length > 0) {
  stringsToDates(args.holidays, holidayDatesArray);
 }
 
 var theme = "WatersEdge";
 if (args.theme) theme = args.theme;

 // Create the chart object.
 var burndownChart = new dojox.charting.Chart2D(chartName, {
  title: args.title,
  titlePos: "top",
  titleGap: 25,
  titleFont: "normal normal bold 12pt Arial",
  titleFontColor: titleColor});

 initChart(burndownChart, tensionValue, animationDuration, args.startDate, args.days, useDayLabels, xAxisTitle, yAxisTitle, theme);
 plotChartData(burndownChart, args);

 // Set tooltips, magnification, etc...
 new dojox.charting.action2d.Magnify(burndownChart, "default", {scale: 2});
 //new dojox.charting.action2d.Tooltip(burndownChart, "default");
 //new dojox.charting.action2d.Highlight(burndownChart, "default");
 
 // Render the chart.
 burndownChart.render();

 // Create the legend.
 var legend = new dojox.charting.widget.Legend({
   chart: burndownChart, 
   horizontal: false, 
   style: "font-family:arial;color:black;font-size:10px;"}, 
  legendName);
}

// The function gets called after the Dojo is initialized.
var onDojoInitialized = function() {
 dojoInitialized = true;
 
 themeClasses["wetland"] = dojox.charting.themes.Wetland;
 themeClasses["tufte"] = dojox.charting.themes.Tufte;
 themeClasses["shrooms"] = dojox.charting.themes.Shrooms;
 themeClasses["watersedge"] = dojox.charting.themes.WatersEdge;
 
 for (var i = 0; i < chartArgs.length; i++) {
  drawBurndownInternal(chartArgs[i]);
 }
}

// The clients call this function to draw the burndown charts.
var drawBurndown = function(args) {
 if (!dojoInitialized) {
  $.getScript('http://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojo/dojo.xd.js', function() {
   dojo.require("dojo.fx");
   dojo.require("dojox.fx.easing");
   dojo.require("dojox.charting.Chart2D");
   dojo.require("dojox.charting.action2d.Magnify");
   dojo.require("dojox.charting.action2d.Tooltip");
   dojo.require("dojox.charting.action2d.Highlight");
   dojo.require("dojox.charting.widget.Legend");
   dojo.require("dojox.charting.themes.WatersEdge");
   dojo.require("dojox.charting.themes.Wetland");
   dojo.require("dojox.charting.themes.Tufte");
   dojo.require("dojox.charting.themes.Shrooms");
   
   // This is a HACK!
   // When I tried 2 charts on the same page, only one chart was rendered.
   // I guess this is because of the second request is not going into onDojoInitialized function.
   // So as a hack, we cache the arguments and then after the Dojo is initialized we start drawing the charts.
   chartArgs.push(args);
   
   dojo.ready(function() { onDojoInitialized(); });
  });
 } else {
  drawBurndownInternal(args);
 }
};

Here is the HTML code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<body style="font-family: Arial;border: 0 none;">
 <script src="http://code.jquery.com/jquery-latest.js"></script>
 <script type="text/javascript" src="burndownChart.js"></script>
 <table border="0"><tr valign="top">
  <td><div id="burndownChart" style="width: 800px; height: 400px;"></div></td>
  <td><div style="width: 100px; margin-top:100px; border: 1px solid #C8C8C8; padding: 5px; background-color: rgba(255, 255, 221, 0.8);"><div id="burndownChartLegend" ></div>
  </div></td>
 </tr></table>

 <script type="text/javascript">
  drawBurndown({
   title: "Linking 2.0 Sprint 9 Burdown Chart",
   startDate: "2011/10/24",
   days: 15,
   tasksRemaining: [50,49,49,53,57,54,33,31,28,28,27,24,23,23,23,16],
   tasksVerified: [0,1,1,5,6,6,7,13,16,16,17,20,21,21,21,30]
  });
 </script> 
</body>

</html>