Using d3 to shade area between two lines

Since you don’t have datapoints at the intersections, the simplest solution is probably to get the areas above and below each line and use clipPaths to crop the difference.

I’ll assume you’re using d3.svg.line to draw the lines that the areas are based on. This way we’ll be able to re-use the .x() and .y() accessor functions on the areas later:

var trafficLine = d3.svg.line()
  .x(function(d) { return x(d3.time.format("%m/%d/%Y").parse(d.original.date)); })
  .y(function(d) { return y(parseInt(d.original.traffic)); });

var rateLine = d3.svg.line()
  .x(trafficLine.x()) // reuse the traffic line's x
  .y(function(d) { return y(parseInt(d.original.rate)); })

You can create separate area functions for calculating the areas both above and below your two lines. The area below each line will be used for drawing the actual path, and the area above will be used as a clipping path. Now we can re-use the accessors from the lines:

var areaAboveTrafficLine = d3.svg.area()
  .x(trafficLine.x())
  .y0(trafficLine.y())
  .y1(0);
var areaBelowTrafficLine = d3.svg.area()
  .x(trafficLine.x())
  .y0(trafficLine.y())
  .y1(height);
var areaAboveRateLine = d3.svg.area()
  .x(rateLine.x())
  .y0(rateLine.y())
  .y1(0);
var areaBelowRateLine = d3.svg.area()
  .x(rateLine.x())
  .y0(rateLine.y())
  .y1(height);

…where height is the height of your chart, and assuming 0 is the y-coordinate of the top of the chart, otherwise adjust those values accordingly.

Now you can use the area-above functions to create clipping paths like this:

var defs = svg.append('defs');

defs.append('clipPath')
  .attr('id', 'clip-traffic')
  .append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaAboveTrafficLine);

defs.append('clipPath')
  .attr('id', 'clip-rate')
  .append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaAboveRateLine);

The id attributes are necessary because we need to refer to those definitions when actually clipping the paths.

Finally, use the area-below functions to draw paths to the svg. The important thing to remember here is that for each area-below, we need to clip to the opposite area-above, so the Rate area will be clipped based on #clip-traffic and vice versa:

// TRAFFIC IS ABOVE RATE
svg.append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaBelowTrafficLine)
  .attr('clip-path', 'url(#clip-rate)')

// RATE IS ABOVE TRAFFIC
svg.append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaBelowRateLine)
  .attr('clip-path', 'url(#clip-traffic)')

After that you’ll just need to give the two regions different fill colors or whatever you want to do to distinguish them from one another. Hope that helps!

Leave a Comment