0

So trying to create a stacked bar graph in D3.js. I have got the axes working, but the graph data isn't showing, any ideas where I'm going wrong?

JS:

var svg = d3.select("#recovery__table"),
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    aspect = width/height,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleBand()
    .rangeRound([0, width])
    .padding(0.1)
    .align(0.1);

var y = d3.scaleLinear()
    .rangeRound([height, 0]);

var z = d3.scaleOrdinal()
    .range(["#717C8B", "#7FDDC3", "#39B3CD"]);

var stack = d3.stack();

data.forEach(function(d) {
    d.year = d['trades.closed_half_year_year'];
    d.loss = d['loss'];
    d.recovered = d['recovered'];
    d.recovery = d['in_recovery'];
    d.total = d.loss + d.recovery + d.recovered;
});

var div = d3.select("body").append("div")
    .attr("class", "tooltip3")
    .style("opacity", "0");

 x.domain(data.map(function(d) { return d.year; }));
 y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
 z.domain(d3.keys(data[0]).filter(function(key){ return key == 'loss' && key == 'recovered' && key == 'in_recovery' }));

 g.selectAll(".serie")
      .data(data)
      .enter().append("rect")
      .attr("class", "bar")
      .attr("fill", function(d){ return z(d.keys); })
      .attr("x", function(d) { return x(d.year); })
      .attr("width", x.bandwidth())
      .attr("y", function(d) { return y(d.total); })
      .attr("height", function(d) { return y[0] - y[1]; })
      .on("mouseover", function(d) {
        var value = parseInt($(this).attr('data-value'));
        div.transition()
          .duration(200)
          .style("opacity", .5);
        div.html(d.data.year + "<br/>£" + total.formatMoney())
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 28) + "px");
      })
      .on("mouseout", function(d) {
        div.transition()
          .duration(500)
          .style("opacity", 0);
      });
    ;

 g.append("g")
      .attr("class", "axis axis--x")
      .attr("transform", "translate(0," + height + ")")
      .attr('x', 20)
      .call(d3.axisBottom(x));

 g.append("g")
      .attr("class", "axis axis--y")
      .call(d3.axisLeft(y).ticks(5, "s"))
      .append("text")
      .attr("x", 2)
      .attr("y", y(y.ticks(10).pop()))
      .attr("dy", "0.35em")
      .attr("text-anchor", "start")
      .attr("fill", "#000");

 var legend = g.selectAll(".legend")
      .data(data)
      .enter().append("g")
      .attr('width', 100)
      .attr("class", "legend")
      .attr('transform', function(d, i) {
        var horz = 100*i;                       // NEW
        var vert = 0;
        if (horz >= width) {
          horz = 100 * (i - 3);
          vert = 40;
        }

        return 'translate(' + horz + ',' + vert + ')';        // NEW
      })
      .style("font", "10px sans-serif");

 legend.append("rect")
      .attr("x", "33%")
      .attr("width", 18)
      .attr("height", 18)
      .attr("fill", z);

 legend.append("text")
      .attr("x", "43%")
      .attr("y", 9)
      .attr("dy", ".35em")
      .attr("text-anchor", "end")
      .text(function(d) { return d; });

JSON example

[{"trades.closed_half_year_year":"2017","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£0.00","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£0.00","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£0.00","loss":"£0.00","recovered":"£0.00","in_recovery":"£0"},
{"trades.closed_half_year_year":"2016","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£123,456.78","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£0.00","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£1,234,234","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£1,321,245.56","loss":"£0.00","recovered":"£457,468.31","in_recovery":"£1,890,567"},
{"trades.closed_half_year_year":"2015","auctioncollectioninfos.total_advanced_amount_delinquent_and_collection_completed_gbp_daily":"£3,345,768.54","auctioncollectioninfos.total_crystallized_loss_gbp_daily":"£555,555.08","auctioncollectioninfos.total_outstanding_amount_delinquent_gbp_daily":"£321,321","auctioncollectioninfos.total_advanced_amount_delinquent_gbp_daily":"£3,321,321.32","loss":"£456,324.33","recovered":"£2,324,234.345","in_recovery":"£333,333"}]

Essentially, need the loss, recovery and recovered to stack on the graph, but there is no data loading onto the graph as previously mentioned.

Any ideas?

1 Answer 1

1

There is a little issue, the data you are using is a JSON thus the object will receive the values as strings, you have to parse them correctly into numbers. An easy way to parse a string to a number is the following:

d.loss = +d['loss'];

But even if we did that we would still have problems with your data. Why? Because some of the numbers in your dataset are formatted:

"loss":"£456,324.33" 

so if you are trying to do something like this:

d.total = d.loss + d.in_recovery + d.recovered;

You will get an invalid value because we may be issuing an operation like the following:

d.total = "£456,324.33" + 0 + "£4,324.33"  // "£456,324.330£4,324.33"

This will screw the scales in our chart.

y.domain([0, d3.max(data, function(d) {
   return d.total;
})]).nice(); // spooky domain here :S

Lets take care of the formatting of your values (assuming values are always formatted the way presented in the JSON you provided):

data.forEach(function(d) {
  d.year = +d['trades.closed_half_year_year'];
  d.loss = typeof d.loss === 'number' ? d.loss : +d['loss'].replace(/£|,/g, '')
  d.recovered = typeof d.recovered === 'number' ? d.recovered : +d['recovered'].replace(/£|,/g, '');
  d.in_recovery = typeof d.in_recovery === 'number' ? d.in_recovery : +d['in_recovery'].replace(/£|,/g, '');
  d.total = d.loss + d.in_recovery + d.recovered;
});

Now that we have a correct dataset we should be ready to start using d3 and the stack layout:

var keys = ['loss', 'recovered', 'in_recovery']; // Declare the keys we will want in our stack
z.domain(keys); // Set them as our z domain so we can retrieve our fill color
var stackLayout = d3.stack().keys(keys)(data); // Create our stack layout

Which will create the following structure:

[
   [
      [
         0,
         0
      ],
      [
         0,
         0
      ],
      [
         0,
         456324.33
      ]
      // key: loss
   ],
   [
      [
         0,
         0
      ],
      [
         0,
         457468.31
      ],
      [
         456324.33,
         2780558.6750000003
      ]
      // key: recovered
   ],
   [
      [
         0,
         0
      ],
      [
         457468.31,
         2348035.31
      ],
      [
         2780558.6750000003,
         3113891.6750000003
      ]
      // key: in_recovery
   ]
]

With the structure above we now can create our bars by key-block, as you can see each array has three values and a key. We will need to create a group element for each array element:

g.selectAll(".serie")
  .data(stackLayout) // Set stack layout as data
  .enter()
  .append("g") // Creating group for each key
  .attr("fill", function(d) { return z(d.key); }) // Fill inner elements with the color provided by our z Scale
  .selectAll("rect") 
  .data(function(d) { // Use the inner array to create our rects
    return d;
  })
  .enter().append("rect")
  .attr("x", function(d) { // Position by our x Scale
    return x(d.data.year);
  })
  .attr("y", function(d) { // Position by our y Scale
    return y(d[1]);
  })
  .attr("height", function(d) { // Find the height value by using the values provided in the inner arrays
    return y(d[0]) - y(d[1]);
  })
  .attr("width", x.bandwidth()); 

We also have to change a little the labels:

var legend = g.selectAll(".legend")
  .data(keys.reverse()) // Use our keys
  .enter().append("g")
  .attr("class", "legend")
  .attr('transform', function(d, i) {
    var horz = width - margin.right - (100 * i); // NEW
    var vert = 0;
    return 'translate(' + horz + ',' + vert + ')'; // NEW
  })
  .style("font", "10px sans-serif");

legend.append("text")
  .attr("x", "-5")
  .attr("y", 9)
  .attr("dy", ".35em")
  .attr("text-anchor", "end")
  .text(function(d) {
    return d;
  });

Working plnkr: https://plnkr.co/edit/eTKsOz8jlaqm1Mf3Esej?p=preview

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.