d3.js How to draw stacked horziontal bars from array?

I am trying to get this particular example of stacked horizontal bars to work in a browser standalone: http://tributary.io/inlet/4966973 . I know a question has been asked about this code before, but it was never taken to a working example. When I run the code below in a browser (ie outside the tributary site), I get this error: 'Error: Invalid value for attribute transform="translate(0,NaN)"'. Why won't the code run in a browser? Is there some element missing?

<body> <script src="http://d3js.org/d3.v3.min.js"></script> <div id="container"> <section id="display" style="width: 1038px;"> <svg xmlns="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> <script> /*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) { return color(i); }); layer.selectAll("rect") .data(function(d) { return d; }) .enter().append("rect") .attr("y", function(d) { return y(d.x); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(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); </script </body>

Answer1:

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.

<div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false"> <div class="snippet-code snippet-currently-hidden">

/*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) { return color(i); });

 layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.8/d3.min.js"></script>
<div id="container">

<section id="display" style="width: 1038px;">
<svg xmlns="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

<div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false"> <div class="snippet-code snippet-currently-hidden">

/*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) { return color(i); });

 layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container">

<section id="display" style="width: 1038px;">
<svg xmlns="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

<div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false"> <div class="snippet-code snippet-currently-hidden">

/*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) { return color(i); });

 layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="container">

<section id="display" style="width: 1038px;">
<svg xmlns="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:

<ol> <li>

Update the way the x value in the stack is referenced

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; })), </li> <li>

Change the domain for the y scale

var y = d3.scale.ordinal() //.domain(d3.range(m)) .domain(labels) .rangeRoundBands([2, height], .08); </li> <li>

Remove the .tickValues call from the y axis. It will use the domain of the scale instead.

var yAxis = d3.svg.axis() .scale(y) .tickSize(1) .tickPadding(6) //.tickValues(labels) .orient("left"); </li> </ol>

You can see that this new version works properly in d3 3.4.11.

Here is the fixed version using d3 3.2.8:

<div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false"> <div class="snippet-code snippet-currently-hidden">

/*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) { return color(i); });

 layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.2.8/d3.min.js"></script>
<div id="container">

<section id="display" style="width: 1038px;">
<svg xmlns="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.

人吐槽 人点赞

Recommend

Comment

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

查看评论:d3.js How to draw stacked horziontal bars from array?