more intelligent time ticks; closes #65

This commit is contained in:
Radek Stepan 2016-01-26 23:40:50 +01:00
parent 687a32e7de
commit e3953f6ce5
6 changed files with 113 additions and 88 deletions

View File

@ -1,6 +1,6 @@
{
"name": "burnchart",
"version": "3.0.0",
"version": "3.0.1",
"description": "GitHub Burndown Chart as a Service",
"author": "Radek Stepan <dev@radekstepan.com> (http://radekstepan.com)",
"license": "AGPL-3.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -67,8 +67,8 @@ export default React.createClass({
let y = d3.scale.linear().range([ height, 0 ]);
// Axes.
let xAxis = axes.horizontal(height, x);
let yAxis = axes.vertical(width, y);
let xAxis = axes.time(height, x, milestone.stats.span);
let yAxis = axes.points(width, y);
// Line generator.
let line = d3.svg.line()
@ -104,22 +104,13 @@ export default React.createClass({
.attr("transform", `translate(0,${height})`)
.call(xAxis);
// Add the months x-axis.
let m = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
let mAxis = xAxis
.orient("top")
.tickSize(height)
.tickFormat((d) => m[d.getMonth()])
.ticks(2);
// Add the years x-axis?
let yrAxis = axes.year(height, xAxis, milestone.stats.span);
svg.append("g")
.attr("class", "x axis month")
.attr("class", "x axis year")
.attr("transform", `translate(0,${height})`)
.call(mAxis);
.call(yrAxis);
// Add the y-axis.
svg.append("g")

View File

@ -1,19 +1,32 @@
import d3 from 'd3';
import _ from 'lodash';
export default {
horizontal(height, x) {
time(height, x, span) {
// Tick time format based on number of days we display.
let specifier = (span < 4) ? '' : (span < 14) ? '%a' : (span < 32) ? '%m/%d' : '%b';
let format = d3.time.format.utc(specifier);
return d3.svg.axis().scale(x)
.orient("bottom")
// Show vertical lines...
.tickSize(-height)
// with day of the month...
.tickFormat((d) => { return d.getDate(); })
// tick time format...
.tickFormat(format)
// and give us a spacer.
.tickPadding(10);
},
vertical(width, y) {
year(height, xAxis, span) {
return xAxis
.orient("top")
.tickSize(height)
.tickFormat(d3.time.format.utc('%Y'))
.ticks(span / 365);
},
points(width, y) {
return d3.svg.axis().scale(y)
.orient("left")
.tickSize(-width)

View File

@ -16,17 +16,23 @@ export default (milestone) => {
// Makes testing easier...
if (milestone.stats != null) return milestone.stats;
let isDone = false, isOnTime = true, isOverdue = false,
isEmpty = true, points = 0, a, b, c, time, days;
let points = 0, a, b, c, time, days, span;
let stats = {
'isDone': false,
'isOnTime': true,
'isOverdue': false,
'isEmpty': true
};
// Progress in points.
a = milestone.issues.closed.size;
b = milestone.issues.open.size;
if (a) {
isEmpty = false;
if (a + b > 0) {
points = progress(a, b);
if (points === 100) isDone = true;
let i = milestone.issues.closed.size,
j = milestone.issues.open.size;
if (i) {
stats.isEmpty = false;
if (i + j > 0) {
points = progress(i, j);
if (points === 100) stats.isDone = true;
}
}
@ -37,30 +43,33 @@ export default (milestone) => {
, milestone.created_at);
}
// Milestones with no due date are always on track.
if (!(milestone.due_on != null)) {
return { isOverdue, isOnTime, isDone, isEmpty, 'progress': { points } };
}
// The dates in this milestone.
a = moment(milestone.created_at, moment.ISO_8601);
b = moment.utc();
c = moment(milestone.due_on, moment.ISO_8601);
// Milestones with no due date are always on track.
if (!(milestone.due_on != null)) {
// The number of days from start to now.
span = b.diff(a, 'days');
return _.extend(stats, { span, 'progress': { points } });
}
// Overdue? Regardless of the date, if we have closed all
// issues, we are no longer overdue.
if (b.isAfter(c) && !isDone) isOverdue = true;
if (b.isAfter(c) && !stats.isDone) stats.isOverdue = true;
// Progress in time.
time = progress(b.diff(a), c.diff(b));
// How many days is 1% of the time?
// Number of days between start and due date or today if overdue.
span = (stats.isOverdue ? b : c).diff(a, 'days');
// How many days is 1% of the time until now?
days = (b.diff(a, 'days')) / 100;
// Are we on time?
isOnTime = points > time;
// If we have closed all issues, we are "on time".
if (isDone) isOnTime = true;
stats.isOnTime = stats.isDone || points > time;
return { isOverdue, isOnTime, isDone, isEmpty, days, 'progress': { points, time } };
return _.extend(stats, { days, span, 'progress': { points, time } });
};