AliShahVis
AliShahVis

Reputation: 141

D3.js - Stacked Barchart from JSON?

I have been trying to recreate this fiddle: https://jsfiddle.net/1c5eqrp3/3/

In the context of a horizontal stacked bar chart that I am building using my data.

var bar1Data = [{
    position: 1,
    label: '70k',
    value: 1708026
  },
  {
    position: 2,
    label: '71K - 149K',
    value: 1915059
  },
];

//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
  return d3.ascending(a.position, b.position);
})

var colors = ['green', 'blue'];

var bars = d3.select('#bars')
  .append('svg')
  .attr('width', 800)
  .attr('height', 200);

bars.selectAll('rect')
  .data(bar1Data)
  .enter()
  .append('rect')
  .attr('width', function(d) {
    return d;
  })
  .attr('x', function(d, i) {
    return sum(bar1Data.value, 0, i);
  })
  .attr('fill', function(d, i) {
    return colors[i];
  })
  .attr('y', 0)
  .attr('height', 100);

function sum(array, start, end) {
  var total = 0;
  for (var i = start; i < end; i++) total += array[i];
  return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="bar"></div>

All was going well, until I tried to get it to read the bar1Data I created.

Now, I am getting the following errors:

Error: <rect> attribute width: Expected length, "[object Object]".
(anonymous) @ d3.js:1211
selection_each @ d3.js:1176
selection_attr @ d3.js:1233
(anonymous) @ bars3.js:23
Promise.then (async)
(anonymous) @ bars3.js:1
bars3.js:33 Uncaught (in promise) TypeError: Cannot read property '0' of undefined
    at sum (bars3.js:33)
    at SVGRectElement.<anonymous> (bars3.js:26)
    at SVGRectElement.<anonymous> (d3.js:1209)
    at Selection.selection_each [as each] (d3.js:1176)
    at Selection.selection_attr [as attr] (d3.js:1233)
    at bars3.js:25

I have been greatly struggling to create a stacked horizontal bar chart using d3.v5. I have been trying various examples using d3.v4 but I have not been able to get them to work. This was the simplest, most light-weight example I have found. But it does not yet include scales (which I shall hopefully put in a little later).

Could you please help me to solve this issue with an explanation?

Upvotes: 0

Views: 525

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

In .attr('width', function(d){ return d;}), you're setting the width of the bar to the entire object d. You need to understand that .data(bar1Data) creates one entry per value of bar1Data, and that that value is accessible as d in all those functions. That means that, as soon as you've passed .data(bar1Data), you shouldn't directly touch that object anymore.

Now, from your data I seem to understand that you want to use the values of all preceding entries to calculate the cumulative sum up to that point. I will implement that in the answer, but would also like to point you towards stack = d3.stack(), which does it for you.

Note also that your values are too large. You need to transform them using a domain, otherwise you get one pixel per dollar (or other unit of money), which means the bars are drawn 100x the width of your monitor to the right.

var bar1Data = [{
    position: 1,
    label: '70k',
    value: 50
  },
  {
    position: 2,
    label: '71K - 149K',
    value: 40
  },
];

//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
  return d3.ascending(a.position, b.position);
})

var colors = ['green', 'blue'];

var bars = d3.select('#bars')
  .append('svg')
  .attr('width', 800)
  .attr('height', 200);

bars.selectAll('rect')
  .data(bar1Data)
  .enter()
  .append('rect')
  .attr('width', function(d) {
    return d.value;
  })
  .attr('x', function(d, i) {
    // Note that I use `.map` here to take only the `.value` attribute
    // from the data and create an array of numbers.
    return sum(bar1Data.map((e) => e.value), 0, i);
  })
  .attr('fill', function(d, i) {
    return colors[i];
  })
  .attr('y', 0)
  .attr('height', 100);

function sum(array, start, end) {
  var total = 0;
  for (var i = start; i < end; i++) total += array[i];
  return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id='bars'>
</div>

Upvotes: 1

Related Questions