diff --git a/ChangeLog b/ChangeLog index b2da7570c..ba6496c24 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,9 @@ ------ current release --------------------------- 1.9.4 () - + Added burndown charts - burndown.daily, burndown.weekly, burndown.monthly. + + Added burndown charts - burndown.daily, burndown.weekly, burndown.monthly, + that use color.burndown.pending, color.burndown.started and + color.burndown.done colors. + Fixed bug #529, where the 'depends' attribute was not mentioned in the task man page (thanks to Dirk Deimeke). + Fixed bug #535 which omitted the holidays-NO.rc file from the packages diff --git a/NEWS b/NEWS index e3d9726b0..de7b40085 100644 --- a/NEWS +++ b/NEWS @@ -13,7 +13,8 @@ New commands in taskwarrior 1.9.4 New configuration options in taskwarrior 1.9.4 - - + - color.burndown.pending, color.burndown.started and color.burndown.done + control the color of the burndown charts. Newly deprecated features in taskwarrior 1.9.4 diff --git a/doc/man/taskrc.5 b/doc/man/taskrc.5 index 64e718ba4..bca701d8a 100644 --- a/doc/man/taskrc.5 +++ b/doc/man/taskrc.5 @@ -701,6 +701,19 @@ Colors the bars on the ghistory report graphs. Defaults to red, green and yellow bars. .RE +.TP +.B color.burndown.pending=on red +.RE +.br +.B color.burndown.started=on yellow +.RE +.br +.B color.burndown.done=on green +.RS +Colors the bars on the burndown reports graphs. Defaults to red, green and +yellow bars. +.RE + .TP .B color.undo.before=red .RE diff --git a/src/burndown.cpp b/src/burndown.cpp index 16a1d21a7..a6bf66d09 100644 --- a/src/burndown.cpp +++ b/src/burndown.cpp @@ -104,6 +104,36 @@ Bar::~Bar () } //////////////////////////////////////////////////////////////////////////////// +// Data gathering algorithm: +// +// e = entry +// s = start +// C = end/Completed +// D = end/Deleted +// > = Pending/Waiting +// +// ID 30 31 01 02 03 04 05 06 07 08 09 10 +// -- ------------------------------------ +// 1 e-----s--C +// 2 e--s-----D +// 3 e-----s--------------> +// 4 e-----------------> +// 5 e-----> +// -- ------------------------------------ +// pp 1 2 3 3 2 2 2 3 3 3 +// ss 2 1 1 1 1 1 1 1 +// dd 1 1 1 1 1 1 1 +// -- ------------------------------------ +// +// 5 | ss dd dd dd dd +// 4 | ss ss dd dd dd ss ss ss +// 3 | pp pp ss ss ss pp pp pp +// 2 | pp pp pp pp pp pp pp pp pp +// 1 | pp pp pp pp pp pp pp pp pp pp +// 0 +------------------------------------- +// 30 31 01 02 03 04 05 06 07 08 09 10 +// Oct Nov +// class Chart { public: @@ -123,6 +153,7 @@ private: Date increment (const Date&); Date decrement (const Date&); void maxima (); + void yLabels (std::vector &); public: int width; @@ -136,6 +167,7 @@ public: int actual_bars; std::map bars; Date earliest; + int carryover_done; char period; // D, W, M. std::string grid; @@ -175,6 +207,9 @@ Chart::Chart (char type) period = type; std::cout << "# period " << period << "\n"; + carryover_done = 0; + std::cout << "# carryover_done " << carryover_done << "\n"; + // Rates are calculated last. find_rate = 0.0; fix_rate = 0.0; @@ -195,13 +230,14 @@ Chart& Chart::operator= (const Chart& other) height = other.height; graph_width = other.graph_width; graph_height = other.graph_height; - max_value = other. max_value; + 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; + carryover_done = other.carryover_done; period = other.period; grid = other.grid; find_rate = other.find_rate; @@ -222,13 +258,12 @@ void Chart::scan (std::vector & 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) + std::vector ::iterator task; + for (task = tasks.begin (); task != tasks.end (); ++task) { // The entry date is when the counting starts. Date from = quantize (Date (task->get ("entry"))); @@ -282,11 +317,11 @@ void Chart::scan (std::vector & tasks) if (bars.find (epoch) != bars.end ()) ++bars[epoch].removed; + // Maintain a running total of 'done' tasks that are off the left of the + // chart. if (end < earliest) { - epoch = earliest.toEpoch (); - if (bars.find (epoch) != bars.end ()) - ++bars[epoch].done; + ++carryover_done; continue; } @@ -422,21 +457,108 @@ std::string Chart::render () 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); + // Determine y-axis labelling. + std::vector labels; + yLabels (labels); + + // 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 x-axis labels. + std::vector bars_in_sequence; + std::map ::iterator it; + for (it = bars.begin (); it != bars.end (); ++it) + bars_in_sequence.push_back (it->first); + + std::sort (bars_in_sequence.begin (), bars_in_sequence.end ()); + std::vector ::iterator seq; + std::string major; + for (seq = bars_in_sequence.begin (); seq != bars_in_sequence.end (); ++seq) + { + Bar bar = bars[*seq]; + + // If it fits within the allowed space. + if (bar.offset < actual_bars) + { + grid.replace (LOC (height - 5, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), bar.minor.length (), bar.minor); + + if (major != bar.major) + grid.replace (LOC (height - 4, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), bar.major.length (), bar.major); + + major = bar.major; + } + } + + // Draw bars. + for (seq = bars_in_sequence.begin (); seq != bars_in_sequence.end (); ++seq) + { + Bar bar = bars[*seq]; + + // If it fits within the allowed space. + if (bar.offset < actual_bars) + { + int pending = (bar.pending * graph_height) / labels[2]; + int started = (bar.started * graph_height) / labels[2]; + int done = ((bar.done + carryover_done) * graph_height) / labels[2]; + + for (int b = 0; b < pending; ++b) + grid.replace (LOC (graph_height - b, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "pp"); + + for (int b = 0; b < started; ++b) + grid.replace (LOC (graph_height - b - pending, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "ss"); + + for (int b = 0; b < done; ++b) + grid.replace (LOC (graph_height - b - pending - started, max_label + 3 + ((actual_bars - bar.offset - 1) * 3)), 2, "dd"); + } + } // Draw rates. +/* + // Calculate and render the rates. + // Calculate 30-day average. + int totalAdded30 = 0; + int totalRemoved30 = 0; + d = (Date () - 30 * 86400).startOfDay (); + for (unsigned int i = 0; i < 30; i++) + { + epoch = d.toEpoch (); + + totalAdded30 += addGroup[epoch]; + totalRemoved30 += removeGroup[epoch]; + + d++; + } + + float find_rate30 = 1.0 * totalAdded30 / x_axis.size (); + float fix_rate30 = 1.0 * totalRemoved30 / x_axis.size (); + + // Calculate 7-day average. + int totalAdded7 = 0; + int totalRemoved7 = 0; + d = (Date () - 7 * 86400).startOfDay (); + for (unsigned int i = 0; i < 7; i++) + { + epoch = d.toEpoch (); + + totalAdded7 += addGroup[epoch]; + totalRemoved7 += removeGroup[epoch]; + + d++; + } + + float find_rate7 = 1.0 * totalAdded7 / x_axis.size (); + float fix_rate7 = 1.0 * totalRemoved7 / x_axis.size (); + +*/ 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"); @@ -445,6 +567,25 @@ std::string Chart::render () grid.replace (LOC (height - 1, max_label + 3), 13 + strlen (rate), std::string ("Fix rate: ") + rate + "/d"); // Draw completion date. +/* + if (last_pending == 0) + { + ; // Do not render an estimated completion date. + } + else if (find_rate7 < fix_rate7) + { + int current_pending = pendingGroup[Date ().startOfDay ().toEpoch ()]; + float days = 2.0 * current_pending / (fix_rate30 + fix_rate7); + Date end; + end += (int) (days * 86400); + std::string formatted = end.toString (context.config.get ("dateformat")); + grid.replace (LOC (height - 2, max_label + 27), 22 + formatted.length (), "Estimated completion: " + formatted); + } + else + { + grid.replace (LOC (height - 2, max_label + 27), 36, "Estimated completion: No convergence"); + } +*/ if (completion.length ()) grid.replace (LOC (height - 2, max_label + 27), 22 + completion.length (), "Estimated completion: " + completion); @@ -480,7 +621,6 @@ void Chart::optimizeGrid () 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"); } } @@ -673,7 +813,8 @@ void Chart::maxima () // Determine max_label. int total = it->second.pending + it->second.started + - it->second.done; + it->second.done + + carryover_done; // Determine max_value. if (total > max_value) @@ -695,10 +836,9 @@ void Chart::maxima () } //////////////////////////////////////////////////////////////////////////////// -// 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 & labels, int height, int value) +// Given the vertical chart area size (graph_height), the largest value +// (max_value), populate a vector of labels for the y axis. +void Chart::yLabels (std::vector & labels) { /* double logarithm = log10 ((double) value); @@ -736,8 +876,8 @@ void calculateYAxis (std::vector & labels, int height, int value) */ // For now, simply select 0, n/2 and n, where n is value rounded up to the - // nearest 10. - int high = value; + // nearest 10. This is a poor solution. + int high = max_value; int mod = high % 10; if (mod) high += 10 - mod; @@ -750,67 +890,12 @@ void calculateYAxis (std::vector & labels, int height, int value) } //////////////////////////////////////////////////////////////////////////////// -// 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 | -// +---------------------------------------------------------------------+ -// -// e = entry -// s = start -// C = end/Completed -// D = end/Deleted -// > = Pending/Waiting -// -// ID 30 31 01 02 03 04 05 06 07 08 09 10 -// -- ------------------------------------ -// 1 e-----s--C -// 2 e--s-----D -// 3 e-----s--------------> -// 4 e-----------------> -// 5 e-----> -// -- ------------------------------------ -// pp 1 2 3 3 2 2 2 3 3 3 -// ss 2 1 1 1 1 1 1 1 -// dd 1 1 1 1 1 1 1 -// -- ------------------------------------ -// -// 5 | ss dd dd dd dd -// 4 | ss ss dd dd dd ss ss ss -// 3 | pp pp ss ss ss pp pp pp -// 2 | pp pp pp pp pp pp pp pp pp -// 1 | pp pp pp pp pp pp pp pp pp pp -// 0 +------------------------------------- -// 30 31 01 02 03 04 05 06 07 08 09 10 -// Oct Nov -// int handleReportBurndownDaily (std::string& outs) { int rc = 0; if (context.hooks.trigger ("pre-burndown-command")) { - std::map groups; - std::map pendingGroup; - std::map startedGroup; - std::map doneGroup; - - std::map addGroup; - std::map removeGroup; - // Scan the pending tasks, applying any filter. std::vector tasks; context.tdb.lock (context.config.getBoolean ("locking")); @@ -819,428 +904,23 @@ int handleReportBurndownDaily (std::string& outs) context.tdb.commit (); context.tdb.unlock (); - // How much space is there to render in? This chart will occupy the - // maximum space, and the width drives various other parameters. - int width = context.getWidth (); - int height = context.getHeight () - 1; // Allow for new line with prompt. + // Create a chart, scan the tasks, then render. + Chart chart ('D'); + chart.scan (tasks); + std::map ::iterator it; + for (it = chart.bars.begin (); it != chart.bars.end (); ++it) + std::cout << "# " << Date (it->first).toString ("YMD") + << " [" << it->second.offset << "] " + << it->second.major << "/" << it->second.minor << " " + << it->second.pending << "p " + << it->second.started << "s " + << it->second.done << "d " + << it->second.added << "a " + << it->second.removed << "r\n"; - // Estimate how many 'bars' can be dsplayed. This will help subset a - // potentially enormous data set. - unsigned int estimate = (width - 1 - 14) / 3; - Date now; - Date cutoff = (now - (estimate * 86400)).startOfDay (); -// std::cout << "# cutoff " << cutoff.toString () << "\n"; -// std::cout << "# now " << now.toString () << "\n"; + outs = chart.render (); - time_t epoch; - foreach (task, tasks) - { - // The entry date is when the counting starts. - Date from = Date (task->get ("entry")).startOfDay (); - addGroup[from.toEpoch ()]++; - - // e--> e--s--> - // ppp> pppsss> - Task::status status = task->getStatus (); - if (status == Task::pending || - status == Task::waiting) - { - if (task->has ("start")) - { - Date start = Date (task->get ("start")).startOfDay (); - while (from < start) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - - while (from < now) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++startedGroup[epoch]; - from++; - } - } - else - { - while (from < now) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - } - } - - // e--C e--s--C - // pppd> pppsssd> - else if (status == Task::completed) - { - // Truncate history so it starts at 'cutoff' for completed tasks. - Date end = Date (task->get ("end")).startOfDay (); - removeGroup[end.toEpoch ()]++; - - if (end < cutoff) - { - ++doneGroup[cutoff.toEpoch ()]; - continue; - } - - if (task->has ("start")) - { - Date start = Date (task->get ("start")).startOfDay (); - while (from < start) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - - while (from < end) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++startedGroup[epoch]; - from++; - } - - while (from < now) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++doneGroup[epoch]; - from++; - } - } - else - { - Date end = Date (task->get ("end")).startOfDay (); - while (from < end) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - - while (from < now) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++doneGroup[epoch]; - from++; - } - } - } - - // e--D e--s--D - // ppp pppsss - else if (status == Task::deleted) - { - // Skip old deleted tasks. - Date end = Date (task->get ("end")).startOfDay (); - removeGroup[end.toEpoch ()]++; - - if (end < cutoff) - continue; - - if (task->has ("start")) - { - Date start = Date (task->get ("start")).startOfDay (); - while (from < start) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - - while (from < end) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++startedGroup[epoch]; - from++; - } - } - else - { - Date end = Date (task->get ("end")).startOfDay (); - while (from < end) - { - epoch = from.startOfDay ().toEpoch (); - groups[epoch] = 0; - ++pendingGroup[epoch]; - from++; - } - } - } - } - -/* - // TODO Render. - foreach (g, groups) - { - std::stringstream s; - s << Date (g->first).toISO () << " " - << pendingGroup[g->first] << "/" - << startedGroup[g->first] << "/" - << doneGroup[g->first] << "\n"; - outs += s.str (); - } -*/ - - if (groups.size ()) - { - // Horizontal Breakdown - // - // >25 | xx ... xx ll pending< - // ^ left margin - // ^^ max_label - // ^ gap - // ^ axis - // ^^^ gap + bar - // ^^^ gap + bar - // ^^ gap - // ^^ legend swatch - // ^ gap - // ^^^^^^^ "Pending", "Started" & "Done" - // ^ right margin - - // Vertical Breakdown - // - // v top margin - // blank line - // | all remaining space - // - x axis - // 9 day/week/month - // 9 month/-/year - // blank line - // f find rate - // f fix rate - // ^ bottom margin - - // What is the longest y-axis label? This is tricky. Having - // optimistically estimate the number of bars to be shown, then determine - // the longest label of the records that lie within the observable range. - // It is important to consider that there may be zero -> bars number of - // records that match. - int max_label = 1; - int max_value = 0; - - std::vector x_axis; - Date now; - for (unsigned int i = 0; i < estimate; ++i) - { - Date x = (now - (i * 86400)).startOfDay (); - x_axis.push_back (x.toEpoch ()); - - int total = pendingGroup[x.toEpoch ()] + - startedGroup[x.toEpoch ()] + - doneGroup[x.toEpoch ()]; - - if (total > max_value) - max_value = total; - - int length = (int) log10 ((double) total) + 1; - if (length > max_label) - max_label = length; - } - - std::sort (x_axis.begin (), x_axis.end ()); - - // How many bars can be shown? - unsigned int bars = (width - max_label - 14) / 3; - int graph_width = width - max_label - 14; - - // Make them match - while (bars < x_axis.size ()) - x_axis.erase (x_axis.begin ()); - - // Determine the y-axis. - int graph_height = height - 7; - - if (graph_height < 5 || - graph_width < 4) - { - outs = "Terminal window too small to draw a graph.\n"; - return rc; - } - - // Determine y-axis labelling. - std::vector labels; - calculateYAxis (labels, graph_height, max_value); -// foreach (i, labels) -// std::cout << "# label " << *i << "\n"; - -// std::cout << "# estimate " << estimate << " bars\n"; -// std::cout << "# actually " << bars << " bars\n"; -// std::cout << "# graph width " << graph_width << "\n"; -// std::cout << "# graph height " << graph_height << "\n"; -// std::cout << "# days " << x_axis.size () << "\n"; -// std::cout << "# max label " << max_label << "\n"; -// std::cout << "# max value " << max_value << "\n"; - - // Determine the start date. - Date start = (Date () - ((bars - 1) * 86400)).startOfDay (); -// std::cout << "# start " << start.toISO () << "\n"; - - // Compose the grid. - std::string grid; - for (int i = 0; i < height; ++i) - grid += std::string (width, ' ') + "\n"; - - // Draw 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 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, '-')); - - int month = 0; - Date d (start); - for (unsigned int i = 0; i < bars; ++i) - { - if (month != d.month ()) - grid.replace (LOC (height - 4, max_label + 3 + (i * 3)), 3, Date::monthName (d.month ()).substr (0, 3)); - - char day [3]; - sprintf (day, "%02d", d.day ()); - grid.replace (LOC (height - 5, max_label + 3 + (i * 3)), 2, day); - - month = d.month (); - d++; - } - - // Draw the y-axis. - for (int i = 0; i < graph_height; ++i) - grid.replace (LOC (i + 1, max_label + 1), 1, "|"); - - 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 the bars. - int last_pending = 0; - d = start; - for (unsigned int i = 0; i < bars; ++i) - { - epoch = d.toEpoch (); - int pending = (pendingGroup[epoch] * graph_height) / labels[2]; - int started = (startedGroup[epoch] * graph_height) / labels[2]; - int done = (doneGroup[epoch] * graph_height) / labels[2]; - - // Track the latest pending count, for convergence calculation. - last_pending = pendingGroup[epoch] + startedGroup[epoch]; - - for (int b = 0; b < pending; ++b) - grid.replace (LOC (graph_height - b, max_label + 3 + (i * 3)), 2, "pp"); - - for (int b = 0; b < started; ++b) - grid.replace (LOC (graph_height - b - pending, max_label + 3 + (i * 3)), 2, "ss"); - - for (int b = 0; b < done; ++b) - grid.replace (LOC (graph_height - b - pending - started, max_label + 3 + (i * 3)), 2, "dd"); - - d++; - } - -// std::cout << "# last pending " << last_pending << "\n"; - - // Calculate and render the rates. - // Calculate 30-day average. - int totalAdded30 = 0; - int totalRemoved30 = 0; - d = (Date () - 30 * 86400).startOfDay (); - for (unsigned int i = 0; i < 30; i++) - { - epoch = d.toEpoch (); - - totalAdded30 += addGroup[epoch]; - totalRemoved30 += removeGroup[epoch]; - - d++; - } - - float find_rate30 = 1.0 * totalAdded30 / x_axis.size (); - float fix_rate30 = 1.0 * totalRemoved30 / x_axis.size (); - - // Calculate 7-day average. - int totalAdded7 = 0; - int totalRemoved7 = 0; - d = (Date () - 7 * 86400).startOfDay (); - for (unsigned int i = 0; i < 7; i++) - { - epoch = d.toEpoch (); - - totalAdded7 += addGroup[epoch]; - totalRemoved7 += removeGroup[epoch]; - - d++; - } - - float find_rate7 = 1.0 * totalAdded7 / x_axis.size (); - float fix_rate7 = 1.0 * totalRemoved7 / x_axis.size (); - - // Render rates. - char rate[12]; - sprintf (rate, "%.1f", (find_rate30 + find_rate7) / 2.0); - grid.replace (LOC (height - 2, max_label + 3), 13 + strlen (rate), std::string ("Find rate: ") + rate + "/d"); - - sprintf (rate, "%.1f", (fix_rate30 + fix_rate7) / 2.0); - grid.replace (LOC (height - 1, max_label + 3), 13 + strlen (rate), std::string ("Fix rate: ") + rate + "/d"); - - if (last_pending == 0) - { - ; // Do not render an estimated completion date. - } - else if (find_rate7 < fix_rate7) - { - int current_pending = pendingGroup[Date ().startOfDay ().toEpoch ()]; - float days = 2.0 * current_pending / (fix_rate30 + fix_rate7); - Date end; - end += (int) (days * 86400); - std::string formatted = end.toString (context.config.get ("dateformat")); - grid.replace (LOC (height - 2, max_label + 27), 22 + formatted.length (), "Estimated completion: " + formatted); - } - else - { - grid.replace (LOC (height - 2, max_label + 27), 36, "Estimated completion: No convergence"); - } - - // Output 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. - // TODO Use configurable values. - 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 (" ")); - - outs += grid; - - context.hooks.trigger ("post-burndown-command"); - } - else - outs = "No matches.\n"; + context.hooks.trigger ("post-burndown-command"); } return rc; @@ -1266,16 +946,14 @@ int handleReportBurndownWeekly (std::string& outs) chart.scan (tasks); std::map ::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"; + std::cout << "# " << Date (it->first).toString ("YMD") + << " [" << it->second.offset << "] " + << it->second.major << "/" << it->second.minor << " " + << it->second.pending << "p " + << it->second.started << "s " + << it->second.done << "d " + << it->second.added << "a " + << it->second.removed << "r\n"; outs = chart.render (); @@ -1305,16 +983,14 @@ int handleReportBurndownMonthly (std::string& outs) chart.scan (tasks); std::map ::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"; + std::cout << "# " << Date (it->first).toString ("YMD") + << " [" << it->second.offset << "] " + << it->second.major << "/" << it->second.minor << " " + << it->second.pending << "p " + << it->second.started << "s " + << it->second.done << "d " + << it->second.added << "a " + << it->second.removed << "r\n"; outs = chart.render ();