work with both labels and single points

This commit is contained in:
Radek Stepan 2014-09-18 20:55:40 -07:00
parent eff268e1de
commit b364f0cda5
11 changed files with 69 additions and 58 deletions

View File

@ -54,6 +54,7 @@ GitHub Burndown Chart as a service. Public repos are free, for private access au
- [ ] allow people to submit suggestions via GitHub Issues
- [ ] find a way where, as a group, we can share repo data by trusting the other repo members that use our platform
- [ ] support Jira & Gitlab
- [x] closed issues can be moved to a newly created milestone, this messes up the chart since we assume milestone is created first!
## Notes

View File

@ -436,7 +436,7 @@ table {
}@keyframes spin{0%{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}
100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}
}
#chart{height:200px;position:relative;}
#chart{height:300px;position:relative;}
#chart #tooltip{position:absolute;top:0;left:0}
#chart svg path.line{fill:none;stroke-width:1px;clip-path:url("#clip");}
#chart svg path.line.actual{stroke:#64584c;stroke-width:3px}

View File

@ -29,7 +29,7 @@
}@keyframes spin{0%{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}
100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}
}
#chart{height:200px;position:relative;}
#chart{height:300px;position:relative;}
#chart #tooltip{position:absolute;top:0;left:0}
#chart svg path.line{fill:none;stroke-width:1px;clip-path:url("#clip");}
#chart svg path.line.actual{stroke:#64584c;stroke-width:3px}

View File

@ -40038,7 +40038,7 @@ if (typeof exports === 'object') {
"milestone": ["closed_issues", "created_at", "description", "due_on", "number", "open_issues", "title", "updated_at"]
},
"chart": {
"off_days": [6, 7],
"off_days": [],
"datetime": /^(\d{4}-\d{2}-\d{2})T(.*)/,
"size_label": /^size (\d+)$/,
"location": /^#!((\/[^\/]+){2,3})$/,
@ -40141,8 +40141,8 @@ if (typeof exports === 'object') {
var head, max, min, range, rest;
head = [
{
date: new Date(created_at),
points: total
'date': new Date(created_at),
'points': total
}
];
min = +Infinity;
@ -40156,10 +40156,9 @@ if (typeof exports === 'object') {
if (size > max) {
max = size;
}
return _.extend({}, issue, {
date: new Date(closed_at),
points: total -= size
});
issue.date = new Date(closed_at);
issue.points = total -= size;
return issue;
});
range = d3.scale.linear().domain([min, max]).range([5, 8]);
rest = _.map(rest, function(issue) {
@ -40262,10 +40261,10 @@ if (typeof exports === 'object') {
document.querySelector('#svg').innerHTML = '';
_ref = document.querySelector('#chart').getBoundingClientRect(), height = _ref.height, width = _ref.width;
margin = {
top: 30,
right: 30,
bottom: 40,
left: 50
'top': 30,
'right': 30,
'bottom': 40,
'left': 50
};
width -= margin.left + margin.right;
height -= margin.top + margin.bottom;
@ -40411,7 +40410,7 @@ if (typeof exports === 'object') {
};
return async.parallel([_.partial(one_status, 'open'), _.partial(one_status, 'closed')], cb);
},
'filter': function(collection, regex, cb) {
'filter': function(collection, cb) {
var filtered, total;
total = 0;
switch (config.get('chart.points')) {
@ -40430,7 +40429,7 @@ if (typeof exports === 'object') {
}
issue.size = _.reduce(labels, function(sum, label) {
var matches;
if (!(matches = label.name.match(regex))) {
if (!(matches = label.name.match(config.get('chart.size_label')))) {
return sum;
}
return sum += parseInt(matches[1]);
@ -40531,11 +40530,11 @@ if (typeof exports === 'object') {
return issues.get_all(opts, cb);
}, function(all, cb) {
return async.map(all, function(array, cb) {
return issues.filter(array, opts.size_label, function(err, filtered, total) {
return issues.filter(array, function(err, filtered, total) {
return cb(err, [filtered, total]);
});
}, function(err, _arg) {
var closed, open;
var closed, open, start;
open = _arg[0], closed = _arg[1];
if (err) {
return cb(err);
@ -40544,15 +40543,18 @@ if (typeof exports === 'object') {
return cb('No matching issues found');
}
opts.issues = {
closed: {
'closed': {
'points': closed[1],
'data': closed[0]
},
open: {
'open': {
'points': open[1],
'data': open[0]
}
};
if ((start = closed[0][0].closed_at) < opts.milestone.created_at) {
opts.milestone.created_at = start;
}
return cb(null);
});
}, function(cb) {
@ -40759,7 +40761,7 @@ if (typeof exports === 'object') {
// showChart.mustache
root.require.register('burnchart/src/templates/pages/showChart.js', function(exports, require, module) {
module.exports = ["<div id=\"content\" class=\"wrap\">"," <div id=\"chart\">"," <div id=\"tooltip\"></div>"," <div id=\"svg\"></div>"," </div>","</div>"].join("\n");
module.exports = ["<div id=\"content\" class=\"wrap\">"," <div id=\"chart\">"," <div id=\"svg\"></div>"," </div>","</div>"].join("\n");
});
// projects.mustache

View File

@ -72,7 +72,7 @@
"milestone": ["closed_issues", "created_at", "description", "due_on", "number", "open_issues", "title", "updated_at"]
},
"chart": {
"off_days": [6, 7],
"off_days": [],
"datetime": /^(\d{4}-\d{2}-\d{2})T(.*)/,
"size_label": /^size (\d+)$/,
"location": /^#!((\/[^\/]+){2,3})$/,
@ -175,8 +175,8 @@
var head, max, min, range, rest;
head = [
{
date: new Date(created_at),
points: total
'date': new Date(created_at),
'points': total
}
];
min = +Infinity;
@ -190,10 +190,9 @@
if (size > max) {
max = size;
}
return _.extend({}, issue, {
date: new Date(closed_at),
points: total -= size
});
issue.date = new Date(closed_at);
issue.points = total -= size;
return issue;
});
range = d3.scale.linear().domain([min, max]).range([5, 8]);
rest = _.map(rest, function(issue) {
@ -296,10 +295,10 @@
document.querySelector('#svg').innerHTML = '';
_ref = document.querySelector('#chart').getBoundingClientRect(), height = _ref.height, width = _ref.width;
margin = {
top: 30,
right: 30,
bottom: 40,
left: 50
'top': 30,
'right': 30,
'bottom': 40,
'left': 50
};
width -= margin.left + margin.right;
height -= margin.top + margin.bottom;
@ -445,7 +444,7 @@
};
return async.parallel([_.partial(one_status, 'open'), _.partial(one_status, 'closed')], cb);
},
'filter': function(collection, regex, cb) {
'filter': function(collection, cb) {
var filtered, total;
total = 0;
switch (config.get('chart.points')) {
@ -464,7 +463,7 @@
}
issue.size = _.reduce(labels, function(sum, label) {
var matches;
if (!(matches = label.name.match(regex))) {
if (!(matches = label.name.match(config.get('chart.size_label')))) {
return sum;
}
return sum += parseInt(matches[1]);
@ -565,11 +564,11 @@
return issues.get_all(opts, cb);
}, function(all, cb) {
return async.map(all, function(array, cb) {
return issues.filter(array, opts.size_label, function(err, filtered, total) {
return issues.filter(array, function(err, filtered, total) {
return cb(err, [filtered, total]);
});
}, function(err, _arg) {
var closed, open;
var closed, open, start;
open = _arg[0], closed = _arg[1];
if (err) {
return cb(err);
@ -578,15 +577,18 @@
return cb('No matching issues found');
}
opts.issues = {
closed: {
'closed': {
'points': closed[1],
'data': closed[0]
},
open: {
'open': {
'points': open[1],
'data': open[0]
}
};
if ((start = closed[0][0].closed_at) < opts.milestone.created_at) {
opts.milestone.created_at = start;
}
return cb(null);
});
}, function(cb) {
@ -793,7 +795,7 @@
// showChart.mustache
root.require.register('burnchart/src/templates/pages/showChart.js', function(exports, require, module) {
module.exports = ["<div id=\"content\" class=\"wrap\">"," <div id=\"chart\">"," <div id=\"tooltip\"></div>"," <div id=\"svg\"></div>"," </div>","</div>"].join("\n");
module.exports = ["<div id=\"content\" class=\"wrap\">"," <div id=\"chart\">"," <div id=\"svg\"></div>"," </div>","</div>"].join("\n");
});
// projects.mustache

View File

@ -21,12 +21,13 @@ module.exports = new Model
]
# Chart configuration.
"chart":
"off_days": [ 6, 7 ]
# Days we are not working.
"off_days": [ ]
# How do we parse GitHub dates?
"datetime": /^(\d{4}-\d{2}-\d{2})T(.*)/
# How does a size label look like?
"size_label": /^size (\d+)$/
# How do we specify which user/repo/(milestone) we want?
"location": /^#!((\/[^\/]+){2,3})$/
# Process all issues as one size.
# Process all issues as one size or use labels.
"points": 'ALL_ONE_SIZE'

View File

@ -4,10 +4,13 @@ config = require '../models/config'
module.exports =
# A graph of closed issues.
# `collection`: issues
# `created_at`: milestone start date
# `total`: total number of points (open & closed issues)
'actual': (collection, created_at, total, cb) ->
head = [ {
date: new Date(created_at)
points: total
'date': new Date created_at
'points': total
} ]
min = +Infinity ; max = -Infinity
@ -20,9 +23,9 @@ module.exports =
max = size if size > max
# Dropping points remaining.
_.extend {}, issue,
date: new Date(closed_at)
points: total -= size
issue.date = new Date closed_at
issue.points = total -= size
issue
# Now add a radius in a range (will be used for a circle).
range = d3.scale.linear().domain([ min, max ]).range([ 5, 8 ])
@ -125,14 +128,14 @@ module.exports =
# Get available space.
{ height, width } = document.querySelector('#chart').getBoundingClientRect()
margin = { top: 30, right: 30, bottom: 40, left: 50 }
margin = { 'top': 30, 'right': 30, 'bottom': 40, 'left': 50 }
width -= margin.left + margin.right
height -= margin.top + margin.bottom
# Scales.
x = d3.time.scale().range([ 0, width ])
y = d3.scale.linear().range([ height, 0 ])
# Axes.
xAxis = d3.svg.axis().scale(x)
.orient("bottom")
@ -225,9 +228,8 @@ module.exports =
# Show when we closed an issue.
svg.selectAll("a.issue")
.data(actual[1...]) # skip the starting point
.data(actual.slice(1)) # skip the starting point
.enter()
# A wrapping link.
.append('svg:a')
.attr("xlink:href", ({ html_url }) -> html_url )

View File

@ -31,10 +31,10 @@ module.exports =
], cb
# Filter an array of incoming issues based on a regex & save size on them.
'filter': (collection, regex, cb) ->
'filter': (collection, cb) ->
# The total size of all issues.
total = 0
# Which point counting mode are we in?
switch config.get 'chart.points'
# All issues are the same size
@ -53,7 +53,7 @@ module.exports =
# Determine the total issue size from all labels.
issue.size = _.reduce labels, (sum, label) ->
# Not matching.
return sum unless matches = label.name.match(regex)
return sum unless matches = label.name.match config.get 'chart.size_label'
# Increase sum.
sum += parseInt matches[1]
, 0
@ -63,5 +63,5 @@ module.exports =
# Are we saving it?
!!issue.size
cb null, filtered, total

View File

@ -21,7 +21,7 @@ module.exports = (opts, cb) ->
# Filter them to labeled ones.
(all, cb) ->
async.map all, (array, cb) ->
issues.filter array, opts.size_label, (err, filtered, total) ->
issues.filter array, (err, filtered, total) ->
cb err, [ filtered, total ]
, (err, [ open, closed ]) ->
return cb err if err
@ -29,8 +29,12 @@ module.exports = (opts, cb) ->
return cb 'No matching issues found' if open[1] + closed[1] is 0
# Save the open/closed on us first.
opts.issues =
closed: { 'points': closed[1], 'data': closed[0] }
open: { 'points': open[1], 'data': open[0] }
'closed': { 'points': closed[1], 'data': closed[0] }
'open': { 'points': open[1], 'data': open[0] }
# Do we need to move the milestone start date?
if (start = closed[0][0].closed_at) < opts.milestone.created_at
opts.milestone.created_at = start
cb null
# Create actual and ideal lines & render.

View File

@ -10,7 +10,7 @@ $background2 = #CC9485
// where D3 renders to
#chart
height: 200px
height: 300px
position: relative
// position will be adjusted dynamically

View File

@ -1,6 +1,5 @@
<div id="content" class="wrap">
<div id="chart">
<div id="tooltip"></div>
<div id="svg"></div>
</div>
</div>