Skip to content

Instantly share code, notes, and snippets.

@Thanaporn-sk
Created March 13, 2017 04:04
Show Gist options
  • Save Thanaporn-sk/58691b32263e5b82f23bbc52fbd36354 to your computer and use it in GitHub Desktop.
Save Thanaporn-sk/58691b32263e5b82f23bbc52fbd36354 to your computer and use it in GitHub Desktop.
Individual to stacked area charts
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.x-axis .tick line {
stroke: #EFEFEF;
}
path {
animation: dash 2s linear infinite;
}
@keyframes dash {
to {
stroke-dashoffset: -16;
}
}
</style>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
function generateRandomData(years){
var randomData = [];
for (var i = 0; i < years; i++){
randomData.push({type: 'local', year: 2000 + i, taxRate: _randomLocalData()});
randomData.push({type: 'state', year: 2000 + i, taxRate: _randomStateData()});
randomData.push({type: 'federal', year: 2000 + i, taxRate: _randomFederalData()});
}
return randomData;
function _randomLocalData(){return Math.min(0.04, Math.random()/10);}
function _randomStateData(){return Math.min(0.11, Math.random()/8);}
function _randomFederalData(){return Math.min(0.25, Math.random()/4);}
}
</script>
<script>
////// COMPOSE DATA
var taxData = generateRandomData(20, 100000);
var taxDataByYear = d3.nest()
.key(function(d){return d.year;})
.entries(taxData);
var taxDataByType = d3.nest()
.key(function(d){return d.type;})
.entries(taxData);
var taxDataForStack = taxDataByYear.reduce(function(taxData, data){
var newData = {year: data.key};
data.values.forEach(function(d){newData[d.type] = d.taxRate;});
taxData.push(newData);
return taxData;
}, [])
var years = taxDataByYear.map(function(d){return +d.key;})
var localTaxData = taxDataByType[0].values;
var stateTaxData = taxDataByType[1].values;
var federalTaxData = taxDataByType[2].values;
////// SETUP CONTAINERS
var svg = d3.select('svg')
var margin = {top: 40, right: 40, bottom: 50, left: 60};
var height = +svg.attr('height') - margin.top - margin.bottom;
var width = +svg.attr('width') - margin.left - margin.right;
var chartPadding = 15;
var chartHeight = height / taxDataByType.length - 15;
////// COMPOSE GENERATORS
var generators = {
'shared': {
color: d3.scaleOrdinal(d3.schemePastel1).domain(['local', 'state', 'federal']),
stack: d3.stack().keys(['local', 'state', 'federal']).order(d3.stackOrderDescending).offset(d3.stackOffsetNone),
x: d3.scaleLinear().domain([d3.min(years), d3.max(years)]).range([0, width]),
y: d3.scaleLinear().domain([d3.max(federalTaxData, function(d){return d.taxRate}), 0]).range([0, height])
},
'local': {},
'state': {},
'federal': {y: d3.scaleLinear().domain([d3.max(federalTaxData, function(d){return d.taxRate}), 0]).range([0, chartHeight])}
};
var stackTaxData = generators.shared.stack(taxDataForStack);
var maxStackData = d3.max(stackTaxData, function(d){return d3.max(d, function(d){return d[1];});});
generators.shared.xAxis = d3.axisBottom(generators.shared.x).tickValues(years).tickFormat(d3.format('.4r')).tickSizeInner(-height);
generators.shared.y = d3.scaleLinear().domain([maxStackData, 0]).range([0, height]);
generators.shared.yAxis = d3.axisLeft(generators.shared.y).tickFormat(d3.format('.2f'));
generators.shared.area = d3.area().x(function(d){return generators.shared.x(d.data.year);})
.y0(function(d){return generators.shared.y(d[0]);}).y1(function(d){return generators.shared.y(d[1]);})
.curve(d3.curveMonotoneX)
generators.local.area = d3.area(localTaxData).x(function(d){return generators.shared.x(d.year);})
.y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
.curve(d3.curveMonotoneX);
generators.state.area = d3.area(stateTaxData).x(function(d){return generators.shared.x(d.year);})
.y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
.curve(d3.curveMonotoneX);
generators.federal.yAxis = d3.axisLeft(generators.federal.y).ticks(4).tickFormat(d3.format('.2f'));
generators.federal.area = d3.area(federalTaxData).x(function(d){return generators.shared.x(d.year);})
.y0(function(d){return generators.federal.y(0);}).y1(function(d){return generators.federal.y(d.taxRate);})
.curve(d3.curveMonotoneX);
////// MAKE IT ALIVE!
var grouping = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
grouping.append('g')
.call(generators.shared.xAxis)
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'x-axis');
containers = grouping.selectAll('.containers').data(taxDataByType).enter()
.append('g')
.attr('transform', function(d, i){ return 'translate(0, ' + ((chartHeight + chartPadding) * i) + ')'; })
.attr('class', 'containers')
containers.append('g')
.attr('class', 'y-axis')
.call(generators.federal.yAxis)
containers.append('path')
.attr('d', function(d){return generators[d.key].area(d.values);})
.attr('class', 'stack-container')
.attr('stroke', 'gray')
.attr('stroke-dasharray', '8 8')
.attr('fill', function(d){return generators.shared.color(d.key)})
var stacked = false;
setInterval(function(){
var selection = d3.selectAll('.containers')
if (stacked){
selection.data(taxDataByType);
selection
.transition().duration(1000)
.attr('transform', function(d, i){ return 'translate(0, ' + ((chartHeight + chartPadding) * i) + ')'; })
.select('.y-axis')
.call(generators.federal.yAxis);
selection.select('.stack-container')
.transition().duration(1000)
.attr('d', function(d){return generators[d.key].area(d.values);});
} else {
selection.data(stackTaxData);
selection
.transition().duration(1000)
.attr('transform', 'translate(0,0)')
.select('.y-axis')
.call(generators.shared.yAxis)
selection.select('.stack-container')
.transition().duration(1000)
.attr('d', generators.shared.area);
}
stacked = !stacked;
}, 2000)
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment