def __init__(self, placeholder, sprint, allTasks, revisions, **_): Chart.__init__(self, placeholder) days = [day for day in sprint.getDays()] now = Weekday.today() futureStarts = minOr(filter(lambda day: day > now, days), None) futureIndex = days.index(futureStarts) if futureStarts else None self.chart.defaultSeriesType = 'line' self.chart.zoomType = 'x' self.title.text = '' self.plotOptions.line.dataLabels.enabled = True self.tooltip.shared = True self.credits.enabled = False with self.xAxis as xAxis: xAxis.tickmarkPlacement = 'on' xAxis.maxZoom = 1 xAxis.title.text = 'Day' # Future bar if futureIndex is not None: xAxis.plotBands = [{ 'color': '#DDD', 'from': futureIndex - 0.75, 'to': len(days) - 0.5 }] self.yAxis.min = 0 self.yAxis.title.text = 'Hours' self.series = seriesList = [] taskSeries = { 'id': 'taskSeries', 'name': 'Tasking', 'data': [], 'color': '#4572a7', 'zIndex': 2 } seriesList.append(taskSeries) availSeries = { 'name': 'Availability', 'data': [], 'color': '#aa4643', 'zIndex': 2 } seriesList.append(availSeries) flagSeries = { 'type': 'flags', 'data': [], 'color': '#4572a7', 'shape': 'flag', 'onSeries': 'taskSeries', 'showInLegend': False, 'y': 16 } seriesList.append(flagSeries) if futureIndex == 0: futureIndex = 1 statusToday, hoursToday = None, None for day in days[:futureIndex]: tasksToday = [revisions[t.id, day] for t in allTasks] statusYesterday, hoursYesterday = statusToday, hoursToday statusToday = {t: t.status for t in tasksToday if t and not t.deleted} hoursToday = {t: t.manHours() for t in tasksToday if t and not t.deleted} taskSeries['data'].append(sum(hoursToday.values())) if hoursYesterday: hoursDiff = {t: hoursToday.get(t, 0) - hoursYesterday.get(t, 0) for t in hoursToday} largeChanges = [t for t, h in hoursDiff.iteritems() if abs(h) >= 16] if largeChanges: texts = [] for t in largeChanges: if t not in hoursYesterday: texts.append("<span style=\"color: #f00\">(New +%d)</span> %s" % (t.effectiveHours(), t.name)) elif hoursDiff[t] > 0: texts.append("<span style=\"color: #f00\">(+%d)</span> %s" % (hoursDiff[t], t.name)) else: if t.status in ('in progress', 'not started'): texts.append("<span style=\"color: #0a0\">(%d)</span> %s" % (hoursDiff[t], t.name)) elif t.status == 'complete': texts.append("<span style=\"color: #0a0\">(Complete %d)</span> %s" % (hoursDiff[t], t.name)) else: texts.append("<span style=\"color: #999\">(%s %d)</span> %s" % (statuses[t.status].getRevisionVerb(statusYesterday.get(t, 'not started')), hoursDiff[t], t.name)) flagSeries['data'].append({'x': days.index(day), 'title': alphabet[len(flagSeries['data']) % len(alphabet)], 'text': '<br>'.join(texts)}) avail = Availability(sprint) for day in days: availSeries['data'].append(avail.getAllForward(day)) setupTimeline(self, sprint, ['Projected tasking']) # Add commitment percentage to the axis label labels = self.xAxis.categories.get() for i in range(len(labels)): # For future percentages, use today's hours (i.e. don't use the projected hours) needed = taskSeries['data'][min(i, futureIndex - 1) if futureIndex else i][1] thisAvail = availSeries['data'][i][1] pcnt = "%d" % (needed * 100 / thisAvail) if thisAvail > 0 else "inf" labels[i] += "<br>%s%%" % pcnt self.xAxis.categories = labels self.xAxis.labels.formatter = "function() {return this.value.replace('inf', '\u221e');}" # Trendline data = self.series[0].data.get() dailyAvail = dict((day, avail.getAll(day)) for day in days) totalAvail = 0 for daysBack in range(1, (futureIndex or 0) + 1): midPoint = [futureIndex - daysBack, data[futureIndex - daysBack][1]] if dailyAvail[days[midPoint[0]]] > 0: daysBack = min(daysBack + 2, futureIndex) startPoint = [futureIndex - daysBack, data[futureIndex - daysBack][1]] totalAvail = sum(dailyAvail[day] for day in days[startPoint[0] : midPoint[0]]) break if totalAvail > 0 and startPoint[0] != midPoint[0]: slope = (midPoint[1] - startPoint[1]) / (midPoint[0] - startPoint[0]) slopePerAvail = slope * (midPoint[0] - startPoint[0]) / totalAvail points, total = [], midPoint[1] total = taskSeries['data'][futureIndex - 1][1] points.append([futureIndex - 1, total]) for i in range(futureIndex, len(days)): total += slopePerAvail * dailyAvail[days[i]] points.append([i, total]) seriesList.append({ 'name': 'Projected tasking', 'data': points, 'color': '#666', 'dataLabels': {'formatter': "function() {return (this.point.x == %d) ? parseInt(this.y, 10) : null;}" % (len(days) - 1)}, 'marker': {'symbol': 'circle'}, 'zIndex': 1 })