Skip to content

Instantly share code, notes, and snippets.

@giguerre
Last active March 8, 2018 22:45
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 giguerre/2bfc5c8d8fe9f1546860e12588141a10 to your computer and use it in GitHub Desktop.
Save giguerre/2bfc5c8d8fe9f1546860e12588141a10 to your computer and use it in GitHub Desktop.
Normalized Stacked Bar Chart
license: gpl-3.0

Create a normalized stacked bar chart in d3 (without the stack layout).

The CSV file can contain any number of groups and up to 8 categories. It must not have a total row at the bottom; that row is calculated with Javascript.

Libraries: d3.js, underscore.js, jQuery

forked from HarryStevens's block: Normalized Stacked Bar Chart

group Nbr_sites_Residentiel Nbr_sites_Professionnel nbr_sites_Agriculture Nbr_sites_Industrie Nbr_sites_Tertiaire
Centre-Val de Loire 906640 126713 4710 4172 15610
Occitanie 2303320 347921 3393 9973 39041
Normandie 1313010 179224 1287 5664 21836
Rhône-Alpes 2494410 397240 2100 16419 52399
Bretagne 1039469 142350 3468 5252 19895
Grand Est 1046785 158151 1681 6925 22505
Île-de-France 22942906 3156573 1559 45444 378786
Hauts-de-France 1661420 196512 1674 6670 28362
Pays de Loire 1303399 181454 3133 7521 24928
Bourgogne-Franche 1172031 178454 1094 6273 16993
Nouvelle-Aquitaine 1545197 231156 5216 8771 32592
Alpes-Côte d'Azur 3926570 618555 1706 9668 56441
<html>
<head>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="legend"></div>
<div class="chart"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="scripts.js"></script>
</body>
</html>
var barHeight = 23, barPad = 2, textDy = 16, width = 682;
var margin = {top:0,bottom:0,left:150,right:0}
var svg = d3.select(".chart").append("svg")
.attr("width", width);
var x = d3.scaleLinear()
.range([0,width - margin.left])
.domain([0,100]);
var colorArray = ['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494','#b3b3b3'];
d3.csv("data.csv",types,function(error,data){
var keys = [], legKeys = [];
_.keys(data[0]).forEach(function(d,i){
if (i > 0){
d.includes('pct') ? keys.push(d) : legKeys.push(d);
}
});
data = addTotal(data, legKeys);
// create the legend
$('.legend').css('margin-left', margin.left);
legKeys.forEach(function(legKey,i){
$('.legend').append('<div class="swatch" style="background:' + colorArray[i] + '"></div>' + legKey);
});
// sort the data, putting the total at the end
var sorted = _.sortBy(data,keys[0]).reverse(), total = _.where(sorted,{group:'total'}), rem = _.reject(sorted,{group:'total'}), dat = [];
rem.forEach(function(r){ dat.push(r); });
dat.push(total[0]);
data = dat;
// now that the data is ready, calculate the height
var height = (data.length * barHeight) + (barPad * 3);
d3.select('svg').attr("height", height);
svg.selectAll('.label')
.data(data)
.enter().append('text')
.attr('class', function(d){
var label;
d.group == 'total' ? label = 'label strong' : label = 'label';
return label;
})
.attr('x', margin.left-5)
.attr('y', function(d,i){ return pad(d,i); })
.attr('dy', textDy)
.attr('text-anchor', 'end')
.text(function(d){ return d.group.charAt(0).toUpperCase() + d.group.slice(1); });
keys.forEach(function(key,i){
svg.selectAll('.bar .' + key)
.data(data)
.enter().append('rect')
.attr('class', 'bar ' + key)
.attr('width', function(d,i){ return x(d[key]); })
.attr('height', barHeight - barPad)
.attr('x', function(d){ return widths(keys,key,d); })
.attr('y', function(d,i){ return pad(d,i); });
d3.selectAll('.' + key)
.style('fill', colorArray[i]);
svg.selectAll('.bar-label .' + key)
.data(data)
.enter().append('text')
.attr('class', 'bar-label ' + key)
.attr('x', function(d){
var barX;
key !== keys[keys.length-1] ? barX = widths(keys,key,d) + 5 : barX = width - 5;
return barX;
})
.attr('dy', textDy)
.attr('y', function(d,i){ return pad(d,i); })
.attr('text-anchor', function(d){
var ta;
key == keys[keys.length-1] ? ta = 'end' : ta = 'start';
return ta;
})
.text(function(d){
var text;
d[key] < 5 ? text = '' : text = Math.round(d[key]) + pct(d, data[0].group, data[data.length-1].group);
return text;
});
});
});
function types(d){
var keys = _.keys(d);
var arr = [];
keys.forEach(function(k,i){
if (i!==0){
arr.push(+d[k]);
d[k] = +d[k];
}
});
var sum = arr.reduce(function(a,b){return a+b}, 0);
keys.forEach(function(k,i){
i !== 0 ? d[k + 'pct'] = +d[k]/sum*100 : null;
});
return d;
}
function pad(d,i){
var barI;
d.group == 'total' ? barI = i * barHeight + (barPad * 2) : barI = i * barHeight;
return barI;
}
function pct(d, first, last){
var extra;
d.group == first || d.group == last ? extra = '%' : extra = '';
return extra;
}
function widths(keys,key,d){
if (key == keys[0]) {
return margin.left;
} else if (key == keys[1]){
return margin.left + x(d[keys[0]]);
} else if (key == keys[2]){
return margin.left + x(d[keys[0]]) + x(d[keys[1]]);
} else if (key == keys[3]) {
return margin.left + x(d[keys[0]]) + x(d[keys[1]]) + x(d[keys[2]]);
} else if (key == keys[4]) {
return margin.left + x(d[keys[0]]) + x(d[keys[1]]) + x(d[keys[2]]) + x(d[keys[3]]);
} else if (key == keys[5]) {
return margin.left + x(d[keys[0]]) + x(d[keys[1]]) + x(d[keys[2]]) + x(d[keys[3]]) + x(d[keys[4]]);
} else if (key == keys[6]) {
return margin.left + x(d[keys[0]]) + x(d[keys[1]]) + x(d[keys[2]]) + x(d[keys[3]]) + x(d[keys[4]]) + x(d[keys[5]]);
} else if (key == keys[7]) {
return margin.left + x(d[keys[0]]) + x(d[keys[1]]) + x(d[keys[2]]) + x(d[keys[3]]) + x(d[keys[4]]) + x(d[keys[5]]) + x(d[keys[6]]);
}
}
function addTotal(data,keys){
var totObj = {};
var arrA = [];
keys.forEach(function(key){
var arrB = [];
data.forEach(function(d){
arrB.push(d[key]);
})
var sumB = arrB.reduce(function(a,b){ return a+b }, 0);
totObj[key] = sumB;
arrA.push(sumB);
})
var sumA = arrA.reduce(function(a,b){ return a+b }, 0);
keys.forEach(function(key){
totObj[key + 'pct'] = totObj[key] / sumA * 100;
});
totObj.group = 'total';
data.push(totObj);
return data;
}
body {
font-family: "Helvetica Neue", sans-serif;
margin: 0 auto;
display: table;
}
.label {
font-size: .9em;
}
.label.strong {
font-weight: 900;
}
.bar-label {
font-size: .8em;
fill: #fff;
}
.legend {
font-size: .9em;
margin-bottom: 10px;
}
.swatch {
display: inline-block;
width: 10px;
height: 10px;
margin-right: 4px;
margin-left: 8px;
}
.swatch:first-of-type {
margin-left: 0px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment