D3.js How To Draw Stacked Horziontal Bars From Array?
Solution 1:
You've found yourself a rather interesting issue, and it may be related to what we found with another similar problem. See this answer.
To test the theory, here is your code copy and pasted into Stack Overflow snippets. The first one is using d3 3.2.8, and seems to work properly.
/*modified from Mike Bostock at http://bl.ocks.org/3943967 */var data = [
{"key":"FL", "pop1":3000, "pop2":4000, "pop3":5000},
{"key":"CA", "pop1":3000, "pop2":3000, "pop3":3000},
{"key":"NY", "pop1":12000, "pop2":5000, "pop3":13000},
{"key":"NC", "pop1":8000, "pop2":21000, "pop3":11000},
{"key":"SC", "pop1":30000, "pop2":12000, "pop3":8000},
{"key":"AZ", "pop1":26614, "pop2":6944, "pop3":30778},
{"key":"TX", "pop1":8000, "pop2":12088, "pop3":20000}
];
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (pop1, pop2 etc, that's the range(n) part)//then go through each object in data and pull out that objects's population data//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['pop' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], .08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var xx = margin.top;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { returncolor(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { returny(d.x); })
.attr("x", function(d) { returnx(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { returnx(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.8/d3.min.js"></script><divid="container"><sectionid="display"style="width: 1038px;"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"xlink:xlink="http://www.w3.org/1999/xlink"class="tributary_svg"width="677"height="533"></svg></section></div>
Here it is again, this time using d3 3.4.11
/*modified from Mike Bostock at http://bl.ocks.org/3943967 */var data = [
{"key":"FL", "pop1":3000, "pop2":4000, "pop3":5000},
{"key":"CA", "pop1":3000, "pop2":3000, "pop3":3000},
{"key":"NY", "pop1":12000, "pop2":5000, "pop3":13000},
{"key":"NC", "pop1":8000, "pop2":21000, "pop3":11000},
{"key":"SC", "pop1":30000, "pop2":12000, "pop3":8000},
{"key":"AZ", "pop1":26614, "pop2":6944, "pop3":30778},
{"key":"TX", "pop1":8000, "pop2":12088, "pop3":20000}
];
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (pop1, pop2 etc, that's the range(n) part)//then go through each object in data and pull out that objects's population data//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['pop' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], .08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var xx = margin.top;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { returncolor(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { returny(d.x); })
.attr("x", function(d) { returnx(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { returnx(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script><divid="container"><sectionid="display"style="width: 1038px;"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"xlink:xlink="http://www.w3.org/1999/xlink"class="tributary_svg"width="677"height="533"></svg></section></div>
The javascript is identical between the two snippets, and you can plainly see that the behavior is different. So, in short, there is a difference between the way that d3 versions handle the y axis (and in particular the domain attached to the axis).
Here's a version that fixes things up in d3 3.4.11
/*modified from Mike Bostock at http://bl.ocks.org/3943967 */var data = [
{"key":"FL", "pop1":3000, "pop2":4000, "pop3":5000},
{"key":"CA", "pop1":3000, "pop2":3000, "pop3":3000},
{"key":"NY", "pop1":12000, "pop2":5000, "pop3":13000},
{"key":"NC", "pop1":8000, "pop2":21000, "pop3":11000},
{"key":"SC", "pop1":30000, "pop2":12000, "pop3":8000},
{"key":"AZ", "pop1":26614, "pop2":6944, "pop3":30778},
{"key":"TX", "pop1":8000, "pop2":12088, "pop3":20000}
];
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (pop1, pop2 etc, that's the range(n) part)//then go through each object in data and pull out that objects's population data//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
//a[i] = {x: i, y: data[i]['pop' + (d+1)]};
a[i] = {x: data[i].key, y: data[i]['pop' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
//.domain(d3.range(m))
.domain(labels)
.rangeRoundBands([2, height], .08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var xx = margin.top;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { returncolor(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { returny(d.x); })
.attr("x", function(d) { returnx(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { returnx(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
//.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script><divid="container"><sectionid="display"style="width: 1038px;"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"xlink:xlink="http://www.w3.org/1999/xlink"class="tributary_svg"width="677"height="533"></svg></section></div>
There are three differences in this version:
Update the way the
x
value in the stack is referencedlayers = stack(d3.range(n).map(function(d) { var a = []; for (var i = 0; i < m; ++i) { //a[i] = {x: i, y: data[i]['pop' + (d+1)]}; a[i] = {x: data[i].key, y: data[i]['pop' + (d+1)]}; } return a; })),
Change the domain for the
y
scalevar y = d3.scale.ordinal() //.domain(d3.range(m)) .domain(labels) .rangeRoundBands([2, height], .08);
Remove the
.tickValues
call from the y axis. It will use thedomain
of the scale instead.var yAxis = d3.svg.axis() .scale(y) .tickSize(1) .tickPadding(6) //.tickValues(labels) .orient("left");
You can see that this new version works properly in d3 3.4.11.
Here is the fixed version using d3 3.2.8:
/*modified from Mike Bostock at http://bl.ocks.org/3943967 */var data = [
{"key":"FL", "pop1":3000, "pop2":4000, "pop3":5000},
{"key":"CA", "pop1":3000, "pop2":3000, "pop3":3000},
{"key":"NY", "pop1":12000, "pop2":5000, "pop3":13000},
{"key":"NC", "pop1":8000, "pop2":21000, "pop3":11000},
{"key":"SC", "pop1":30000, "pop2":12000, "pop3":8000},
{"key":"AZ", "pop1":26614, "pop2":6944, "pop3":30778},
{"key":"TX", "pop1":8000, "pop2":12088, "pop3":20000}
];
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (pop1, pop2 etc, that's the range(n) part)//then go through each object in data and pull out that objects's population data//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
//a[i] = {x: i, y: data[i]['pop' + (d+1)]};
a[i] = {x: data[i].key, y: data[i]['pop' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
//.domain(d3.range(m))
.domain(labels)
.rangeRoundBands([2, height], .08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var xx = margin.top;
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { returncolor(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { returny(d.x); })
.attr("x", function(d) { returnx(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { returnx(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
//.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.8/d3.min.js"></script><divid="container"><sectionid="display"style="width: 1038px;"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"xlink:xlink="http://www.w3.org/1999/xlink"class="tributary_svg"width="677"height="533"></svg></section></div>
It also seems to work OK in d3 3.2.8, so it should solve your issues.
It's a fun one to diagnose, and the only thing I can think of is that tributary.io is based on an older version of d3, where the axis/domain interaction was working OK (albeit broken) and in your standalone version, you were referencing the latest version, which has fixed what ever the issue was (resulting in a broken visualisation, since your code depended on it).
Note: it was fun figuring this one out, but without the additional comments in my answer on 26029141 it would have been near impossible to diagnose.
Post a Comment for "D3.js How To Draw Stacked Horziontal Bars From Array?"