Each colored bar represents a different step / stage in the hiring process
Built with blockbuilder.org
license: mit |
Each colored bar represents a different step / stage in the hiring process
Built with blockbuilder.org
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { width: 100%; height: 100%; } | |
a {position: absolute; right: 15px; padding: 5px; border-bottom: 3px solid black; cursor: pointer;} | |
</style> | |
</head> | |
<body> | |
<a onclick="toggleStage();">Toggle</a> | |
<script> | |
var colors = d3.scale.category20c(); | |
var TIME = [ | |
{ | |
"team": "Engineering", | |
"stages": [ | |
{"text": "New applicant", "days": 35, "candidates": 11156}, | |
{"text": "New lead", "days": 180, "candidates": 1503}, | |
{"text": "Recruiter Screen", "days": 32, "candidates": 1230}, | |
{"text": "Phone Interview", "days": 25, "candidates": 1322}, | |
{"text": "2nd Phone Interview", "days": 8, "candidates": 13}, | |
{"text": "Initial Onsite", "days": 29, "candidates": 176}, | |
{"text": "Homework", "days": 16, "candidates": 240}, | |
{"text": "On-site interview", "days": 34, "candidates": 540}, | |
{"text": "Offer", "days": 31, "candidates": 179} | |
] | |
}, | |
{ | |
"team": "Marketing", | |
"stages": [ | |
{"text": "New applicant", "days": 38, "candidates": 4317}, | |
{"text": "New lead", "days": 26, "candidates": 575}, | |
{"text": "Recruiter Screen", "days": 25, "candidates": 584}, | |
{"text": "Phone Interview", "days": 25, "candidates": 378}, | |
{"text": "2nd Phone Interview", "days": 14, "candidates": 14}, | |
{"text": "Initial Onsite", "days": 35, "candidates": 113}, | |
{"text": "Homework", "days": 28, "candidates": 197}, | |
{"text": "On-site interview", "days": 21, "candidates": 69}, | |
{"text": "Offer", "days": 106, "candidates": 80} | |
] | |
}, | |
{ | |
"team": "Operations", | |
"stages": [ | |
{"text": "New applicant", "days": 46, "candidates": 5427}, | |
{"text": "New lead", "days": 42, "candidates": 586}, | |
{"text": "Recruiter Screen", "days": 37, "candidates": 435}, | |
{"text": "Phone Interview", "days": 22, "candidates": 340}, | |
{"text": "2nd Phone Interview", "days": 11, "candidates": 22}, | |
{"text": "Initial Onsite", "days": 35, "candidates": 166}, | |
{"text": "Homework", "days": 12, "candidates": 73}, | |
{"text": "On-site interview", "days": 24, "candidates": 87}, | |
{"text": "Offer", "days": 18, "candidates": 53} | |
] | |
}, | |
{ | |
"team": "Creative", | |
"stages": [ | |
{"text": "New applicant", "days": 190, "candidates": 1286}, | |
{"text": "New lead", "days": 23, "candidates": 168}, | |
{"text": "Recruiter Screen", "days": 78, "candidates": 152}, | |
{"text": "Phone Interview", "days": 42, "candidates": 49}, | |
{"text": "2nd Phone Interview", "days": 0, "candidates": 0}, | |
{"text": "Initial Onsite", "days": 83, "candidates": 46}, | |
{"text": "Homework", "days": 18, "candidates": 11}, | |
{"text": "On-site interview", "days": 56, "candidates": 17}, | |
{"text": "Offer", "days": 19, "candidates": 6} | |
] | |
}, | |
{ | |
"team": "Support", | |
"stages": [ | |
{"text": "New applicant", "days": 14, "candidates": 2423}, | |
{"text": "New lead", "days": 33, "candidates": 209}, | |
{"text": "Recruiter Screen", "days": 13, "candidates": 212}, | |
{"text": "Phone Interview", "days": 38, "candidates": 56}, | |
{"text": "2nd Phone Interview", "days": 0, "candidates": 0}, | |
{"text": "Initial Onsite", "days": 18, "candidates": 144}, | |
{"text": "Homework", "days": 5, "candidates": 239}, | |
{"text": "On-site interview", "days": 13, "candidates": 71}, | |
{"text": "Offer", "days": 24, "candidates": 23} | |
] | |
}, | |
{ | |
"team": "People", | |
"stages": [ | |
{"text": "New applicant", "days": 30, "candidates": 3182}, | |
{"text": "New lead", "days": 31, "candidates": 369}, | |
{"text": "Recruiter Screen", "days": 19, "candidates": 332}, | |
{"text": "Phone Interview", "days": 14, "candidates": 117}, | |
{"text": "2nd Phone Interview", "days": 7, "candidates": 1}, | |
{"text": "Initial Onsite", "days": 21, "candidates": 173}, | |
{"text": "Homework", "days": 4, "candidates": 8}, | |
{"text": "On-site interview", "days": 32, "candidates": 81}, | |
{"text": "Offer", "days": 17, "candidates": 27 } | |
] | |
}, | |
{ | |
"team": "Finance", | |
"stages": [ | |
{"text": "New applicant", "days": 168, "candidates": 1083}, | |
{"text": "New lead", "days": 7, "candidates": 60}, | |
{"text": "Recruiter Screen", "days": 23, "candidates": 59}, | |
{"text": "Phone Interview", "days": 49, "candidates": 37}, | |
{"text": "2nd Phone Interview", "days": 0, "candidates": 0}, | |
{"text": "Initial Onsite", "days": 43, "candidates": 32}, | |
{"text": "Homework", "days": 2, "candidates": 8}, | |
{"text": "On-site interview", "days": 136, "candidates": 13}, | |
{"text": "Offer", "days": 53, "candidates": 5} | |
] | |
}, | |
{ | |
"team": "Legal", | |
"stages": [ | |
{"text": "New applicant", "days": 11, "candidates": 1055}, | |
{"text": "New lead", "days": 8, "candidates": 154}, | |
{"text": "Recruiter Screen", "days": 6, "candidates": 134}, | |
{"text": "Phone Interview", "days": 16, "candidates": 102}, | |
{"text": "2nd Phone Interview", "days": 0, "candidates": 0}, | |
{"text": "Initial Onsite", "days": 31, "candidates": 59}, | |
{"text": "Homework", "days": 0, "candidates": 5}, | |
{"text": "On-site interview", "days": 19, "candidates": 25}, | |
{"text": "Offer", "days": 34, "candidates": 10} | |
] | |
}, | |
] | |
var margin = {top: 0, right: 0, bottom: 0, left: 0}; | |
var width = 960 - margin.left - margin.right; | |
var height = 500 - margin.top - margin.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 + ")") | |
.attr('id', 'transformGroup') | |
// Get max time | |
var totals = {}; | |
totals.days = TIME.reduce(function(prevMax, currentTeam){ | |
var currentDays = currentTeam.stages.reduce(function(pv, stage){return pv + stage.days;},0); | |
return Math.max(prevMax, currentDays); | |
},0) | |
totals.candidates = TIME.reduce(function(prevMax, currentTeam){ | |
var currentCandidates = currentTeam.stages.reduce(function(pv, stage){return pv + stage.candidates;},0); | |
return Math.max(prevMax, currentCandidates); | |
},0) | |
// INITIAL RENDER | |
TIME.forEach(function(team, idx){ | |
team.stages.forEach(function(currentStage, jdx){ | |
totals[currentStage.text + '-days'] = Math.max((totals[currentStage.text + '-days']||0), currentStage.days) | |
totals[currentStage.text + '-candidates'] = Math.max((totals[currentStage.text + '-candidates']||0), currentStage.candidates) | |
if (jdx === 0){ | |
TIME[idx].stages[jdx].cumulDays = 0; | |
TIME[idx].stages[jdx].cumulCandidates = 0; | |
} else { | |
var prevConversion = TIME[idx].stages[jdx - 1]; | |
TIME[idx].stages[jdx].cumulDays = prevConversion.cumulDays + prevConversion.days; | |
TIME[idx].stages[jdx].cumulCandidates = prevConversion.cumulCandidates + prevConversion.candidates; | |
} | |
}) | |
// console.log(totals.days, team.stages[team.stages.length-1].cumulDays + team.stages[team.stages.length-1].days) | |
d3.select('#transformGroup').append('g').attr('class', team.team) | |
.selectAll('rect').data(team.stages).enter().append('rect') | |
.attr({ | |
width: function(d){return width * d.days/totals.days;}, | |
x: function(d){ | |
var barWidth = width * d.cumulDays/totals.days; | |
return barWidth; | |
}, | |
height: height * 1 / TIME.length, | |
y: function(d, i){ | |
var barHeight = height * idx / TIME.length; | |
return barHeight; | |
}, | |
stroke: 'black', | |
'stroke-width': 1 | |
}) | |
.style({ | |
fill: function(d,i){ return colors(i)}, | |
opacity: 0.2 | |
}) | |
// APPEND TEAM TEXT | |
var teamTotalDays = team.stages[team.stages.length - 1].cumulDays + team.stages[team.stages.length - 1].days | |
d3.select('.' + team.team).append('text') | |
.text(team.team + ': ' + teamTotalDays + ' days') | |
.attr({ | |
x: 0, | |
y: function(d){ | |
var barHeight = height * idx / TIME.length; | |
return barHeight + 14; | |
} | |
}) | |
}) | |
var scaleStage = true; | |
function toggleStage(){ | |
scaleStage = !scaleStage; | |
TIME.forEach(function(team, idx){ | |
d3.select('.' + team.team).selectAll('rect').data(team.stages) | |
.transition().duration(1000) | |
.attr({ | |
width: function(d){ | |
if (scaleStage){ | |
return width * d.days/totals.days; | |
} else { | |
return (width * 1/(TIME.length + 1)) * d.days / totals[d.text + '-days']; | |
} | |
}, | |
x: function(d, i){ | |
if (scaleStage){ | |
var barWidth = width * d.cumulDays/totals.days; | |
return barWidth; | |
} else { | |
return width * i/(TIME.length + 1); | |
} | |
} | |
}) | |
}) | |
} | |
</script> | |
</body> |