Burndown Chart

- Infrastructure rewritten with the following benefits:
  - greater performance
  - reduced memory footprint
  - generalized periods (daily, weekly, monthly)
- Currently graphs do not render, daily chart is still the old implementation
This commit is contained in:
Paul Beckingham 2010-11-22 01:47:10 -05:00
parent 652b7d9c8d
commit c4a5a75fd6
2 changed files with 738 additions and 99 deletions

View file

@ -26,40 +26,678 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#include <iostream> // TODO Remove #include <iostream> // TODO Remove
//#include <iomanip>
#include <sstream> #include <sstream>
//#include <fstream>
#include <algorithm> #include <algorithm>
//#include <sys/types.h>
//#include <stdio.h>
//#include <unistd.h>
//#include <stdlib.h>
//#include <pwd.h>
//#include <time.h>
#include <math.h> #include <math.h>
#include "Context.h" #include <Context.h>
//#include "Directory.h" #include <Date.h>
//#include "File.h" #include <text.h>
#include "Date.h" #include <util.h>
//#include "Duration.h" #include <main.h>
//#include "Table.h"
#include "text.h"
#include "util.h"
#include "main.h"
//#ifdef HAVE_LIBNCURSES
//#include <ncurses.h>
//#endif
extern Context context; extern Context context;
// Helper macro. // Helper macro.
#define LOC(y,x) (((y) * (width + 1)) + (x)) #define LOC(y,x) (((y) * (width + 1)) + (x))
////////////////////////////////////////////////////////////////////////////////
class Bar
{
public:
Bar ();
Bar (const Bar&);
Bar& operator= (const Bar&);
~Bar ();
public:
int offset; // from left of chart
std::string major; // x-axis label, major (year/-/month)
std::string minor; // x-axis label, minor (month/week/day)
int pending; // Number of pending task in period
int started; // Number of started task in period
int done; // Number of done task in period
int added; // Number added in period
int removed; // Number removed in period
};
////////////////////////////////////////////////////////////////////////////////
Bar::Bar ()
: offset (0)
, major ("")
, minor ("")
, pending (0)
, started (0)
, done (0)
, added (0)
, removed (0)
{
}
////////////////////////////////////////////////////////////////////////////////
Bar::Bar (const Bar& other)
{
*this = other;
}
////////////////////////////////////////////////////////////////////////////////
Bar& Bar::operator= (const Bar& other)
{
if (this != &other)
{
offset = other.offset;
major = other.major;
minor = other.minor;
pending = other.pending;
started = other.started;
done = other.done;
added = other.added;
removed = other.removed;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Bar::~Bar ()
{
}
////////////////////////////////////////////////////////////////////////////////
class Chart
{
public:
Chart (char);
Chart (const Chart&);
Chart& operator= (const Chart&);
~Chart ();
void scan (std::vector <Task>&);
std::string render ();
private:
void generateBars ();
void optimizeGrid ();
Date quantize (const Date&);
Date increment (const Date&);
Date decrement (const Date&);
void maxima ();
public:
int width;
int height;
int graph_width;
int graph_height;
int max_value;
int max_label;
std::vector <int> labels;
int estimated_bars;
int actual_bars;
std::map <time_t, Bar> bars;
Date earliest;
char period; // D, W, M.
std::string grid;
float find_rate;
float fix_rate;
std::string completion;
};
////////////////////////////////////////////////////////////////////////////////
Chart::Chart (char type)
{
// How much space is there to render in? This chart will occupy the
// maximum space, and the width drives various other parameters.
width = context.getWidth ();
height = context.getHeight () - 1; // Allow for new line with prompt.
std::cout << "# width " << width << "\n";
std::cout << "# height " << height << "\n";
max_value = 0;
std::cout << "# max_value " << max_value << "\n";
max_label = 1;
std::cout << "# max_label " << max_label << "\n";
graph_height = height - 7;
std::cout << "# graph_height " << graph_height << "\n";
graph_width = width - max_label - 14;
std::cout << "# graph_width " << graph_width << "\n";
// Estimate how many 'bars' can be dsplayed. This will help subset a
// potentially enormous data set.
estimated_bars = (width - 1 - 14) / 3;
std::cout << "# estimated_bars " << estimated_bars << "\n";
actual_bars = 0;
std::cout << "# actual_bars " << actual_bars << "\n";
period = type;
std::cout << "# period " << period << "\n";
// Rates are calculated last.
find_rate = 0.0;
fix_rate = 0.0;
}
////////////////////////////////////////////////////////////////////////////////
Chart::Chart (const Chart& other)
{
*this = other;
}
////////////////////////////////////////////////////////////////////////////////
Chart& Chart::operator= (const Chart& other)
{
if (this != &other)
{
width = other.width;
height = other.height;
graph_width = other.graph_width;
graph_height = other.graph_height;
max_value = other. max_value;
max_label = other.max_label;
labels = other.labels;
estimated_bars = other.estimated_bars;
actual_bars = other.actual_bars;
bars = other.bars;
earliest = other.earliest;
period = other.period;
grid = other.grid;
find_rate = other.find_rate;
fix_rate = other.fix_rate;
completion = other.completion;
}
return *this;
}
////////////////////////////////////////////////////////////////////////////////
Chart::~Chart ()
{
}
////////////////////////////////////////////////////////////////////////////////
void Chart::scan (std::vector <Task>& tasks)
{
generateBars ();
std::cout << "# loaded " << tasks.size () << " tasks\n";
// Not quantized, so that "while (xxx < now)" is inclusive.
Date now;
time_t epoch;
foreach (task, tasks)
{
// The entry date is when the counting starts.
Date from = quantize (Date (task->get ("entry")));
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ())
++bars[epoch].added;
// e--> e--s-->
// ppp> pppsss>
Task::status status = task->getStatus ();
if (status == Task::pending ||
status == Task::waiting)
{
if (task->has ("start"))
{
Date start = quantize (Date (task->get ("start")));
while (from < start)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
while (from < now)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].started;
from = increment (from);
}
}
else
{
while (from < now)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
}
}
// e--C e--s--C
// pppd> pppsssd>
else if (status == Task::completed)
{
// Truncate history so it starts at 'earliest' for completed tasks.
Date end = quantize (Date (task->get ("end")));
epoch = end.toEpoch ();
if (bars.find (epoch) != bars.end ())
++bars[epoch].removed;
if (end < earliest)
{
epoch = earliest.toEpoch ();
if (bars.find (epoch) != bars.end ())
++bars[epoch].done;
continue;
}
if (task->has ("start"))
{
Date start = quantize (Date (task->get ("start")));
while (from < start)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
while (from < end)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].started;
from = increment (from);
}
while (from < now)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].done;
from = increment (from);
}
}
else
{
Date end = quantize (Date (task->get ("end")));
while (from < end)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
while (from < now)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].done;
from = increment (from);
}
}
}
// e--D e--s--D
// ppp pppsss
else if (status == Task::deleted)
{
// Skip old deleted tasks.
Date end = quantize (Date (task->get ("end")));
epoch = end.toEpoch ();
if (bars.find (epoch) != bars.end ())
++bars[epoch].removed;
if (end < earliest)
continue;
if (task->has ("start"))
{
Date start = quantize (Date (task->get ("start")));
while (from < start)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
while (from < end)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].started;
from = increment (from);
}
}
else
{
Date end = quantize (Date (task->get ("end")));
while (from < end)
{
epoch = from.toEpoch ();
if (bars.find (epoch) != bars.end ()) ++bars[epoch].pending;
from = increment (from);
}
}
}
}
// Size the data.
maxima ();
}
////////////////////////////////////////////////////////////////////////////////
// Graph should render like this:
// +---------------------------------------------------------------------+
// | |
// | 20 | |
// | | dd dd dd dd dd dd dd dd |
// | | dd dd dd dd dd dd dd dd dd dd dd dd dd dd |
// | | pp pp ss ss ss ss ss ss ss ss ss dd dd dd dd dd dd dd Done |
// | 10 | pp pp pp pp pp pp ss ss ss ss ss ss dd dd dd dd dd ss Started|
// | | pp pp pp pp pp pp pp pp pp pp pp ss ss ss ss dd dd pp Pending|
// | | pp pp pp pp pp pp pp pp pp pp pp pp pp pp pp ss dd |
// | | pp pp pp pp pp pp pp pp pp pp pp pp pp pp pp pp pp |
// | 0 +---------------------------------------------------- |
// | 21 22 23 24 25 26 27 28 29 30 31 01 02 03 04 05 06 |
// | July August |
// | |
// | Find rate 1.7/d Estimated completion 8/12/2010 |
// | Fix rate 1.3/d |
// +---------------------------------------------------------------------+
std::string Chart::render ()
{
if (graph_height < 5 || // a 4-line graph is essentially unreadable.
graph_width < 2) // A single-bar graph is useless.
{
return "Terminal window too small to draw a graph.\n";
}
// Create a grid, folded into a string.
// TODO Upgrade grid to a vector of strings, for simpler optimization.
grid = "";
for (int i = 0; i < height; ++i)
grid += std::string (width, ' ') + "\n";
// Legend.
grid.replace (LOC (graph_height / 2 - 1, width - 10), 10, "dd Done ");
grid.replace (LOC (graph_height / 2, width - 10), 10, "ss Started");
grid.replace (LOC (graph_height / 2 + 1, width - 10), 10, "pp Pending");
// Draw y-axis.
for (int i = 0; i < graph_height; ++i)
grid.replace (LOC (i + 1, max_label + 1), 1, "|");
// TODO Draw y-axis labels.
// char label [12];
// sprintf (label, "%*d", max_label, labels[2]);
// grid.replace (LOC (1, max_label - strlen (label)), strlen (label), label);
// sprintf (label, "%*d", max_label, labels[1]);
// grid.replace (LOC (1 + (graph_height / 2), max_label - strlen (label)), strlen (label), label);
grid.replace (LOC (graph_height + 1, max_label - 1), 1, "0");
// Draw x-axis.
grid.replace (LOC (height - 6, max_label + 1), 1, "+");
grid.replace (LOC (height - 6, max_label + 2), graph_width, std::string (graph_width, '-'));
// TODO Draw x-axis labels.
// Draw rates.
char rate[12];
sprintf (rate, "%.1f", find_rate);
grid.replace (LOC (height - 2, max_label + 3), 13 + strlen (rate), std::string ("Find rate: ") + rate + "/d");
sprintf (rate, "%.1f", fix_rate);
grid.replace (LOC (height - 1, max_label + 3), 13 + strlen (rate), std::string ("Fix rate: ") + rate + "/d");
// Draw completion date.
if (completion.length ())
grid.replace (LOC (height - 2, max_label + 27), 22 + completion.length (), "Estimated completion: " + completion);
optimizeGrid ();
// Colorize the grid.
Color color_pending (context.config.get ("color.burndown.pending"));
Color color_done (context.config.get ("color.burndown.done"));
Color color_started (context.config.get ("color.burndown.started"));
// Replace dd, ss, pp with colored strings.
std::string::size_type i;
while ((i = grid.find ("pp")) != std::string::npos)
grid.replace (i, 2, color_pending.colorize (" "));
while ((i = grid.find ("ss")) != std::string::npos)
grid.replace (i, 2, color_started.colorize (" "));
while ((i = grid.find ("dd")) != std::string::npos)
grid.replace (i, 2, color_done.colorize (" "));
return grid;
}
////////////////////////////////////////////////////////////////////////////////
// grid =~ /\s+$//g
void Chart::optimizeGrid ()
{
std::string::size_type ws;
while ((ws = grid.find (" \n")) != std::string::npos)
{
std::string::size_type non_ws = ws;
while (grid[non_ws] == ' ')
--non_ws;
// std::cout << "# WS at EOL " << non_ws + 1 << "-" << ws << "\n";
grid.replace (non_ws + 1, ws - non_ws + 1, "\n");
}
}
////////////////////////////////////////////////////////////////////////////////
Date Chart::quantize (const Date& input)
{
if (period == 'D') return input.startOfDay ();
if (period == 'W') return input.startOfWeek ();
if (period == 'M') return input.startOfMonth ();
return input;
}
////////////////////////////////////////////////////////////////////////////////
Date Chart::increment (const Date& input)
{
// Move to the next period.
int d = input.day ();
int m = input.month ();
int y = input.year ();
int days;
switch (period)
{
case 'D':
if (++d > Date::daysInMonth (m, y))
{
d = 1;
if (++m == 13)
{
m = 1;
++y;
}
}
break;
case 'W':
d += 7;
days = Date::daysInMonth (m, y);
if (d > days)
{
d -= days;
if (++m == 13)
{
m = 1;
++y;
}
}
break;
case 'M':
d = 1;
if (++m == 13)
{
m = 1;
++y;
}
break;
}
return Date (m, d, y, 0, 0, 0);
}
////////////////////////////////////////////////////////////////////////////////
Date Chart::decrement (const Date& input)
{
// Move to the previous period.
int d = input.day ();
int m = input.month ();
int y = input.year ();
switch (period)
{
case 'D':
if (--d == 0)
{
if (--m == 0)
{
m = 12;
--y;
}
d = Date::daysInMonth (m, y);
}
break;
case 'W':
d -= 7;
if (d < 1)
{
if (--m == 0)
{
m = 12;
y--;
}
d += Date::daysInMonth (m, y);
}
break;
case 'M':
d = 1;
if (--m == 0)
{
m = 12;
--y;
}
break;
}
return Date (m, d, y, 0, 0, 0);
}
////////////////////////////////////////////////////////////////////////////////
// Do 'bars[epoch] = Bar' for every bar that may appear on a chart.
void Chart::generateBars ()
{
Bar bar;
// Determine the last bar date.
Date cursor;
switch (period)
{
case 'D': cursor = Date ().startOfDay (); break;
case 'W': cursor = Date ().startOfWeek (); break;
case 'M': cursor = Date ().startOfMonth (); break;
}
// Iterate and determine all the other bar dates.
char str[12];
for (int i = 0; i < estimated_bars; ++i)
{
// Create the major and minor labels.
switch (period)
{
case 'D': // month/day
{
std::string month = Date::monthName (cursor.month ());
bar.major = month.substr (0, 3);
sprintf (str, "%02d", cursor.day ());
bar.minor = str;
}
break;
case 'W': // year/week
sprintf (str, "%d", cursor.year ());
bar.major = str;
sprintf (str, "%02d", cursor.weekOfYear (0));
bar.minor = str;
break;
case 'M': // year/month
sprintf (str, "%d", cursor.year ());
bar.major = str;
sprintf (str, "%02d", cursor.month ());
bar.minor = str;
break;
}
bar.offset = i;
bars[cursor.toEpoch ()] = bar;
// Record the earliest date, for use as a cutoff when scanning data.
earliest = cursor;
// Move to the previous period.
cursor = decrement (cursor);
}
std::cout << "# Bar count " << bars.size () << "\n";
std::cout << "# earliest " << earliest.toString ("YMD") << "\n";
}
////////////////////////////////////////////////////////////////////////////////
void Chart::maxima ()
{
max_value = 0;
max_label = 1;
std::map <time_t, Bar>::iterator it;
for (it = bars.begin (); it != bars.end (); it++)
{
// Determine max_label.
int total = it->second.pending +
it->second.started +
it->second.done;
// Determine max_value.
if (total > max_value)
max_value = total;
int length = (int) log10 ((double) total) + 1;
if (length > max_label)
max_label = length;
}
// How many bars can be shown?
actual_bars = (width - max_label - 14) / 3;
graph_width = width - max_label - 14;
std::cout << "# max_value " << max_value << "\n";
std::cout << "# max_label " << max_label << "\n";
std::cout << "# actual_bars " << actual_bars << "\n";
std::cout << "# graph_width " << graph_width << "\n";
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Given the vertical chart area size (height), the largest value (value), // Given the vertical chart area size (height), the largest value (value),
// populate a vector of labels for the y axis. // populate a vector of labels for the y axis.
// TODO Make this a member of Chart.
void calculateYAxis (std::vector <int>& labels, int height, int value) void calculateYAxis (std::vector <int>& labels, int height, int value)
{ {
/* /*
@ -613,6 +1251,37 @@ int handleReportBurndownWeekly (std::string& outs)
{ {
int rc = 0; int rc = 0;
if (context.hooks.trigger ("pre-burndown-command"))
{
// Scan the pending tasks, applying any filter.
std::vector <Task> tasks;
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
// Create a chart, scan the tasks, then render.
Chart chart ('W');
chart.scan (tasks);
std::map <time_t, Bar>::iterator it;
for (it = chart.bars.begin (); it != chart.bars.end (); ++it)
std::cout << "# bar " << Date (it->first).toString ("YMD")
<< " offset=" << it->second.offset
<< " major=" << it->second.major
<< " minor=" << it->second.minor
<< " pending=" << it->second.pending
<< " started=" << it->second.started
<< " done=" << it->second.done
<< " added=" << it->second.added
<< " removed=" << it->second.removed
<< "\n";
outs = chart.render ();
context.hooks.trigger ("post-burndown-command");
}
return rc; return rc;
} }
@ -621,6 +1290,37 @@ int handleReportBurndownMonthly (std::string& outs)
{ {
int rc = 0; int rc = 0;
if (context.hooks.trigger ("pre-burndown-command"))
{
// Scan the pending tasks, applying any filter.
std::vector <Task> tasks;
context.tdb.lock (context.config.getBoolean ("locking"));
handleRecurrence ();
context.tdb.load (tasks, context.filter);
context.tdb.commit ();
context.tdb.unlock ();
// Create a chart, scan the tasks, then render.
Chart chart ('M');
chart.scan (tasks);
std::map <time_t, Bar>::iterator it;
for (it = chart.bars.begin (); it != chart.bars.end (); ++it)
std::cout << "# bar " << Date (it->first).toString ("YMD")
<< " offset=" << it->second.offset
<< " major=" << it->second.major
<< " minor=" << it->second.minor
<< " pending=" << it->second.pending
<< " started=" << it->second.started
<< " done=" << it->second.done
<< " added=" << it->second.added
<< " removed=" << it->second.removed
<< "\n";
outs = chart.render ();
context.hooks.trigger ("post-burndown-command");
}
return rc; return rc;
} }

View file

@ -25,30 +25,13 @@
// //
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//#include <iostream>
//#include <iomanip>
#include <sstream> #include <sstream>
//#include <fstream>
//#include <sys/types.h>
//#include <stdio.h>
//#include <unistd.h>
//#include <stdlib.h>
//#include <pwd.h>
//#include <time.h>
#include "Context.h" #include <Context.h>
//#include "Directory.h" #include <Table.h>
//#include "File.h" #include <text.h>
//#include "Date.h" #include <util.h>
//#include "Duration.h" #include <main.h>
#include "Table.h"
#include "text.h"
#include "util.h"
#include "main.h"
//#ifdef HAVE_LIBNCURSES
//#include <ncurses.h>
//#endif
extern Context context; extern Context context;
@ -84,21 +67,14 @@ int handleReportHistoryMonthly (std::string& outs)
groups[epoch] = 0; groups[epoch] = 0;
// Every task has an entry date. // Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ()) ++addedGroup[epoch];
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date. // All deleted tasks have an end date.
if (task->getStatus () == Task::deleted) if (task->getStatus () == Task::deleted)
{ {
epoch = end.startOfMonth ().toEpoch (); epoch = end.startOfMonth ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++deletedGroup[epoch];
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
} }
// All completed tasks have an end date. // All completed tasks have an end date.
@ -106,11 +82,7 @@ int handleReportHistoryMonthly (std::string& outs)
{ {
epoch = end.startOfMonth ().toEpoch (); epoch = end.startOfMonth ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++completedGroup[epoch];
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
} }
} }
@ -257,21 +229,14 @@ int handleReportHistoryAnnual (std::string& outs)
groups[epoch] = 0; groups[epoch] = 0;
// Every task has an entry date. // Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ()) ++addedGroup[epoch];
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date. // All deleted tasks have an end date.
if (task->getStatus () == Task::deleted) if (task->getStatus () == Task::deleted)
{ {
epoch = end.startOfYear ().toEpoch (); epoch = end.startOfYear ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++deletedGroup[epoch];
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
} }
// All completed tasks have an end date. // All completed tasks have an end date.
@ -279,11 +244,7 @@ int handleReportHistoryAnnual (std::string& outs)
{ {
epoch = end.startOfYear ().toEpoch (); epoch = end.startOfYear ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++completedGroup[epoch];
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
} }
} }
@ -427,21 +388,14 @@ int handleReportGHistoryMonthly (std::string& outs)
groups[epoch] = 0; groups[epoch] = 0;
// Every task has an entry date. // Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ()) ++addedGroup[epoch];
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date. // All deleted tasks have an end date.
if (task->getStatus () == Task::deleted) if (task->getStatus () == Task::deleted)
{ {
epoch = end.startOfMonth ().toEpoch (); epoch = end.startOfMonth ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++deletedGroup[epoch];
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
} }
// All completed tasks have an end date. // All completed tasks have an end date.
@ -449,11 +403,7 @@ int handleReportGHistoryMonthly (std::string& outs)
{ {
epoch = end.startOfMonth ().toEpoch (); epoch = end.startOfMonth ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++completedGroup[epoch];
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
} }
} }
@ -640,21 +590,14 @@ int handleReportGHistoryAnnual (std::string& outs)
groups[epoch] = 0; groups[epoch] = 0;
// Every task has an entry date. // Every task has an entry date.
if (addedGroup.find (epoch) != addedGroup.end ()) ++addedGroup[epoch];
addedGroup[epoch] = addedGroup[epoch] + 1;
else
addedGroup[epoch] = 1;
// All deleted tasks have an end date. // All deleted tasks have an end date.
if (task->getStatus () == Task::deleted) if (task->getStatus () == Task::deleted)
{ {
epoch = end.startOfYear ().toEpoch (); epoch = end.startOfYear ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++deletedGroup[epoch];
if (deletedGroup.find (epoch) != deletedGroup.end ())
deletedGroup[epoch] = deletedGroup[epoch] + 1;
else
deletedGroup[epoch] = 1;
} }
// All completed tasks have an end date. // All completed tasks have an end date.
@ -662,11 +605,7 @@ int handleReportGHistoryAnnual (std::string& outs)
{ {
epoch = end.startOfYear ().toEpoch (); epoch = end.startOfYear ().toEpoch ();
groups[epoch] = 0; groups[epoch] = 0;
++completedGroup[epoch];
if (completedGroup.find (epoch) != completedGroup.end ())
completedGroup[epoch] = completedGroup[epoch] + 1;
else
completedGroup[epoch] = 1;
} }
} }