I am trying to make a bar chart out of some grouped data. This is dummy data, but the structure is basically the same. The data: election results includes a bunch of candidates, organized into the districts they were running in, and the total vote count:
district,candidate,votes
Dist 1,Leticia Putte,3580
Dist 2,David Barron,1620
Dist 2,John Higginson,339
Dist 2,Walter Bannister,2866
[...]
I'd like to create a bar or column chart (either, honestly, though my end goal is horizontal) that groups the candidates by district.
Mike Bostock has an excellent demo but I'm having trouble translating it intelligently for my purposes. I started to tease it out at https://jsfiddle.net/97ur6cwt/6/ but my data is organized somewhat differently -- instead of rows, by group, I have a column that sets the category. And there might be just one candidate or there might be a few candidates.
Can I group items if the groups aren't the same size?
My answer is similar to @GerardoFurtado but instead I use a d3.nest
to build a domain per district. This removes the need for hardcoding values and cleans it up a bit:
y0.domain(data.map(function(d) { return d.district; }));
var districtD = d3.nest()
.key(function(d) { return d.district; })
.rollup(function(d){
return d3.scale.ordinal()
.domain(d.map(function(c){return c.candidate}))
.rangeRoundBands([0, y0.rangeBand()], pad);
}).map(data);
districtD
becomes a map of domains for your y-axis which you use when placing the rects:
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d,i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
.attr("height", function(d){
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
I'm off to a meeting but the next step is to clean up the axis and get the candidate names on there.
Full running code:
var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
var m = {top: 10, right: 10, bottom: 50, left: 110},
w = 800 - m.left - m.right,
h = 500 - m.top - m.bottom,
pad = .1;
var x = d3.scale.linear().range([0, w]);
y0 = d3.scale.ordinal().rangeRoundBands([0, h], pad);
var color = d3.scale.category20c();
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickFormat(d3.format("$,.0f"));
var svg = d3.select("#chart").append("svg")
.attr("width", w + m.right + m.left + 100)
.attr("height", h + m.top + m.bottom)
.append("g")
.attr("transform",
"translate(" + m.left + "," + m.top + ")");
// This moves the SVG over by m.left(110)
// and down by m.top (10)
d3.csv(url, function(error, data) {
data.forEach(function(d) {
d.votes = +d.votes;
});
y0.domain(data.map(function(d) { return d.district; }));
districtD = d3.nest()
.key(function(d) { return d.district; })
.rollup(function(d){
console.log(d);
return d3.scale.ordinal()
.domain(d.map(function(c){return c.candidate}))
.rangeRoundBands([0, y0.rangeBand()], pad);
})
.map(data);
x.domain([0, d3.max(data, function(d) {
return d.votes;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d,i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
.attr("height", function(d){
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
svg.selectAll(".label")
.data(data)
.enter().append("text")
.text(function(d) {
return (d.votes);
})
.attr("text-anchor", "start")
.attr("x", function(d) { return x(d.votes)})
.attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand()/2;})
.attr("class", "axis");
});
.axis {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
An alternate version which sizes the bars the same and scales the outer domain appropriately:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<style>
.label {
font: 10px sans-serif;
}
.axis {
font: 11px sans-serif;
font-weight: bold;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
var m = {
top: 10,
right: 10,
bottom: 50,
left: 110
},
w = 800 - m.left - m.right,
h = 500 - m.top - m.bottom,
pad = .1, padPixel = 5;
var x = d3.scale.linear().range([0, w]);
var y0 = d3.scale.ordinal();
var color = d3.scale.category20c();
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickFormat(d3.format("$,.0f"));
var svg = d3.select("#chart").append("svg")
.attr("width", w + m.right + m.left + 100)
.attr("height", h + m.top + m.bottom)
.append("g")
.attr("transform",
"translate(" + m.left + "," + m.top + ")");
// This moves the SVG over by m.left(110)
// and down by m.top (10)
d3.csv(url, function(error, data) {
data.forEach(function(d) {
d.votes = +d.votes;
});
var barHeight = h / data.length;
y0.domain(data.map(function(d) {
return d.district;
}));
var y0Range = [0];
districtD = d3.nest()
.key(function(d) {
return d.district;
})
.rollup(function(d) {
var barSpace = (barHeight * d.length);
y0Range.push(y0Range[y0Range.length - 1] + barSpace);
return d3.scale.ordinal()
.domain(d.map(function(c) {
return c.candidate
}))
.rangeRoundBands([0, barSpace], pad);
})
.map(data);
y0.range(y0Range);
x.domain([0, d3.max(data, function(d) {
return d.votes;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", function(d, i) {
return color(d.district);
})
.attr("x", 0)
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate);
})
.attr("height", function(d) {
return districtD[d.district].rangeBand();
})
.attr("width", function(d) {
return x(d.votes);
});
var ls = svg.selectAll(".labels")
.data(data)
.enter().append("g");
ls.append("text")
.text(function(d) {
return (d.votes);
})
.attr("text-anchor", "start")
.attr("x", function(d) {
return x(d.votes)
})
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
})
.attr("class", "label");
ls.append("text")
.text(function(d) {
return (d.candidate);
})
.attr("text-anchor", "end")
.attr("x", -2)
.attr("y", function(d) {
return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
})
.style("alignment-baseline", "middle")
.attr("class", "label");
});
</script>
</body>
</html>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With