Published
Edited
Sep 10, 2022
7 stars
Also listed in…
Math
Teaching Calculus
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Implementation of the peaks function
function f([x, y]) {
return (
3 * Math.exp(-(x ** 2 + (y + 1) ** 2)) * (1 - x) ** 2 -
Math.exp(-((x + 1) ** 2 + y ** 2)) / 3 -
10 * Math.exp(-(x ** 2 + y ** 2)) * (x / 5 - x ** 3 - y ** 5)
);
}
Insert cell
// The gradient of the peaks function
function grad([x, y]) {
return [
-6 * Math.exp(-x * x - (1 + y) ** 2) * (1 - x) -
6 * Math.exp(-x * x - (1 + y) ** 2) * (1 - x) ** 2 * x +
(2 * Math.exp(-(1 + x) * (1 + x) - y ** 2) * (1 + x)) / 3 -
10 * Math.exp(-x * x - y ** 2) * (1 / 5 - 3 * x ** 2) +
20 * Math.exp(-x * x - y ** 2) * x * (x / 5 - x ** 3 - y ** 5),
(2 * Math.exp(-(1 + x) * (1 + x) - y ** 2) * y) / 3 +
50 * Math.exp(-x * x - y ** 2) * y ** 4 -
6 * Math.exp(-x * x - (1 + y) ** 2) * (1 - x) ** 2 * (1 + y) +
20 * Math.exp(-x * x - y ** 2) * y * (x / 5 - x ** 3 - y ** 5)
];
}
Insert cell
// To compute the gradient ascent from a starting point,
// we'll literally follow the gradient at each point.
function compute_path([x0, y0]) {
const tol = 0.01;
const dt = 0.01;
let path = [[x0, y0]];
let step = grad([x0, y0]);
let cnt = 0;
while (Math.abs(step[0]) + Math.abs(step[1]) > tol && cnt < 10000) {
let [x, y] = path[path.length - 1];
let x_new = x + step[0] * dt;
let y_new = y + step[1] * dt;
path.push([x_new, y_new]);
step = grad([x_new, y_new]);
cnt = cnt + 1;
}
return path;
}
Insert cell
Insert cell
grid = {
let a = -2.5;
let b = 2.5;
const m = 250;
let dx = (b - a) / m;
let c = -2.5;
let d = 2.5;
const n = 250;
let dy = (d - c) / n;
let grid = d3.range((m + 1) * (n + 1)).map(function(k) {
let i = k % (m + 1);
let x = a + i * dx;
let j = Math.floor(k / (m + 1));
let y = d - j * dy;
return [x, y];
});
grid.m = m;
grid.n = n;
grid.a = a;
grid.b = b;
grid.c = c;
grid.d = d;
return grid;
}
Insert cell
// A lot of magic happens here!
contours = d3
.contours()
.size([grid.m + 1, grid.n + 1])
.thresholds(thresholds)(grid.map(f))
.map(transform)
Insert cell
// Transform the contours by mapping from grid coordinates (indices)
// to svg coordinates (px).
function transform({ type, value, coordinates }) {
let i_scale = d3
.scaleLinear()
.domain([0, grid.m + 1])
.range([0, svg_width]);
let j_scale = d3
.scaleLinear()
.domain([0, grid.n + 1])
.range([0, svg_height]);
return {
type,
value,
coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([i, j]) => [i_scale(i), j_scale(j)]);
});
})
};
}
Insert cell
// Contour placement
thresholds = d3.range(-8, 9, 1 / 2)
Insert cell
// Put it all together!
function make_gradient_ascent_pic() {
const svg = d3
.create("svg")
.attr("class", "contour_plot")
.attr("viewBox", [0, 0, svg_width, svg_height])
.style("max-width", `${svg_width}px`);
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(null));

let x_scale = d3.scaleLinear().domain([grid.a, grid.b]).range([0, svg_width]);
let y_scale = d3
.scaleLinear()
.domain([grid.c, grid.d])
.range([svg_height, 0]);
let pts_to_path = d3
.line()
.x((d) => x_scale(d[0]))
.y((d) => y_scale(d[1]));
let ascent = svg.append("g");

svg
.on("pointerenter", function (evt) {
let [w, h] = d3.pointer(evt);
let x = x_scale.invert(w);
let y = y_scale.invert(h);
let path = compute_path([x, y]);
ascent
.selectAll("path")
.data([path])
.enter()
.append("path")
.attr("d", pts_to_path)
.attr("stroke", "black")
.attr("stroke-width", 4)
.attr("fill", "none");
ascent
.append("circle")
.attr("id", "start")
.attr("cx", x_scale(path[0][0]))
.attr("cy", y_scale(path[0][1]))
.attr("r", 5)
.attr("fill", "#5f5");
ascent
.append("circle")
.attr("id", "fin")
.attr("cx", x_scale(path[path.length - 1][0]))
.attr("cy", y_scale(path[path.length - 1][1]))
.attr("r", 5)
.attr("fill", "#f55");
})
.on("touchstart", (e) => e.preventDefault())
.on("pointermove", function (evt) {
let [w, h] = d3.pointer(evt);
let x = x_scale.invert(w);
let y = y_scale.invert(h);
let path = compute_path([x, y]);
ascent.select("path").data([path]).join("path").attr("d", pts_to_path);
ascent
.selectAll("circle#start")
.data([path])
.join("circle")
.attr("cx", (d) => x_scale(d[0][0]))
.attr("cy", (d) => y_scale(d[0][1]));
ascent
.selectAll("circle#fin")
.data([path])
.join("circle")
.attr("cx", (d) => x_scale(d[d.length - 1][0]))
.attr("cy", (d) => y_scale(d[d.length - 1][1]));
})
.on("pointerleave", function () {
ascent.selectAll("*").remove();
});

return svg.node();
}
Insert cell
color = d3
.scaleLinear()
.domain(d3.extent(thresholds))
.interpolate(d => d3.interpolateBlues)
Insert cell
svg_width = width < 800 ? width : 800
Insert cell
// Specifies the height of the SVG in px
svg_height = svg_width
Insert cell
// d3 = require('d3@5')
Insert cell
x3dom = require("x3dom@1.7").catch(() => window["x3dom"])
Insert cell
html`<style>
canvas {
outline: none;
}
</style>`
Insert cell
static_pic = {
let pic = d3.select(make_gradient_ascent_pic());
pic.on("pointerenter", null);
pic.on("pointerleave", null);
pic.on("pointeremove", null);
return pic.node();
}
Insert cell
FileAttachment("peaks.x3d").text()
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