D3.js How to draw folded horizontal bars from an array?

I am trying to get this specific example of stacked horizontal stripes to work in a standalone browser: http://tributary.io/inlet/4966973 . I know that a question was asked before this question, but it never got into a working example. When I run the code below in the browser (i.e., outside the tributary site), I get this error: 'Error: invalid value for the attribute transform = "translate (0, NaN)". Why does the code not run in the browser? Is there an 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 the range(n) part) //then go through each object in data and pull out that objects 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 dy; }); }), //the largest stack yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + dy; }); }); 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(dx); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(dy); }); 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> 
+2
source share
1 answer

You found yourself a rather interesting problem, and this may be due to what we found with another similar problem. See this answer .

To test the theory, here is your copy of the code and pasted into Stack Overflow fragments. The first uses d3 3.2.8 and seems to work correctly.

 /*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 the range(n) part) //then go through each object in data and pull out that objects 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 dy; }); }), //the largest stack yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + dy; }); }); 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(dx); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(dy); }); 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 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 the range(n) part) //then go through each object in data and pull out that objects 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 dy; }); }), //the largest stack yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + dy; }); }); 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(dx); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(dy); }); 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> 

Javascript is identical between the two snippets, and you can clearly see that the behavior is different. In short, there is a difference between how the d3 versions handle the y axis (and, in particular, the domain attached to the axis).

Here is the version that fixes things 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 the range(n) part) //then go through each object in data and pull out that objects 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 dy; }); }), //the largest stack yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + dy; }); }); 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(dx); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(dy); }); 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:

  • Update the way you specify the value of x on the stack

     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; })), 
  • Change the domain for the y scale

     var y = d3.scale.ordinal() //.domain(d3.range(m)) .domain(labels) .rangeRoundBands([2, height], .08); 
  • Remove the .tickValues call from the y axis. Instead, it will use domain scales.

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

You can see that this new version works correctly 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 the range(n) part) //then go through each object in data and pull out that objects 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 dy; }); }), //the largest stack yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + dy; }); }); 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(dx); }) .attr("x", function(d) { return x(d.y0); }) .attr("height", y.rangeBand()) .attr("width", function(d) { return x(dy); }); 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 works fine in d3 3.2.8, so it should solve your problems.

It's fun 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 works fine (albeit broken) and in your standalone version, you referenced the latest a version that fixes what has ever been a problem (which led to broken visualization, as your code depended on it).

Note: it was interesting to understand this, but without additional comments in my answer 26029141 it would be almost impossible to diagnose.

+3
source

Source: https://habr.com/ru/post/1203349/


All Articles