Search code examples
javascriptd3.jslinechartarea-chart

D3 area chart filling above the line


I'm trying to create a d3 area chart with a gradient area fill. But if the graph data starts or ends at value anything other than 0 then the area fills above the line. Here is the working fiddle with the code.

https://jsfiddle.net/damamgova/dhLb67mt/

var svg = d3.select("#" + id)
    .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 + ")");

// Get the data
data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.value = d.value;
});
var area = d3.svg.area()
    .x(function(d) { return x(d.date); })
    .y0(height)
    .y1(function(d) { return y(d.value); })
    // Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
const maxY = d3.max(data, function(d) { return d.value; });
// Add the valueline path.
svg.append("path")
    .attr("class", "line")
    .attr("stroke", color)
    .attr("stroke-width", 3)
    .attr("fill", "url(#gradient)")
    .attr("d", valueline(data));

// Add Gradient
svg.append("linearGradient")
    .attr("id", "gradient")
    .attr("gradientUnits", "userSpaceOnUse")
    .attr("x1", 0).attr("y1", y(0))
    .attr("x2", 0).attr("y2", y(maxY))
    .selectAll("stop")
    .data([
        { offset: "0%", color: "transparent" },
        { offset: "50%", color: color + "50" },
        { offset: "100%", color: color }
    ])
    .enter().append("stop")
    .attr("offset", function(d) { return d.offset; })
    .attr("stop-color", function(d) { return d.color; });

// Add the X Axis
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

// Add the Y Axis
svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);

Solution

  • You are defining an area path generator, but are not using it to generate the path for your line.

    Instead, (in your fiddle, but not in the code above) you have defined a line generator called valueline and are using that to generate the path.

    If there is a fill, the line generator will close the line end with the line start and fill in the enclosed space, which is why you are seeing the fill above the line.

    With the area generator you specify the y0 property, which in the case of your chart will simply be the bottom of the chart. The outputted path will then close along the base-line and any applied fill will be for the whole area.

    Your path definition using the area path generator would be simply:

    svg.append("path")
      .attr("class", "line")
      .attr("stroke", color)
      .attr("stroke-width", 3)
      .attr("fill", "url(#gradient)")
      .attr("d", area(data));
    

    There is an issue with this though, and that is that as you are not using a solid fill for your area and you are using a stroke, the bottom edge of the area will also be stroked. You could add a couple of pixels onto the height of the y0 parameter of your area function so that the x axis covers it...

    https://jsfiddle.net/qp8xvhaL/