Created
January 18, 2018 13:56
-
-
Save erochest/d3ea97a455710b6d7c0aa4767c1711ef to your computer and use it in GitHub Desktop.
Tracker Iteration Burnup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width" /> | |
<title>Iteration Burnup</title> | |
<link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8"> | |
</head> | |
<body> | |
<form> | |
<label for="api_token">API Token</label> | |
<input type="text" id="api_token" value="6e1726ef5af3e25ee3a792f5613047d3"/> | |
<label for="project_id">Project ID</label> | |
<input type="text" id="project_id" value="2098949"/> | |
<button id="submit_chart">Chart</button> | |
</form> | |
<svg id="container" width="750" height="340"></svg> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/d3-time.v1.min.js"></script> | |
<script src="https://d3js.org/d3-time-format.v2.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.20.1/moment.min.js"></script> | |
<script src="main.js" charset="utf-8"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
document.addEventListener("DOMContentLoaded", function() { | |
const pivotalApi = "https://www.pivotaltracker.com/services/v5"; | |
const reportFields = new Set([ | |
"points_accepted", "points_rejected", "points_delivered", "points_finished", | |
"points_started", "points_unstarted" | |
]); | |
const getCurrentIteration = (apiToken, projectId) => { | |
const url = `${pivotalApi}/projects/${projectId}?fields=current_iteration_number`; | |
const init = { | |
method: 'GET', | |
headers: {'X-TrackerToken': apiToken} | |
}; | |
console.log('fetching current iteration'); | |
return fetch(url, init) | |
.then((response) => response.json()) | |
.then((json) => json.current_iteration_number); | |
}; | |
const getIterationHistory = (apiToken, projectId, iterationNumber) => { | |
const url = `${pivotalApi}/projects/${projectId}/history/iterations/${iterationNumber}/days`; | |
const init = { | |
method: 'GET', | |
headers: {'X-TrackerToken': apiToken} | |
}; | |
console.log('fetching iteration history'); | |
return fetch(url, init) | |
.then((response) => response.json()); | |
}; | |
const objectify = (header, row) => { | |
const accum = (o, v, i) => { | |
o[v] = row[i]; | |
return o; | |
}; | |
const sumReportFields = (s, v, i) => { | |
if (reportFields.has(v)) { | |
s += row[i]; | |
} | |
return s; | |
}; | |
let obj = header.reduce(accum, {}); | |
let total = header.reduce(sumReportFields, 0); | |
obj.total = total; | |
return obj; | |
} | |
const legendLabel = key => { | |
const label = key.split('_')[1]; | |
return label[0].toUpperCase() + label.substring(1); | |
}; | |
const columnLabel = key => { | |
const day = moment(key); | |
return day.format('MMM D'); | |
}; | |
const chartBurnUp = (iterationData) => { | |
var {header, data} = iterationData; | |
var objects = data.map(row => objectify(header, row)); | |
var svg = d3.select('#container'), | |
margin = {top: 42, right: 20, bottom: 30, left: 40}, | |
width = +svg.attr("width") - margin.left - margin.right, | |
height = +svg.attr("height") - margin.top - margin.bottom, | |
g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); | |
const legendColumn = 40; | |
var x = d3.scaleBand() | |
.rangeRound([0, width]) | |
.padding(0.4) | |
.align(0.1); | |
var y = d3.scaleLinear() | |
.rangeRound([height, 0]); | |
var z = d3.scaleOrdinal() | |
.range(["#73cb34", "#e02626", "#ffad13", "#849ecd", "#f3e958", "#cccdcf"]); | |
var keys = ["points_accepted", "points_rejected", "points_delivered", | |
"points_finished", "points_started", "points_unstarted"]; | |
x.domain(objects.map(o => o.date)); | |
y.domain([0, d3.max(objects, o => o.total)]).nice(); | |
z.domain(keys); | |
g.append('g') | |
.selectAll('g') | |
.data(d3.stack().keys(keys)(objects)) | |
.enter().append('g') | |
.attr('fill', d => z(d.key)) | |
.selectAll('rect') | |
.data(d => d) | |
.enter().append('rect') | |
.attr('x', d => x(d.data.date)) | |
.attr('y', d => y(d[1])) | |
.attr('height', d => y(d[0]) - y(d[1])) | |
.attr('width', x.bandwidth()); | |
const xAxis = d3.axisBottom(x) | |
.tickFormat(d => moment(d).format('MMM D')); | |
g.append('g') | |
.attr('class', 'axis') | |
.attr('transform', `translate(0, ${height})`) | |
.call(xAxis); | |
g.append('g') | |
.attr('class', 'axis') | |
.call(d3.axisLeft(y).ticks(null, 's')) | |
.append('text') | |
.attr('x', 2) | |
.attr('y', y(y.ticks().pop()) + 0.5) | |
.attr('dy', '0.32em') | |
.attr('fill', '#000') | |
/* | |
*.attr('font-weight', 'bold') | |
*.attr('text-anchor', 'start') | |
*.text('Points') | |
*/ | |
; | |
// TODO: dotted lines going across the background | |
// TODO: fill in empty data for today and future dates | |
var legend = g.append('g') | |
.attr('font-family', 'sans-serif') | |
.attr('font-size', 10) | |
.attr('text-anchor', 'end') | |
.attr('transform', 'translate(0, -30)') | |
.selectAll('g') | |
.data(keys) | |
.enter().append('g') | |
.attr('transform', (d, i) => `translate(${i * legendColumn}, 0)`); | |
legend.append('rect') | |
.attr('x', (d, i) => 50 + i * legendColumn) | |
.attr('width', 19) | |
.attr('height', 19) | |
.attr('fill', z); | |
legend.append('text') | |
.attr('x', (d, i) => 50 + i * legendColumn - 5) | |
.attr('y', 9.5) | |
.attr('dy', '0.32em') | |
.text(legendLabel); | |
}; | |
document.getElementById("submit_chart").addEventListener('click', (event) => { | |
const apiToken = document.getElementById("api_token").value; | |
const projectId = document.getElementById("project_id").value; | |
event.preventDefault(); | |
getCurrentIteration(apiToken, projectId) | |
.then(iterationNumber => getIterationHistory(apiToken, projectId, iterationNumber)) | |
.then(chartBurnUp); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#container { | |
height: 340px; | |
width: 750px; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment