mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-20 04:13:07 +02:00
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:
parent
652b7d9c8d
commit
c4a5a75fd6
2 changed files with 738 additions and 99 deletions
742
src/burndown.cpp
742
src/burndown.cpp
|
@ -26,40 +26,678 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <iostream> // TODO Remove
|
||||
//#include <iomanip>
|
||||
#include <sstream>
|
||||
//#include <fstream>
|
||||
#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 "Context.h"
|
||||
//#include "Directory.h"
|
||||
//#include "File.h"
|
||||
#include "Date.h"
|
||||
//#include "Duration.h"
|
||||
//#include "Table.h"
|
||||
#include "text.h"
|
||||
#include "util.h"
|
||||
#include "main.h"
|
||||
|
||||
//#ifdef HAVE_LIBNCURSES
|
||||
//#include <ncurses.h>
|
||||
//#endif
|
||||
#include <Context.h>
|
||||
#include <Date.h>
|
||||
#include <text.h>
|
||||
#include <util.h>
|
||||
#include <main.h>
|
||||
|
||||
extern Context context;
|
||||
|
||||
// Helper macro.
|
||||
#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),
|
||||
// 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)
|
||||
{
|
||||
/*
|
||||
|
@ -613,6 +1251,37 @@ int handleReportBurndownWeekly (std::string& outs)
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -621,6 +1290,37 @@ int handleReportBurndownMonthly (std::string& outs)
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,30 +25,13 @@
|
|||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//#include <iostream>
|
||||
//#include <iomanip>
|
||||
#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 "Directory.h"
|
||||
//#include "File.h"
|
||||
//#include "Date.h"
|
||||
//#include "Duration.h"
|
||||
#include "Table.h"
|
||||
#include "text.h"
|
||||
#include "util.h"
|
||||
#include "main.h"
|
||||
|
||||
//#ifdef HAVE_LIBNCURSES
|
||||
//#include <ncurses.h>
|
||||
//#endif
|
||||
#include <Context.h>
|
||||
#include <Table.h>
|
||||
#include <text.h>
|
||||
#include <util.h>
|
||||
#include <main.h>
|
||||
|
||||
extern Context context;
|
||||
|
||||
|
@ -84,21 +67,14 @@ int handleReportHistoryMonthly (std::string& outs)
|
|||
groups[epoch] = 0;
|
||||
|
||||
// Every task has an entry date.
|
||||
if (addedGroup.find (epoch) != addedGroup.end ())
|
||||
addedGroup[epoch] = addedGroup[epoch] + 1;
|
||||
else
|
||||
addedGroup[epoch] = 1;
|
||||
++addedGroup[epoch];
|
||||
|
||||
// All deleted tasks have an end date.
|
||||
if (task->getStatus () == Task::deleted)
|
||||
{
|
||||
epoch = end.startOfMonth ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (deletedGroup.find (epoch) != deletedGroup.end ())
|
||||
deletedGroup[epoch] = deletedGroup[epoch] + 1;
|
||||
else
|
||||
deletedGroup[epoch] = 1;
|
||||
++deletedGroup[epoch];
|
||||
}
|
||||
|
||||
// All completed tasks have an end date.
|
||||
|
@ -106,11 +82,7 @@ int handleReportHistoryMonthly (std::string& outs)
|
|||
{
|
||||
epoch = end.startOfMonth ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (completedGroup.find (epoch) != completedGroup.end ())
|
||||
completedGroup[epoch] = completedGroup[epoch] + 1;
|
||||
else
|
||||
completedGroup[epoch] = 1;
|
||||
++completedGroup[epoch];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,21 +229,14 @@ int handleReportHistoryAnnual (std::string& outs)
|
|||
groups[epoch] = 0;
|
||||
|
||||
// Every task has an entry date.
|
||||
if (addedGroup.find (epoch) != addedGroup.end ())
|
||||
addedGroup[epoch] = addedGroup[epoch] + 1;
|
||||
else
|
||||
addedGroup[epoch] = 1;
|
||||
++addedGroup[epoch];
|
||||
|
||||
// All deleted tasks have an end date.
|
||||
if (task->getStatus () == Task::deleted)
|
||||
{
|
||||
epoch = end.startOfYear ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (deletedGroup.find (epoch) != deletedGroup.end ())
|
||||
deletedGroup[epoch] = deletedGroup[epoch] + 1;
|
||||
else
|
||||
deletedGroup[epoch] = 1;
|
||||
++deletedGroup[epoch];
|
||||
}
|
||||
|
||||
// All completed tasks have an end date.
|
||||
|
@ -279,11 +244,7 @@ int handleReportHistoryAnnual (std::string& outs)
|
|||
{
|
||||
epoch = end.startOfYear ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (completedGroup.find (epoch) != completedGroup.end ())
|
||||
completedGroup[epoch] = completedGroup[epoch] + 1;
|
||||
else
|
||||
completedGroup[epoch] = 1;
|
||||
++completedGroup[epoch];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,21 +388,14 @@ int handleReportGHistoryMonthly (std::string& outs)
|
|||
groups[epoch] = 0;
|
||||
|
||||
// Every task has an entry date.
|
||||
if (addedGroup.find (epoch) != addedGroup.end ())
|
||||
addedGroup[epoch] = addedGroup[epoch] + 1;
|
||||
else
|
||||
addedGroup[epoch] = 1;
|
||||
++addedGroup[epoch];
|
||||
|
||||
// All deleted tasks have an end date.
|
||||
if (task->getStatus () == Task::deleted)
|
||||
{
|
||||
epoch = end.startOfMonth ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (deletedGroup.find (epoch) != deletedGroup.end ())
|
||||
deletedGroup[epoch] = deletedGroup[epoch] + 1;
|
||||
else
|
||||
deletedGroup[epoch] = 1;
|
||||
++deletedGroup[epoch];
|
||||
}
|
||||
|
||||
// All completed tasks have an end date.
|
||||
|
@ -449,11 +403,7 @@ int handleReportGHistoryMonthly (std::string& outs)
|
|||
{
|
||||
epoch = end.startOfMonth ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (completedGroup.find (epoch) != completedGroup.end ())
|
||||
completedGroup[epoch] = completedGroup[epoch] + 1;
|
||||
else
|
||||
completedGroup[epoch] = 1;
|
||||
++completedGroup[epoch];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,21 +590,14 @@ int handleReportGHistoryAnnual (std::string& outs)
|
|||
groups[epoch] = 0;
|
||||
|
||||
// Every task has an entry date.
|
||||
if (addedGroup.find (epoch) != addedGroup.end ())
|
||||
addedGroup[epoch] = addedGroup[epoch] + 1;
|
||||
else
|
||||
addedGroup[epoch] = 1;
|
||||
++addedGroup[epoch];
|
||||
|
||||
// All deleted tasks have an end date.
|
||||
if (task->getStatus () == Task::deleted)
|
||||
{
|
||||
epoch = end.startOfYear ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (deletedGroup.find (epoch) != deletedGroup.end ())
|
||||
deletedGroup[epoch] = deletedGroup[epoch] + 1;
|
||||
else
|
||||
deletedGroup[epoch] = 1;
|
||||
++deletedGroup[epoch];
|
||||
}
|
||||
|
||||
// All completed tasks have an end date.
|
||||
|
@ -662,11 +605,7 @@ int handleReportGHistoryAnnual (std::string& outs)
|
|||
{
|
||||
epoch = end.startOfYear ().toEpoch ();
|
||||
groups[epoch] = 0;
|
||||
|
||||
if (completedGroup.find (epoch) != completedGroup.end ())
|
||||
completedGroup[epoch] = completedGroup[epoch] + 1;
|
||||
else
|
||||
completedGroup[epoch] = 1;
|
||||
++completedGroup[epoch];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue