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>