Built with blockbuilder.org
forked from sxywu's block: Chase Data: Bar Chart
Built with blockbuilder.org
forked from sxywu's block: Chase Data: Bar Chart
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://npmcdn.com/babel-core@5.8.34/browser.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js'></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
</head> | |
<body> | |
<input id="loadfile" type="file" multiple /> | |
<svg></svg> | |
<div id='data'></div> | |
<script type="text/babel"> | |
/************** | |
* This section modified from @enjalot's block: | |
http://bl.ocks.org/enjalot/63d06e2ccadad0cb30dc5f920efd1cdf | |
***************/ | |
d3.select("#loadfile").node() | |
.addEventListener('change', loadFile, false); | |
function loadFile(evt) { | |
evt.stopPropagation(); | |
evt.preventDefault(); | |
var files; | |
if(evt.dataTransfer) { | |
files = evt.dataTransfer.files; | |
} else { | |
files = evt.target.files; | |
} | |
var i = 0; | |
var reader = new FileReader(); | |
reader.onload = function(e) { | |
parseData(e.target.result); | |
// start the next | |
i += 1; | |
files[i] && reader.readAsText(files[i]); | |
} | |
reader.readAsText(files[i]); | |
} | |
var allTransactions = []; | |
function parseData(result) { | |
var transactions = d3.csv.parse(result); | |
// go through each transaction | |
_.each(transactions, transaction => { | |
if (transaction['Type'] === 'Sale') { | |
allTransactions.push({ | |
date: new Date(transaction['Trans Date']), | |
amount: -1 * parseFloat(transaction['Amount']), | |
title: transaction['Description'], | |
}); | |
} | |
}); | |
updateGraph(allTransactions); | |
} | |
// draw bar chart | |
var width = 900; | |
var height = 500; | |
var barWidth = 1; | |
var padding = {top: 40, left: 20}; | |
var linearGradient = d3.select('svg').append('defs') | |
.append("linearGradient") | |
.attr("id", "linear-gradient") | |
.attr({gradientUnits: 'userSpaceOnUse', | |
x1: 0, | |
y1: 0, | |
x2: 0, | |
y2: height - padding.top}); | |
var svg = d3.select('svg') | |
.attr({width, height}) | |
.append('g'); | |
var timeFormat = d3.time.format('%a %b %e'); | |
// do da linear gradient | |
linearGradient.append("stop") | |
.attr("offset", 0) | |
.attr("stop-color", "#017351"); // start at red | |
/** | |
linearGradient.append("stop") | |
.attr("offset", '50%') | |
.attr("stop-color", "#03c383"); **/ | |
linearGradient.append("stop") | |
.attr("offset", '100%') | |
.attr("stop-color", "#aad962"); // end at green | |
// initialize scales and axis | |
var colorScale = d3.scale.linear() | |
.range(["#aad962", "#03c383", "#017351"]); | |
var timeScale = d3.time.scale() | |
.range([0, width - padding.left]); | |
var xAxis = d3.svg.axis() | |
.orient('bottom') | |
.scale(timeScale); | |
var heightScale = d3.scale.linear() | |
.range([0, height - padding.top]); | |
var yAxis = d3.svg.axis() | |
.orient('left') | |
.scale(heightScale); | |
// initialize the containers | |
var barsG = svg.append('g') | |
.attr('transform', 'translate(' + padding.left + ',0)'); | |
var xAxisG = svg.append('g') | |
.classed('axis', true) | |
.attr('transform', | |
'translate(' + padding.left + ',' + (height - padding.top) + ')'); | |
var yAxisG = svg.append('g') | |
.classed('axis', true) | |
.attr('transform', 'translate(' + padding.left + ',0)'); | |
function updateGraph(data) { | |
// first group the data into days | |
data = _.chain(data) | |
.groupBy(d => d.date) | |
.map((transactions) => { | |
return { | |
date: transactions[0].date, | |
transactions: _.sortBy(transactions, t => t.amount), | |
total: _.reduce(transactions, (sum, t) => { | |
return t.amount ? sum + t.amount : sum; | |
}, 0), | |
}; | |
}).sortBy(d => d.date).value(); | |
barWidth = Math.floor((width - padding.left) / data.length) - 2; | |
// update the scales | |
timeScale.domain([data[0].date, _.last(data).date]); | |
var maxTotal = d3.max(data, d => d.total); | |
heightScale.domain([0, maxTotal]); | |
colorScale.domain([0, maxTotal / 2, maxTotal]); | |
xAxisG.call(xAxis); | |
// yAxisG.call(yAxis); | |
var bars = barsG.selectAll('g') | |
.data(data, d => d.date); | |
bars.enter().append('g'); | |
bars.exit().remove(); | |
bars.attr('transform', d => { | |
return 'translate(' + timeScale(d.date) + ',' + | |
(height - padding.top - heightScale(d.total)) + ')'; | |
}); | |
var bar = bars.selectAll('rect') | |
.data(d => d.transactions); | |
bar.enter().append('rect'); | |
bar.exit().remove(); | |
var allY = 0; | |
bar.attr({ | |
x: -barWidth / 2, | |
y: (d, i) => { | |
var y = allY; | |
allY += heightScale(d.amount); | |
if (!i) { | |
// if this is the first rect | |
y = 0; | |
allY = heightScale(d.amount); | |
} | |
return y; | |
}, | |
width: barWidth, | |
height: d => heightScale(d.amount), | |
fill: d => colorScale(d.amount), | |
'fill-opacity': 0.5, | |
stroke: d => colorScale(d.amount), | |
cursor: 'pointer', | |
}).on('mouseover', d => { | |
var html = d.title + ' '; | |
html += '(' + timeFormat(d.date) + '): '; | |
html += '<b>$' + d.amount + '</b><br>'; | |
d3.select('#data') | |
.html(html); | |
}); | |
// move the group to the correct place | |
/** | |
bars | |
.attr({ | |
width: barWidth, | |
height: d => heightScale(d.amount), | |
fill: d => colorScale(d.amount), | |
stroke: '#fff', | |
}) | |
; | |
**/ | |
} | |
</script> | |
</body> |