If you have been following my mini-series on understanding the TFS11 burndown chart, surely you know that in this blog post we are going to analyze how Team WebAccess calculates the ideal and actual burndown trend lines. Just as a quick recap, here’s what we’ve learned so far. In part one of the series, we have discovered how to construct a query expression which would retrieve the work items that can be used for calculating the burndown chart data for a specified iteration. Then, in part two, we’ve made good use of this query expression by using it to execute historical queries for each day in the iteration duration and to retrieve the remaining work for the specified iteration day.
This blog post is the third and final part, where we’ll see how the trend lines are calculated and why they are used. If you still haven’t read the first two posts, I’d really encourage you to go through them, as this blog post will make a lot more sense. You can find the posts here: part one, part two.
The sample data
Just to make everything a bit easier to understand and visualize, I’ve decided to use an imaginary iteration, which I’ve chosen to last between April 9th and April 29th 2012. Just as a note, I’m fully aware that people most often use two week iterations, but since this isn’t important for the purpose of our discussion, I’ve opted to use a longer iteration so we can work with more data points. Speaking of which, here are the imaginary values for the remaining work, which could have been retrieved by the code we’ve covered in parts one and two:
110, 105, 102, 92, 87, 87, 87, 65, 70, 70, 35,
55, 55, 55, 52, 52, 52, null, null, null, null
What the last four null values are saying is that we don’t have data for those iteration days, which should indicate that the iteration from the example is still in progress, and that we’ll imagine that today’s date is April 25th (since that’s the last day we retrieved the data for).
To visualize things even better, here’s a nice chart, which I’ve whipped up using Microsoft Excel:
Now, there are a few interesting things in the sample data and the above chart. The first one is, like I’ve pointed out, that the iteration is still in progress and that we don’t have remaining work data for all the days in the iteration. What’s also interesting is that we have two places in the chart that the remaining work has actually increased, instead of decreasing or remaining the same. Most typical causes of something like this is if you add new tasks during the iteration or if some tasks were underestimated and a correction was necessary. I am pointing out these subtleties because they will impact the way we calculate the ideal and actual burndown trend lines, as you’ll see in a moment.
The ideal burndown trend line
So what should the ideal burndown line look like? Which two points define it? You might be thinking that the first point should be the remaining work value on the first day of the iteration and the last point should be the last day of the iteration, where the remaining work value should equal zero. And you would be perfectly right, if it weren’t for situations like the one I’ve described just now, where someone could add a new task to the iteration or increase the remaining work value of a task you have committed to implement. Because of this, the Team WebAccess burndown chart first calculates a correction value, by which it will offset the starting point of the ideal (and actual) burndown trend line to compensate for the aforementioned situations.
The way this correction value is calculated is by iterating over the entire sequence of remaining work values and aggregating values whenever the remaining work value for an iteration day is greater than the previous day. It might sound a bit complicated, but it’s really simple stuff. Let’s see how it’s implemented in C#. First, a nice helper class that holds all the information about a data-point in the burndown chart:
class DataPoint
{
public double? Work { get; set; }
public DateTime Date { get; set; }
public double IdealTrend { get; set; }
public double ActualTrend { get; set; }
}
And now for the correction value calculation:
static void CalculateTrendlines(DataPoint[] dataPoints)
{
if (dataPoints.Where(dp => dp.Work.HasValue).Any())
{
int pointsWithValue = dataPoints.Where(dp => dp.Work.HasValue).Count();
double correction = 0;
// Iterate over data points that have a remaining work value set.
for (int i = 1; i < pointsWithValue; i++)
{
// Read the remaining work for the current and the previous day.
double currentWork = dataPoints[i].Work.Value;
double previousWork = dataPoints[i - 1].Work.Value;
// Aggregate the data if needed.
if (currentWork > previousWork)
correction += (currentWork - previousWork);
}
}
}
Like I’ve said, pretty simple stuff. We use a for loop to iterate over all the data points which have a remaining work value (that is, it’s not null) and whenever the current remaining work value is greater than the day before, we accumulate the difference. If we add the correction value to the remaining work value on the first iteration day, we then know both points required to draw the ideal burndown trend line:
// Calculate the remaining work value for the first day.
double firstValue = dataPoints[0].Work + correction;
// Get the iteration duration (in days).
int lastPointIndex = dataPoints.Count() - 1;
int iterationDays = (dataPoints[lastPointIndex].Date - dataPoints[0].Date).Days;
// The value by which the ideal burndown value should decrease each day.
double idealDelta = firstValue / iterationDays;
// Set the ideal trend value for the first day.
dataPoints[0].IdealTrend = firstValue;
// Set the ideal trend values for the rest of the iteration.
for (int i = 1; i <= lastPointIndex; i++)
dataPoints[i].IdealTrend = dataPoints[i - 1].IdealTrend - idealDelta;
Once we’ve calculated the ideal trend line values, we can overlay that data over the sample burndown chart:
By looking at this chart, you can immediately see that the team was progressing just fine until about April 20th, after which the remaining work stopped decreasing and the team fell behind schedule. If we’d like to know how much we’re falling behind (or if we’re ahead of schedule), as well as where we’ll be work-wise at the end of the iteration, we need to have a look at the actual burndown trend line.
The actual burndown trend line
Similarly to the ideal burndown trend line, the actual burndown trend line will have the same starting point. That is, the starting point will again be the remaining work value on the first day, increased by the correction value. The end point will differ, however, and it’s value will be the remaining work value on the last day that we were able to retrieve data (that is, including today, if the iteration is still in progress). It’s simple to find the value of the end point:
// Calculate the remaining work for the last day we have data on.
double lastValue = dataPoints[pointsWithValue - 1].Work.Value;
// The value by which the actual burndown value should decrease each day.
double actualDelta = (firstValue - lastValue) / (pointsWithValue - 1);
// Set the actual trend value for the first day.
dataPoints[0].ActualTrend = firstValue;
// Set the ideal trend values for the rest of the iteration.
for (int i = 1; i <= lastPointIndex; i++)
dataPoints[i].ActualTrend = dataPoints[i - 1].ActualTrend - actualDelta;
Now there’s nothing left to do but to overlay the actual burndown trend line over the previous burndown chart, which results in a chart similar to this one:
By looking at the chart, we can project that the team will have about 30 hours of remaining work left by the end of the iteration. You can also see that the actual burndown trend line just barely touches the burndown chart on the last day that we’ve retrieved data for (that is, today, April 25th).
Source code listing
Here’s the source code listing for the entire method that calculates both the actual and ideal burndown trend lines:
static void CalculateTrendlines(List<DataPoint> dataPoints)
{
if (dataPoints.Any() && dataPoints.Where(dp => dp.Effort.HasValue).Any())
{
int pointsWithValue = dataPoints.Where(dp => dp.Effort.HasValue).Count();
double firstValue = dataPoints[0].Effort.Value;
// Calculate the correction value and use it to
// update the starting value for the trend lines.
for (int i = 1; i < pointsWithValue; i++)
{
double currentEffort = dataPoints[i].Effort.Value;
double previousEffort = dataPoints[i - 1].Effort.Value;
if (currentEffort > previousEffort)
firstValue += (currentEffort - previousEffort);
}
// Calculate the iteration duration (in days).
int lastPointIndex = dataPoints.Count() - 1;
int days = (dataPoints[lastPointIndex].Date - dataPoints[0].Date).Days;
// Calculate the delta for the ideal burndown trend line.
double idealDelta = firstValue / days;
// Calculate the delta for the actual burndown trend line.
double lastValue = dataPoints[pointsWithValue - 1].Effort.Value;
double actualDelta = (firstValue - lastValue) / (pointsWithValue - 1);
// Set the values for the first day of the iteration.
dataPoints[0].ActualTrend = firstValue;
dataPoints[0].IdealTrend = firstValue;
// Set the trend line data for the rest of the iteration days.
for (int i = 1; i <= lastPointIndex; i++)
{
dataPoints[i].IdealTrend = dataPoints[i - 1].IdealTrend - idealDelta;
dataPoints[i].ActualTrend = dataPoints[i - 1].ActualTrend - actualDelta;
}
}
}
Conclusion
This is the end of the blog post series on understanding how the Team WebAccess burndown chart works under the hood. I hope that it’s been interesting to read as it was interesting for me to research and reverse-engineer! I you happen to have any feedback or ideas on how to improve the burndown chart to make it more useful, I’d be very interested to hear what you have to say. See you soon!




