- click and drag to zoom in on an area
- click the
reset zoom
button to get back to the start
- press the
i
key to view info about the zoomed area
- I used this article as a starting point. Source code available here
import { fmandelbrot } from "./components/mandelbrot.js";
const MAX_ITER = 512;
const W = 800;
const H = 800;
const state = {
xscale: d3.scaleLinear([0, W], [-2, 0.6]),
yscale: d3.scaleLinear([0, H], [1.25, -1.25]),
coords: [0, 0],
infoEnabled: false,
};
const colors = new Map();
const colorscale = d3
.scaleSequentialLog(d3.interpolateInferno)
.domain([1, MAX_ITER]);
for (let i = 1; i < MAX_ITER; i++) {
colors.set(i, colorscale(i));
}
colors.set(MAX_ITER, `rgb(0, 0, 0)`);
const ctx = document.querySelector("canvas#mcanv").getContext("2d");
const drawRow = (ctx, xscale, yscale, y = 0, rows = 20) => {
for (let i = 0; i < rows; i++) {
for (let x = 0; x <= W; x++) {
const l = fmandelbrot([xscale(x), yscale(y + i)], MAX_ITER);
ctx.fillStyle = colors.get(l);
ctx.fillRect(x, y + i, 1, 1);
}
}
if (y < H) {
requestAnimationFrame(() => drawRow(ctx, xscale, yscale, y + rows, rows));
}
};
requestAnimationFrame(() => drawRow(ctx, state.xscale, state.yscale));
const ocanvas = document.querySelector("canvas#overlay");
const overlay = ocanvas.getContext("2d");
const bbox = ocanvas.getBoundingClientRect();
overlay.font = "24px Arial";
let mousedown = false;
const drawDebug = () => {
overlay.fillStyle = "white";
overlay.strokeStyle = "black";
overlay.lineWidth = 2;
overlay.strokeText(`x scale: ${state.xscale.range()}`, 10, 30);
overlay.fillText(`x scale: ${state.xscale.range()}`, 10, 30);
overlay.strokeText(`y scale: ${state.yscale.range()}`, 10, 60);
overlay.fillText(`y scale: ${state.yscale.range()}`, 10, 60);
};
const clearOverlay = () => {
overlay.clearRect(0, 0, W, H);
};
document.querySelector("#overlay").addEventListener("mousedown", (e) => {
const mx = e.clientX - bbox.left;
const my = e.clientY - bbox.top;
state.coords = [mx, my];
mousedown = true;
});
document.querySelector("#overlay").addEventListener("mousemove", (e) => {
if (mousedown) {
const mx = e.clientX - bbox.left;
const my = e.clientY - bbox.top;
clearOverlay();
overlay.strokeStyle = "white";
overlay.strokeRect(
state.coords[0],
state.coords[1],
mx - state.coords[0],
my - state.coords[1],
);
console.log(state.infoEnabled);
if (state.infoEnabled) drawDebug();
}
});
document.querySelector("#overlay").addEventListener("mouseup", (e) => {
if (!mousedown) {
return;
}
mousedown = false;
clearOverlay();
const mx = e.clientX - bbox.left;
const my = e.clientY - bbox.top;
const minx = Math.min(state.coords[0], mx);
const maxx = Math.max(state.coords[0], mx);
const miny = Math.min(state.coords[1], my);
const maxy = Math.max(state.coords[1], my);
const maxDiff = Math.max(maxx - minx, maxy - miny);
state.xscale = d3.scaleLinear(
[0, W],
[state.xscale(minx), state.xscale(minx + maxDiff)],
);
state.yscale = d3.scaleLinear(
[0, H],
[state.yscale(miny), state.yscale(miny + maxDiff)],
);
if (state.infoEnabled) drawDebug();
requestAnimationFrame(() => drawRow(ctx, state.xscale, state.yscale));
});
document.addEventListener("keydown", (event) => {
if (event.key === "i") {
if (!state.infoEnabled) {
state.infoEnabled = true;
drawDebug();
} else {
state.infoEnabled = false;
clearOverlay();
}
}
});