Skip to content

Instantly share code, notes, and snippets.

@caesarsol
Last active April 8, 2016 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caesarsol/05d3bee9f75aa35fe7e2390fd49a2f58 to your computer and use it in GitHub Desktop.
Save caesarsol/05d3bee9f75aa35fe7e2390fd49a2f58 to your computer and use it in GitHub Desktop.
BusFactor experiments
//const fetch = require('node-fetch');
const _ = require('lodash');
const moment = require('moment');
const process = require('process');
const d3 = require('d3');
Object.prototype.inspect = function(fn = (x => x)) { console.log(fn(this)); return this; };
const repos = [
'clojure/clojurescript',
'facebook/react',
'Homebrew/homebrew',
'torvalds/linux',
];
repos.map(fetchContributors).forEach(fetchedRepo => {
fetchedRepo.then((list) => {
busFactor(list);
const graphData = list.map((e, i) => ({y: e.percentNorm, x: i, user: e.login}) )
drawRepoGraph(graphData, list.name)
});
});
function userRepoContributions(user) {
// deletions are more valuable, because they imply refactorings and/or
// better understanding of the codebase
return user.weeks.reduce((accu, n) => {
const timestamp = parseInt(n.w);
const week = moment.unix(timestamp);
const reference = moment().subtract(1, 'year');
const diff = moment.duration(week.diff(reference)).months();
const coeff = diff < 1 ? 0.5 : Math.exp(diff / 4);
return accu + (n.a + n.c + n.d * 2) * coeff;
}, 0);
}
function fetchContributors(repo) {
const url = `https://api.github.com/repos/${repo}/stats/contributors`;
return fetch(url)
.then((res) => res.json())
.then((json) => {
const stats = json.map((user) => ({
login: user.author.login,
contributions: userRepoContributions(user)
}));
const sorted = _.sortBy(stats, 'contributions');
const sum = _.sum(sorted.map(x => x.contributions));
const biggest = _.reverse(_.takeRight(sorted, 10));
const list = biggest.map(x => {
x.percent = (x.contributions * 100.0 / sum).toFixed(2);
return x;
})
const norm = _.sum(list.map(e => parseInt(e.percent)));
list.forEach(e => { e.percentNorm = e.percent / norm * 100 }); // yeah, mutating in-place
list.name = repo;
return list;
});
}
function busFactor(list) {
average(list.map(e => e.percentNorm)).inspect(x => `Avg: ${x}`);
let std = standardDeviation(list.map(e => e.percentNorm)).inspect(x => `StdDev: ${x}`);
return (std * ((list.length - 1) / list.length)).inspect(x => `${x}`);
}
function standardDeviation(values){
var avg = average(values);
var squareDiffs = values.map(function(value){
var diff = value - avg;
var sqrDiff = Math.pow(diff, 2);
return sqrDiff;
});
var avgSquareDiff = average(squareDiffs);
return Math.sqrt(avgSquareDiff);
}
function average(data){
return _.sum(data) / data.length;
}
function drawRepoGraph(data, name) {
var margin = {top: 40, right: 50, bottom: 100, left: 30};
var width = 200 - margin.left - margin.right;
var height = 200 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([d3.min(data, d => d.x), d3.max(data, d => d.x)])
.range([0, width]);
var y = d3.scale.linear()
.domain([d3.min(data, d => d.y), d3.max(data, d => d.y)])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.style("fill", "#B4B4D6")
.attr("transform", d => `translate(${x(d.x)}, ${y(d.y)})`);
bar.append("rect")
.attr("x", 1)
.attr("width", width/15)
.attr("height", d => height - y(d.y));
/*
bar.append("text")
.attr("y", -10)
.attr("x", 20)
.attr("text-anchor", "middle")
.text(d => d3.format(",.2f")(d.y));
bar.append("text")
.attr("transform", d => `translate(${0}, ${height - y(d.y) + 20}) rotate(90)`)
.attr("dx", -10)
.attr("dy", -15)
.style("text-anchor", "start")
.text(d => d.user);
*/
svg.append("text")
.text(name)
.attr("x", 10)
.attr("y", 10)
.style("text-anchor", "start")
.style("fill", "#7A7A94")
.style("font-family", "sans-serif");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment