JamesE
JamesE

Reputation: 3923

d3 area stacked line chart

I'm working on modifying this stacked line chart example: https://bl.ocks.org/d3indepth/e4efd402b4d9fdb2088ccdf3135745c3

I'm adding a time x axis, but I'm struggling with this block of code:

var areaGenerator = d3.area()
  .x(function(d, i) {
      // return i * 100;
      return i * 253.5;
  })
  .y0(function(d) {
      return y(d[0]);
  })
  .y1(function(d) {
      return y(d[1]);
  });

The original example has the .x accessor as i * 100 which seems to be a random value. When I add the X axis the stacked line chart does not line up correctly with the date ticks. I can manually force it to line up by returning i * 253.5 but that is not ideal. I don't really understand how this area function is working - any help would be appreciated.

let height = 600;
let width  = 800;
const yMax = 4000;
//var hEach  = 40;

let margin = {top: 20, right: 15, bottom: 25, left: 25};

width  =    width - margin.left - margin.right;
height =    height - margin.top - margin.bottom;

var svg = d3.select('body').append("svg")
  .attr("width",  width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

let formatDate = d3.timeFormat("%b-%Y")
let parseTime = d3.timeParse("%Y-%m-%d");


let data = [
  {
    "host_count": 2553,
    "container_count": 875,
    "hour": "2019-01-31",
    "apm_host_count": 0,
    "agent_host_count": 2208,
    "gcp_host_count": 0,
    "aws_host_count": 345
  },
  {
    "host_count": 1553,
    "container_count": 675,
    "hour": "2019-02-01",
    "apm_host_count": 0,
    "agent_host_count": 1208,
    "gcp_host_count": 0,
    "aws_host_count": 445
  },
  {
    "host_count": 716,
    "container_count": 6234,
    "hour": "2019-02-02",
    "apm_host_count": 0,
    "agent_host_count": 479,
    "gcp_host_count": 0,
    "aws_host_count": 237
  },
  {
    "host_count": 516,
    "container_count": 4234,
    "hour": "2019-02-03",
    "apm_host_count": 0,
    "agent_host_count": 679,
    "gcp_host_count": 0,
    "aws_host_count": 137
  }
];

  // format the data
  data.forEach(function(d) {
      d.hour = parseTime(d.hour);
  });

  // set the ranges
  var x = d3.scaleTime().range([0, width]);

  x.domain(d3.extent(data, function(d) { return d.hour; }));

  var xAxis = d3.axisBottom(x).ticks(11).tickFormat(d3.timeFormat("%y-%b-%d")).tickValues(data.map(d=>d.hour));


  var y = d3.scaleLinear()
    .domain([0, yMax])
    .range([height, 0]);

  var areaGenerator = d3.area()
  	.x(function(d, i) {
      console.log(d);
  		return i * 100;
  	})
  	.y0(function(d) {
  		return y(d[0]);
  	})
  	.y1(function(d) {
  		return y(d[1]);
  	});

  var colors = ['#FBB65B', '#513551', '#de3163']

  var stack = d3.stack()
    .keys(['agent_host_count', 'aws_host_count', 'container_count']);

  var stackedSeries = stack(data);

  d3.select('g')
  	.selectAll('path')
  	.data(stackedSeries)
  	.enter()
  	.append('path')
  	.style('fill', function(d, i) {
  		return colors[i];
  	})
  	.attr('d', areaGenerator)

  svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);
<!doctype html>
<html lang="en">
  <head>
    <title>Usage</title>
  </head>
  <body>

    <div id="svg"></div>

    <script src="https://d3js.org/d3.v5.min.js"></script>

  </body>
</html>

Upvotes: 1

Views: 1318

Answers (1)

TheGentleman
TheGentleman

Reputation: 2362

When using axes in d3, you actually need to use the axis variable to calculate scaling factors. The functions that do this calculation are returned by the scale*() methods. In your code you have this for the x-axis:

  var x = d3.scaleTime().range([0, width]);

As such, the variable x now contains a function that will do interpolation for you. this is what your areaGenerator function should look like:

var areaGenerator = d3.area()
  .x(function(d, i) {
      return x(d.data.hour);
  })
  .y0(function(d) {
      return y(d[0]);
  })
  .y1(function(d) {
      return y(d[1]);
  });

The only thing you need to remember is that when calculating the value you need to use the same variable that the axis is based on. I.e. your x-axis is a time axis so you need to calculate the interpolation using the time variable (d.data.hour).

As to where 100 comes from in the example, you are essentially correct. In that block the value of 100 is more or less arbitrary. It was likely chosen because the chart looks reasonably good at that scale. By choosing 100, each "tick" is spaced 100px apart and since there is no x-axis to be judged against it doesn't actually matter what is used as long as it changes for each data point.

Upvotes: 1

Related Questions