Search code examples
javascriptd3.jslinecharttrend

Plotting a trendline with D3 - Actual & Forecast


I have a multi-series line chart with a trend line for each series. My data is formatted as such that the first half is actual data and the second is forecast (predicted) data. So for the first 32 weeks, I have real data and then those fields go to 0. Then the forecast fields have numbers and the actual part is 0. You can see this by looking at the data. I handle this with an if statement.

Both series' line are accurate.

My issue is the trendline for the forecast is wrong, and I haven't been able to figure it out because it's correct for the actual part.

You can see the difference in the charts if you change the config parameter from "forecast" to "actual" here:

"config": {
    "measure": "DNGP",
    "time": "forecast"
  }

I use this method I found online to add a trendline to the chart:

var xSeries;
  var ySeries;
  var leastSquaresCoeff1;
  var x1;
  var y1;
  var x2;
  var y2;
  var trendData = []
  var trendline;
  var leastSquaresCoeff;

  // get the x and y values for least squares
  xSeries = d3.range(d3.min(actualData, function(d) {
    return d.items[columns.indexOf("Week")];
  }), d3.max(actualData, function(d) {
    return d.items[columns.indexOf("Week")];
  }) + 1);
  ySeries = actualData.map(function(d) {
    return parseFloat(d.items[columns.indexOf("Measure")])
  });

  leastSquaresCoeff = leastSquares(xSeries, ySeries);

  // apply the reults of the least squares regression
  x1 = actualData[0].items[columns.indexOf("Week")];
  y1 = leastSquaresCoeff[0] + leastSquaresCoeff[1];
  x2 = actualData[actualData.length - 1].items[columns.indexOf("Week")];
  y2 = leastSquaresCoeff[0] * xSeries.length + leastSquaresCoeff[1];
  trendData = [
    [x1, y1, x2, y2]
  ];

  trendline = svg.selectAll(".trendline")
    .data(trendData).enter()
    .append("line")
    .attr("class", "trendline")
    .style("stroke-dasharray", ("3, 3"))
    .attr("x1", function(d) {
      return x(d[0]);
    })
    .attr("y1", function(d) {
      return y(d[1]);
    })
    .attr("x2", function(d) {
      return x(d[2]);
    })
    .attr("y2", function(d) {
      return y(d[3]);
    })
    .attr("stroke", "#319455")
    .attr("stroke-width", 1);

You can see my fiddle here (currently set to forecast): http://jsfiddle.net/euLgczjf/4/


Solution

  • I still have no idea why this wasn't working. But I found a different solution using an outside library.

    My solution is based off this example: http://bl.ocks.org/tmcw/3931800/972696d92e86c4f541ec0c89b5657771b40e99fa

    This code no plots the regression lines:

    var regLine = d3.line()
        .x(function(d) { 
            return x(d.Week); 
        })
        .y(function(d) { 
            return y(d.Measure); 
        });
    
      // Derive a linear regression
      var regression = ss.linearRegression(actualData.map(function(d) {
        return [+d.items[columns.indexOf("Week")], d.items[columns.indexOf("Measure")]];
      }));
    
      var lin = ss.linearRegressionLine(regression);
    
      // Create a line based on the beginning and endpoints of the range
      var lindata = x.domain().map(function(x) {
        return {
          Week: x,
          Measure: lin(+x)
        };
      });
    
      svg.append("path")
          .datum(lindata)
          .attr("class", "reg")
            .style("stroke-dasharray", ("3, 3"))
            .attr("stroke", "#319455")
            .attr("stroke-width", 1)
          .attr("d", regLine);
    

    Completed chart: http://jsfiddle.net/euLgczjf/5/