Search code examples
javascriptd3.jscomputer-visiondata-analysisjstat

Drawing an error ellipse in javascript with d3 and numericjs


I'm attempting to implement this method for drawing covariance error ellipses in javascript.

errorEllipse = function(stdDevX, stdDevY, cor, center, level) {
  var errEllipse,
  cov = cor * stdDevX * stdDevY,
  covmat = [
  [stdDevX * stdDevX, cov],
  [cov, stdDevY * stdDevY]
  ],
  eig = numeric.eig(covmat),
  scale = Math.sqrt(jStat.chisquare.inv(level, 2)),
  maxLambdaI = indexOfMax(eig.lambda.x),
  minLambdaI = indexOfMin(eig.lambda.x),
  rx = stdDevX > stdDevY ? Math.sqrt(eig.lambda.x[maxLambdaI]) * scale : Math.sqrt(eig.lambda.x[minLambdaI]) * scale,
  ry = stdDevY > stdDevX ? Math.sqrt(eig.lambda.x[maxLambdaI]) * scale : Math.sqrt(eig.lambda.x[minLambdaI]) * scale,
  v1 = eig.E.x[maxLambdaI],
  theta = Math.atan2(v1[1], v1[0]) * 180 / Math.PI;

  if (theta < 0) {
    theta += 360;
  }
  //make the ellipse object
  errEllipse = {
    rx: rx,
    ry: ry,
    cx: center.x,
    cy: center.y,
    orient: -theta
  };

  return errEllipse;
};

The most obvious issue I see with the results of my current attempt is the ellipse is not fitting the data. It's been difficult to find exactly where I'm going wrong.

Can anyone show me: 1.) What's wrong with how I'm getting or plotting the radiuses and angle of the ellipse. or 2.) Any example of drawing error / confidence ellipses in javascript.

I’m using the libraries numericjs for the eigenvectors and values, jstat, and d3 for plotting.

Here is a plnkr with the current code.

The errorEllipse function is in the “script.js” file.Test data can be changed by editing testData.js.

Update:

I'm adding the code for plotting the ellipse with d3 in case that is where the issue is:

svg.append('ellipse')
  .attr('class', 'q-ellipse-99')
  .attr('rx', Math.abs(xScale(xExtent[0] + ellipse99.rx) - xScale(xExtent[0])))
  .attr('ry', Math.abs(yScale(yExtent[0] + ellipse99.ry) - yScale(yExtent[0])))
  .attr('transform', 'translate(' + xScale(ellipse99.cx) + ',' + yScale(ellipse99.cy) + ')rotate(' + ellipse99.orient + ')');

Both the xExtent and yExtent arrays are returned from calling d3.extent() on the data.


Solution

  • The original issue was that I didn't scale the ellipse's rx and ry values when plotting. Seems to work OK now with code above (under update). Here is a fork of plnkr that uses random normal test data (below) instead.

    function genTestData(numPoints){
      var data = {x:[], y:[]};
      for(var i = 0; i < numPoints; i++){
        data.x[i] = jStat.normal.inv(Math.random(), 8.5, 2);
        data.y[i] = jStat.normal.inv(Math.random(), 6.5, 3);
        data.y[i] += data.x[i] * 2.1; //to test ellipse
      }
      return data;
    }