function plotZernike(maxRadialDegree, {
plotSize=0.5*width, cellSize=1, numContours=21,
}={}) {
const ncell = Math.round(plotSize / cellSize);
const xygrid = d3.range(ncell).map((_,i) => (i - (ncell - 1) / 2) / ((ncell - 1) / 2));
const [y, x] = d3.transpose(d3.cross(xygrid, xygrid));
const calculated = calculateZernike(maxRadialDegree, x, y);
const X = d3.scaleLinear().domain([0, ncell-1]).range([0, plotSize-1]);
const Y = d3.scaleLinear().domain([0, ncell-1]).range([plotSize-1, 0]);
const transform = ({type, value, coordinates}) => {
return {type, value, coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([ix, iy]) => [ X(ix), Y(iy) ] );
});
})};
};
const svg = d3.create("svg")
.attr("viewBox", [0, 0, plotSize, plotSize])
.style("width", plotSize)
.style("height", plotSize);
svg.append("circle")
.attr("id", "circle")
.attr("cx", plotSize / 2)
.attr("cy", plotSize / 2)
.attr("r", plotSize / 2 - 2);
svg.append("clipPath")
.attr("id", "circleClip")
.append("use")
.attr("href", "#circle");
svg.append("g")
.attr("id", "contours")
.attr("fill", "none")
.attr("stroke", "#777")
.attr("stroke-opacity", 0.5)
.attr("clip-path", "url(#circleClip)");
svg.append("use")
.attr("href", "#circle")
.attr("fill", "none")
.attr("stroke-width", 2)
.attr("stroke", "#000");
function update(nollIndices, coefs, {plotQuantity="Z"}={}) {
if(nollIndices.length != coefs.length) throw new Error(
`Coefficients array does not have expected length ${nollIndices.length}.`);
const grid = Float64Array.from({length:x.length}, (_,k) => {
let sum = 0.0;
for(let i=0; i < coefs.length; ++i) {
const {n,l} = nollDecode(nollIndices[i]);
if(coefs[i] != 0) sum += coefs[i] * calculated[plotQuantity](n, l, k);
}
return sum;
});
const [lo, hi] = d3.extent(grid);
const thresholds = d3.range(numContours).map(i => lo + (hi - lo) / (numContours - 1) * i);
const contours = d3.contours()
.size([ncell, ncell]).thresholds(thresholds)(grid)
.map(transform);
const color = d3.scaleSequential([hi, lo], d3.interpolateRdBu);
svg.select("#contours")
.selectAll("path")
.data(contours)
.join("path")
.attr("fill", d => color(d.value))
.attr("d", d3.geoPath());
}
update([], []);
return Object.assign(svg.node(), { update });
}