chart = {
const width = 400;
const height = 400;
const svg = d3.create("svg")
.attr('width', width)
.attr('height', height);
const color = [
d3.schemeSet1[0],
d3.schemeSet1[1],
d3.schemeSet1[2],
d3.schemeSet1[3],
d3.schemeSet1[4],
d3.schemeSet1[6]
];
const scaleX = d3.scaleLinear().domain([-15,15]).range([0, width]);
const scaleY = d3.scaleLinear().domain([-15,15]).range([height, 0]);
const ds = student_grades.distributions.map(d => new uapca.MultivariateNormal(d.mean, d.cov));
const projected = uapca.UaPCA.fit(ds, k).aligned().transform(ds, 2);
projected.forEach((d, idx) => {
const [[a11, a12], [a21, a22]] = uapca.transformationMatrix(d).to2DArray();
console.log(a11, a21);
const [tx, ty] = d.mean().to1DArray();
const matStr = `matrix(${a11}, ${a12}, ${a21}, ${-a22}, ${scaleX(tx)}, ${scaleY(ty)})`;
svg.append('ellipse')
.attr('transform', matStr)
.attr('rx', 20)
.attr('ry', 20)
.attr('stroke', color[idx])
.attr('fill', 'none')
.attr('vector-effect', 'non-scaling-stroke');
svg.append('ellipse')
.attr('transform', matStr)
.attr('rx', 10)
.attr('ry', 10)
.attr('stroke', color[idx])
.attr('fill', 'none')
.attr('vector-effect', 'non-scaling-stroke');
svg.append('circle')
.attr('cx', scaleX(d.mean().get(0,0)))
.attr('cy', scaleY(d.mean().get(0,1)))
.attr('r', 3)
.attr('fill', color[idx]);
svg.append('text')
.attr('x', scaleX(d.mean().get(0,0))+7)
.attr('y', scaleY(d.mean().get(0,1)))
.attr('stroke', 'white')
.attr('stroke-width', 3)
.text(student_grades.names[idx]);
svg.append('text')
.attr('x', scaleX(d.mean().get(0,0))+7)
.attr('y', scaleY(d.mean().get(0,1)))
.text(student_grades.names[idx]);
});
return svg.node();
}