Adding the index line and area gradients

We'll look at the first couple of functions we called in the update function: the addGradients, addArea, and addGradients function.
When you run the code with just these functions, you'll see the following:

In this figure, you can see that we've got a colored gradient between the lines, and the lines themselves are also colored. To get these colors, we need to define two gradients, which we can later add as the stroke for the line and the fill for the area. These gradients are SVG gradients (a topic for a book in itself), on which you can find more information here: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Gradients. What we do in the following code is create two linear gradients based on the yIndexed scale:

function addGradients(yIndexed) { 

var rangeMax = yIndexed.invert(0);
var rangeMin = yIndexed.invert(height);

chart.append("linearGradient")
.attr("id", "area-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", yIndexed(100+xRangeAdjusted))
.attr("x2", 0).attr("y2", yIndexed(100-xRangeAdjusted))
.selectAll("stop")
.data([
{offset: "0%", color: "#E5F2D7"},
{offset: "50%", color: "#eee"},
{offset: "100%", color: "#EFDBE3"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });

// For the line gradient we do almost the same
...
.data([
{offset: "0", color: "#97D755"},
{offset: "0.5", color: "#97D755"},
{offset: "0.5", color: "#CD94AB"},
{offset: "1", color: "#CD94AB"}
])
...
}

We'll skip over most of the code, since it's really SVG gradients internals. We use the x1, x2, y1, and y2 attributes the set the size of the gradient to the size of line we want to draw and use the data function to generate the stop points of the gradient. A stop point defines a color at a certain percentage in the gradient. SVG will calculate the required in-between colors. If we apply these gradients to a rectangle, you'd get something like this:

And as you can see, this lines up perfectly with the colors we want for the area beneath the indexed income line and maps to the color of the indexed line. With the colors managed, let's look at how we can draw the lines and the area. We do this in two steps. First, we add the area, and on top of the area we draw our indexed income line.

function addArea(xScale, yIndexedScale, adjustedIndexedData) { 
var area = d3.area()
.x1(function(d) { return xScale(d.date); })
.y1(function(d) { return yIndexedScale(d.indexed); })
.x0(function(d) { return xScale(d.date); })
.y0(function(d) { return yIndexedScale(100); })
.curve(d3.curveCatmullRom.alpha(0.5));

chart.append("path")
.attr("d", area(adjustedIndexedData))
.style("fill", "url(#area-gradient)");
}

To create the SVG string to assign to the d attribute of a path element, we use the d3.area function. To specify the area that needs to be filled, we define two lines. The area between these two lines is filled. So, in our case we draw an horizontal line in the center of the line graph (where the index is 100). In the preceding code that line is defined by the x0 and y0 functions. We also draw the curved line, which is drawn through our data points. This second line is defined by the x1 and y1 functions. The following figure shows what these lines would like when drawn as normal lines:

If we now call the area() function with our data, the area between the lines shown in the previous figure will be filled with the fill defined by the style function. In this case, we point to the gradient we defined earlier. The syntax to do that is url(#name-of-gradient). If you look back at the code, you might see we skipped over the curve function. With this function, we define how the data points (generated by the x0, y0, x1, and y1) are combined into a line. D3 provides different interpolation functions to draw the curve. The following figure shows different approaches:

Most of these curves also allow specific configuration (for example, you can also close them by connecting the last point to the first one). More information on this can be found in the D3 API description on curves: https://github.com/d3/d3-shape/blob/master/README.md#curves.

Now that we have the area, we also draw an additional line on top of this area to better show the values. If you look closely at the following code, you'll see it looks a lot like the code we used to add the area:

function addIndexedLine(xScale, yIndexedScale, adjustedIndexedData) { 
var line = d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yIndexedScale(d.indexed); })
.curve(d3.curveCatmullRom.alpha(0.5));

chart
.append("path")
.attr("d", line(adjustedIndexedData))
.style("fill", "none")
.style("stroke", "url(#line-gradient)")
.style("stroke-width", "2");
}

For a line we use the d3.line function to generate the SVG path that we can assign to the d attribute of the path element we add to the chart. With the x and y functions, we define the points that make up this line, and with the curve function we define what the line will look like.

With the indexed line drawn, we can next draw the unadjusted income line.