Public
Edited
Dec 20, 2022
Importers
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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);

// Initialize transforms from data index space [0,ncell) x [0,ncell) to pixel space.
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) ] );
});
})};
};

// Initialize the SVG output.
const svg = d3.create("svg")
.attr("viewBox", [0, 0, plotSize, plotSize])
.style("width", plotSize)
.style("height", plotSize);

// Construct the unit disk in pixel space (to re-use below).
svg.append("circle")
.attr("id", "circle")
.attr("cx", plotSize / 2)
.attr("cy", plotSize / 2)
.attr("r", plotSize / 2 - 2);

// Create a clipping path from the unit disk.
svg.append("clipPath")
.attr("id", "circleClip")
.append("use")
.attr("href", "#circle");

// Initialize the contours drawing.
svg.append("g")
.attr("id", "contours")
.attr("fill", "none")
.attr("stroke", "#777")
.attr("stroke-opacity", 0.5)
.attr("clip-path", "url(#circleClip)");
// Draw the disk outline.
svg.append("use")
.attr("href", "#circle")
.attr("fill", "none")
.attr("stroke-width", 2)
.attr("stroke", "#000");

// Define a callback to compute the contours for specified Zernike coefficients.
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;
});
// See https://observablehq.com/@d3/contours
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);
// Draw the computed contours for this grid.
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 });
}
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more