mirror of
https://github.com/status-im/burnchart.git
synced 2025-02-09 09:03:52 +00:00
more intelligent time ticks; closes #65
This commit is contained in:
parent
687a32e7de
commit
e3953f6ce5
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "burnchart",
|
"name": "burnchart",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"description": "GitHub Burndown Chart as a Service",
|
"description": "GitHub Burndown Chart as a Service",
|
||||||
"author": "Radek Stepan <dev@radekstepan.com> (http://radekstepan.com)",
|
"author": "Radek Stepan <dev@radekstepan.com> (http://radekstepan.com)",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
File diff suppressed because one or more lines are too long
6
public/js/bundle.min.js
vendored
6
public/js/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
@ -67,8 +67,8 @@ export default React.createClass({
|
|||||||
let y = d3.scale.linear().range([ height, 0 ]);
|
let y = d3.scale.linear().range([ height, 0 ]);
|
||||||
|
|
||||||
// Axes.
|
// Axes.
|
||||||
let xAxis = axes.horizontal(height, x);
|
let xAxis = axes.time(height, x, milestone.stats.span);
|
||||||
let yAxis = axes.vertical(width, y);
|
let yAxis = axes.points(width, y);
|
||||||
|
|
||||||
// Line generator.
|
// Line generator.
|
||||||
let line = d3.svg.line()
|
let line = d3.svg.line()
|
||||||
@ -104,22 +104,13 @@ export default React.createClass({
|
|||||||
.attr("transform", `translate(0,${height})`)
|
.attr("transform", `translate(0,${height})`)
|
||||||
.call(xAxis);
|
.call(xAxis);
|
||||||
|
|
||||||
// Add the months x-axis.
|
// Add the years x-axis?
|
||||||
let m = [
|
let yrAxis = axes.year(height, xAxis, milestone.stats.span);
|
||||||
'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);
|
|
||||||
|
|
||||||
svg.append("g")
|
svg.append("g")
|
||||||
.attr("class", "x axis month")
|
.attr("class", "x axis year")
|
||||||
.attr("transform", `translate(0,${height})`)
|
.attr("transform", `translate(0,${height})`)
|
||||||
.call(mAxis);
|
.call(yrAxis);
|
||||||
|
|
||||||
// Add the y-axis.
|
// Add the y-axis.
|
||||||
svg.append("g")
|
svg.append("g")
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
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)
|
return d3.svg.axis().scale(x)
|
||||||
.orient("bottom")
|
.orient("bottom")
|
||||||
// Show vertical lines...
|
// Show vertical lines...
|
||||||
.tickSize(-height)
|
.tickSize(-height)
|
||||||
// with day of the month...
|
// tick time format...
|
||||||
.tickFormat((d) => { return d.getDate(); })
|
.tickFormat(format)
|
||||||
// and give us a spacer.
|
// and give us a spacer.
|
||||||
.tickPadding(10);
|
.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)
|
return d3.svg.axis().scale(y)
|
||||||
.orient("left")
|
.orient("left")
|
||||||
.tickSize(-width)
|
.tickSize(-width)
|
||||||
|
@ -16,17 +16,23 @@ export default (milestone) => {
|
|||||||
// Makes testing easier...
|
// Makes testing easier...
|
||||||
if (milestone.stats != null) return milestone.stats;
|
if (milestone.stats != null) return milestone.stats;
|
||||||
|
|
||||||
let isDone = false, isOnTime = true, isOverdue = false,
|
let points = 0, a, b, c, time, days, span;
|
||||||
isEmpty = true, points = 0, a, b, c, time, days;
|
|
||||||
|
let stats = {
|
||||||
|
'isDone': false,
|
||||||
|
'isOnTime': true,
|
||||||
|
'isOverdue': false,
|
||||||
|
'isEmpty': true
|
||||||
|
};
|
||||||
|
|
||||||
// Progress in points.
|
// Progress in points.
|
||||||
a = milestone.issues.closed.size;
|
let i = milestone.issues.closed.size,
|
||||||
b = milestone.issues.open.size;
|
j = milestone.issues.open.size;
|
||||||
if (a) {
|
if (i) {
|
||||||
isEmpty = false;
|
stats.isEmpty = false;
|
||||||
if (a + b > 0) {
|
if (i + j > 0) {
|
||||||
points = progress(a, b);
|
points = progress(i, j);
|
||||||
if (points === 100) isDone = true;
|
if (points === 100) stats.isDone = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,30 +43,33 @@ export default (milestone) => {
|
|||||||
, milestone.created_at);
|
, milestone.created_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Milestones with no due date are always on track.
|
// The dates in this milestone.
|
||||||
if (!(milestone.due_on != null)) {
|
|
||||||
return { isOverdue, isOnTime, isDone, isEmpty, 'progress': { points } };
|
|
||||||
}
|
|
||||||
|
|
||||||
a = moment(milestone.created_at, moment.ISO_8601);
|
a = moment(milestone.created_at, moment.ISO_8601);
|
||||||
b = moment.utc();
|
b = moment.utc();
|
||||||
c = moment(milestone.due_on, moment.ISO_8601);
|
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
|
// Overdue? Regardless of the date, if we have closed all
|
||||||
// issues, we are no longer overdue.
|
// 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.
|
// Progress in time.
|
||||||
time = progress(b.diff(a), c.diff(b));
|
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;
|
days = (b.diff(a, 'days')) / 100;
|
||||||
|
|
||||||
// Are we on time?
|
|
||||||
isOnTime = points > time;
|
|
||||||
|
|
||||||
// If we have closed all issues, we are "on 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 } });
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user