public IGraph GetGraph(string sprintId) { var sprintFilter = " Sprint: " + sprintId; var lineDefinitions = new Dictionary<string, string> { {"#{Business Systems} -JDEdwards -Construction -Vendor" + sprintFilter, "BS.NET"}, {"#{Business Systems} #{JDEdwards}" + sprintFilter, "JDE"}, {"#{Marketing Systems}" + sprintFilter, "Marketing"}, {"#{Construction} #{Vendor}" + sprintFilter, "Headspring"} }; var fields = new[] {"id", "resolved", "Estimation", "summary", "type"}; var ignoreTypes = new[] {"Feature", "Epic", "User Story"}; var sprint = GetSprint(sprintId); var results = (MultiSearchResult) _client.QueryIssues(lineDefinitions.Keys.ToArray(), fields, 2000, 1); var issues = results.SearchResult .SelectMany(sr => sr.Issues, (sr, issue) => new Issue(lineDefinitions[sr.Search], issue)) .ToArray(); var graph = new Graph(); var ideal = graph.GenerateIdealLine(sprint.Start, sprint.End); foreach (var group in issues.GroupBy(i => i.LineLabel)) { var line = graph.AddLine(group.Key); FillLine(line, sprint, group.ToArray(), ignoreTypes); } ideal = graph.Lines.First(); if (!ideal.Begin.HasValue || !ideal.End.HasValue) return graph.Normalize(); if (DateTime.UtcNow < ideal.Begin.Value || DateTime.UtcNow > ideal.End.Value) return graph.Normalize(); foreach (var line in graph.Lines.Except(new[] {ideal}).Cast<Line>()) { if (!line.Points.Any()) continue; if (line.End.HasValue && line.End.Value > ideal.End.Value) continue; if (line.Min.HasValue && line.Min.Value < 0.01) continue; // Figure out how far to project this line... var lastTS = DateTime.UtcNow; if (lastTS > ideal.End.Value) lastTS = ideal.End.Value; if (line.End.Value > lastTS) lastTS = line.End.Value; var lastValue = line.Points.Last().Value; // Project 0 progress through this moment var point = line.AddPoint(lastValue, lastTS); point.IsProjection = true; point.OriginalValue = (float) Math.Round(lastValue); if (lastTS < ideal.End.Value) { // Project average progress through the end of the sprint var elapsed = line.End.Value.Subtract(line.Begin.Value).Ticks; var completed = line.Min.Value - line.Max.Value; if (Math.Abs(completed - 0) < 0.001) continue; var rate = completed/elapsed; // y = mx + b; // 0 = rate * x + max; // -max = rate * x; // -max / rate = x; var estimatedCompletion = line.Begin.Value.AddTicks(Convert.ToInt64(-line.Max.Value/rate)); // y = mx + b // y = rate * sprintTicks + max var sprintTicks = ideal.End.Value.Subtract(line.Begin.Value).Ticks; var estimatedPointsRemaining = rate*sprintTicks + line.Max.Value; point = estimatedCompletion > ideal.End.Value ? line.AddPoint(estimatedPointsRemaining, ideal.End.Value) : line.AddPoint(0, estimatedCompletion); point.OriginalValue = (float) Math.Round(point.Value); point.Label = "Points remaining at sprint completion: " + Convert.ToInt64(estimatedPointsRemaining); point.Description = "Estimated completion of points: " + estimatedCompletion.ToString("f"); point.Label = string.Format("{0} by {1} points", estimatedPointsRemaining >= 0 ? "Over" : "Under", Convert.ToInt64(Math.Abs(estimatedPointsRemaining))); point.IsProjection = true; } } return graph.Normalize(); }