Search code examples
javascriptd3.jsstacked-area-chart

Trying to make a stacked area chart


I'm trying to make a stack area chart with 4 categories, all adding up to 100. It looks like I've mapped my data so that there are x, y, y0 values in the newDataset array however the shaded areas are being drawn but they are completely off the chart. here is my fiddle: http://jsfiddle.net/RL_NewtoJS/9rCWG/

Here is the code too

var marginTop = 10;
var marginBottom = 20;
var marginRight = 15;
var marginLeft = 30;
var height = 280 - marginTop - marginBottom;
var width = 480 - marginLeft - marginRight;

var svgSelection = d3.select('#chart1')
    .append("svg")
    .attr("width", width + marginLeft + marginRight)
    .attr("height", height + marginTop + marginBottom);

var baseGroup = svgSelection
    .append("g")
    .attr("transform", "translate("+marginLeft+","+marginTop+")");


var yScale = d3.scale.linear()
    .range([height,0])
    .domain([0,100]);   


var xScale = d3.time.scale()
    .range([0, width]);

var colorScale = d3.scale.ordinal()
    .range(["#4C82C3", "#F37B6D", "#6CC071", "#FFD900"]);

var hoverLabel = d3.scale.ordinal()
    .range(["age1", "age2", "age3", "age4"]);


var yAxis = d3.svg.axis()
    .scale(yScale)
    .tickSize(-width, 0, 0)
    .ticks(5)
    .tickFormat(function(d){if(d==100){return d +"%";}else{return d}})
    .orient("left");


var xBar = d3.svg.axis()
    .scale(xScale)
    .orient("bottom");

var dataset = [
    { year: "2000", age1: 31, age2: 10, age3: 32, age4: 27 },
    { year: "2001", age1: 32, age2: 12, age3: 30, age4: 26 },
    { year: "2002", age1: 24, age2: 19, age3: 32, age4: 25 },
    { year: "2003", age1: 26, age2: 18, age3: 31, age4: 25 },
    { year: "2004", age1: 22, age2: 17, age3: 34, age4: 27 },
    { year: "2004", age1: 24, age2: 17, age3: 33, age4: 26 },
    { year: "2006", age1: 31, age2: 15, age3: 32, age4: 22 },
    { year: "2007", age1: 30, age2: 15, age3: 35, age4: 20 },
    { year: "2008", age1: 27, age2: 18, age3: 31, age4: 24 },
    { year: "2009", age1: 25, age2: 15, age3: 35, age4: 25 },
    { year: "2010", age1: 34, age2: 12, age3: 33, age4: 21 },
    { year: "2011", age1: 31, age2: 14, age3: 32, age4: 23 },
    { year: "2012", age1: 27, age2: 18, age3: 30, age4: 25 },
    { year: "2013", age1: 25, age2: 20, age3: 35, age4: 20 }
];

// each key (age), uses a map to create all the objects for that age
// i in the anonymous function passed to map is the index of the dataset array, so can be used as the ID
var newDataset = ["age1", "age2", "age3", "age4"].map(function(n){
    return dataset.map(function(d, i){
           return { x: i, y: d[n] };
       });
});

d3.layout.stack()(newDataset);

console.log(newDataset);

var parseDate = d3.time.format("%Y").parse;

dataset.forEach(function(d) {
    d.year = parseDate(d.year);
}); 

xScale.domain(d3.extent(dataset, function(d) { return d.year }))

baseGroup.append("g")
      .attr("class", "xaxis")
      .attr("transform", "translate(0," + height + ")")
      .call(xBar);              

baseGroup.append("g")
      .attr("class", "yaxis")
      .call(yAxis);


var area = d3.svg.area()
    .x(function(d) { return xScale(d.x); })
    .y0(function(d) { return yScale(d.y); })
    .y1(function(d) { return yScale(d.y + d.y0); });

var ageGroup = baseGroup.selectAll(".valgroup")
    .data(newDataset)
    .enter()
    .append("g")
    .attr("class", "valgroup")
    .style("fill", function(d, i) { return colorScale(i); })
    .attr("class", function(d, i) { return hoverLabel(i); });

ageGroup.append("path")
    .attr("d", function(d) { return area(d); });

Solution

  • There are two problems with your code. (Note that the code you posted and the code in your fiddle are slightly different, so I'm going off the code in your post.)

    1. Instead of using the array index i for your x-values:

      return { x: i, y: d[n] }; // before
      

      you should be using the year instead. You also need to set the y0 values to 0 explicitly, which you can easily do when creating the newDataset:

      return { x: d.year, y: d[n], y0: 0 }; // after
      
    2. You need to parse the years before you create the newDataset, i.e. it should go in this order:

      // convert years to dates (needs to happen before creating new dataset)
      var parseDate = d3.time.format("%Y").parse;
      dataset.forEach(function(d) {
          d.year = parseDate(d.year);
      }); 
      
      // each key (age), uses a map to create all the objects for that age
      // i in the anonymous function passed to map is the index of the dataset array, so can be used as the ID
      var newDataset = ["age1", "age2", "age3", "age4"].map(function(n){
          return dataset.map(function(d, i){
                 // use year instead of array index i
                 return { x: d.year, y: d[n], y0: 0 };
             });
      });
      

    Here's a working JSFiddle and a screenshot: enter image description here