function contour_plot(f, [[a, b], [c, d]], thresholds, opts = {}) {
let {
xsteps = 200,
ysteps = 200,
xticks = 4,
yticks = 4,
w = width < 600 ? width : 600,
h = w,
pad = 30
} = opts;
let dx = (b - a) / xsteps;
let dy = (d - c) / ysteps;
let grid = d3.range((xsteps + 1) * (ysteps + 1)).map(function (k) {
let i = k % (xsteps + 1);
let x = a + i * dx;
let j = Math.floor(k / (xsteps + 1));
let y = d - j * dy;
return [x, y];
});
grid.m = xsteps;
grid.n = ysteps;
grid.a = a;
grid.b = b;
grid.c = c;
grid.d = d;
let x_scale = d3
.scaleLinear()
.domain([grid.a, grid.b])
.range([pad, w - pad]);
let y_scale = d3
.scaleLinear()
.domain([grid.c, grid.d])
.range([h - pad, pad]);
let extent = d3.extent(thresholds);
let zmin = extent[0];
let zmax = extent[1];
let color = d3
.scaleLinear()
.domain([zmax, zmin])
.interpolate((d) => (t) => d3.interpolateBlues(1 - t));
let contours = d3
.contours()
.size([grid.m + 1, grid.n + 1])
.thresholds(thresholds)(grid.map(f))
.map(transform);
const svg = d3
.create("svg")
.attr("class", "contour_plot")
.attr("width", w)
.attr("height", h);
svg
.append("g")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.8)
.selectAll("path")
.data(contours)
.join("path")
.attr("fill", (d) => color(d.value))
.attr("d", d3.geoPath())
.append("title")
.text((d) =>
Math.abs(d.value) < 10 ** -5 ? 0 : d3.format("0.4")(d.value)
);
svg
.append("g")
.attr("transform", `translate(0, ${h - pad})`)
.call(d3.axisBottom(x_scale).ticks(xticks));
svg
.append("g")
.attr("transform", `translate(${pad})`)
.call(d3.axisLeft(y_scale).ticks(yticks));
return svg.node();
function transform({ type, value, coordinates }) {
let i_scale = d3
.scaleLinear()
.domain([0, grid.m + 1])
.range([pad, w - pad]);
let j_scale = d3
.scaleLinear()
.domain([0, grid.n + 1])
.range([pad, h - pad]);
return {
type,
value,
coordinates: coordinates.map((rings) => {
return rings.map((points) => {
return points.map(([i, j]) => [i_scale(i), j_scale(j)]);
});
})
};
}
}