Mandelbrot explorer · deepseek-r1-0528
Render the Mandelbrot set with a smooth color gradient on a full-screen canvas, with click-and-drag to pan and mouse-wheel to zoom.
transcript · 4 messages
Render the Mandelbrot set with a smooth color gradient on a full-screen canvas, with click-and-drag to pan and mouse-wheel to zoom.
thinking
We are creating a single-page web app that renders the Mandelbrot set with pan and zoom.
The entire code will be in one file: index.html.
Features:
- Full-screen canvas for Mandelbrot set.
- Smooth color gradient for the set.
- Click and drag to pan.
- Mouse wheel to zoom.
Implementation Plan:
- Use HTML5 Canvas for rendering.
- The Mandelbrot set is computed by iterating a complex number function until escape.
- We'll use an escape time algorithm and map the iteration count to a color.
- For smooth coloring, we'll use the normalized iteration count method.
- We need variables to store the current view (center and scale) and to handle panning and zooming.
- Steps:
1. Initialize the canvas to full screen.
2. Define the initial view of the Mandelbrot set (e.g., center at (-0.5, 0) and scale such that the entire set is visible).
3. Implement rendering function that draws the set for the current view.
4. Add event listeners for mouse down, move, and up to handle panning.
5. Add event listener for mouse wheel to handle zooming.
- The rendering function:
For each pixel in the canvas:
Convert pixel coordinates to a complex number in the Mandelbrot set coordinate system.
Compute the escape time (number of iterations until the complex number escapes a certain radius).
Map the iteration count to a color (with smoothing).
- To achieve smooth coloring, we can use:
float iteration = n + 1 - log(log(zn)) / log(2);
where n is the current iteration, zn is |z| at the nth iteration.
- The color gradient can be created by mapping the normalized iteration count to a color in a gradient palette.
- We'll create a color palette function that returns an RGB color for a normalized value (0 to 1).
- Performance: Since the Mandelbrot set computation is expensive, we might want to consider:
- Using Web Workers for parallel processing? But the requirement is one file and no external resources.
- So we'll do a simple iterative per-pixel rendering. It might be slow for high resolutions, so we may have to limit the number of iterations and use progressive rendering if necessary.
- However, note that we are required to have a single file with no external resources. We will not use Web Workers to keep it simple.
- We can use requestAnimationFrame for rendering and update only the necessary parts? But we are doing a full render.
- We'll set a maximum number of iterations (e.g., 1000) and hope that for initial view it's acceptable.
- When panning and zooming, we should re-render the whole set. We might want to use an offscreen buffer? But we are in a single file and not using advanced techniques.
- We'll do the simplest thing: on every view change, re-render the entire set.
- Steps for the rendering function:
function render() {
const width = canvas.width;
const height = canvas.height;
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Convert pixel (x,y) to complex coordinate (c_re, c_im)
const c_re = (x - width/2) * (4 / width) * scale + centerX;
const c_im = (y - height/2) * (4 / height) * scale + centerY;
// Calculate the escape time and smooth iteration count
let n = 0;
let z_re = 0;
let z_im = 0;
let z_re_sq = 0;
let z_im_sq = 0;
const max_iterations = 1000;
let absZ = 0;
while (n < max_iterations && z_re_sq + z_im_sq <= 4) {
z_im = 2 * z_re * z_im + c_im;
z_re = z_re_sq - z_im_sq + c_re;
z_re_sq = z_re * z_re;
z_im_sq = z_im * z_im;
n++;
}
// Smooth coloring
let color = 0;
if (n === max_iterations) {
color = 0; // inside the set = black
} else {
absZ = Math.sqrt(z_re_sq + z_im_sq);
const nu = Math.log(Math.log(absZ) / Math.LN2) / Math.LN2;
// normalized iteration count = n + 1 - nu
color = (n + 1 - nu) / max_iterations;
}
// Map color to a gradient
const rgb = colormap(color);
const idx = 4 * (y * width + x);
data[idx] = rgb[0]; // R
data[idx+1] = rgb[1]; // G
data[idx+2] = rgb[2]; // B
data[idx+3] = 255; // A
}
}
ctx.putImageData(imageData, 0, 0);
}
- The colormap function: let's use a gradient from dark blue to green to yellow to white.
- We'll define a function that maps a value (0 to 1) to an RGB color.
function colormap(t) {
// t: 0 to 1
// We'll create a gradient from dark blue (0,0,80) to blue (0,0,255) to cyan (0,255,255) to yellow (255,255,0) to red (255,0,0) to white (255,255,255)
// We can use multiple segments.
let r, g, b;
if (t < 0.2) {
// 0 to 0.2: dark blue to blue
const s = t / 0.2;
r = 0;
g = 0;
b = 80 + s * (255 - 80);
} else if (t < 0.4) {
// 0.2 to 0.4: blue to cyan
const s = (t - 0.2) / 0.2;
r = 0;
g = s * 255;
b = 255;
} else if (t < 0.6) {
// 0.4 to 0.6: cyan to yellow
const s = (t - 0.4) / 0.2;
r = s * 255;
g = 255;
b = 255 - s * 255;
} else if (t < 0.8) {
// 0.6 to 0.8: yellow to red
const s = (t - 0.6) / 0.2;
r = 255;
g = 255 - s * 255;
b = 0;
} else {
// 0.8 to 1.0: red to white
const s = (t - 0.8) / 0.2;
r = 255;
g = s * 255;
b = g;
}
return [Math.floor(r), Math.floor(g), Math.floor(b)];
}
- For panning and zooming:
We store:
let centerX = -0.5;
let centerY = 0;
let scale = 1; // this will change when zooming
When zooming with the mouse wheel, we adjust the scale and also adjust the center to zoom towards the mouse.
For panning:
Let startX, startY on mousedown (in canvas coordinates), and then on mousemove we calculate the offset and update centerX and centerY.
Steps for panning:
let isDragging = false;
let lastX, lastY;
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
// Move the center by (dx, dy) in canvas coordinates, scaled appropriately
centerX -= dx * (4 / canvas.width) * scale;
centerY -= dy * (4 / canvas.height) * scale;
render();
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
Steps for zooming:
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomSpeed = 0.1;
const zoomFactor = e.deltaY < 0 ? (1 + zoomSpeed) : (1 - zoomSpeed);
// Get mouse position relative to canvas
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Convert mouse position to Mandelbrot coordinates
const old_x = (mouseX - canvas.width/2) * (4 / canvas.width) * scale + centerX;
const old_y = (mouseY - canvas.height/2) * (4 / canvas.height) * scale + centerY;
// Apply zoom
scale *= zoomFactor;
// Adjust center so that the point under the mouse stays fixed
const new_x = (mouseX - canvas.width/2) * (4 / canvas.width) * scale + centerX;
const new_y = (mouseY - canvas.height/2) * (4 / canvas.height) * scale + centerY;
centerX += old_x - new_x;
centerY += old_y - new_y;
render();
});
- The rendering process is computationally heavy and might block the UI. We might consider using requestAnimationFrame and breaking the rendering into chunks? But for simplicity, we'll do a single render call and hope the browser doesn't hang for moderate resolutions and iterations.
- We can set max_iterations to 1000 but it might still be slow on large canvases.
- We must reset the canvas size to full window on resize.
window.addEventListener('resize', () => {
adjustCanvasSize();
render();
});
function adjustCanvasSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
- We also need to adjust the canvas size initially.
We write the entire code in one file: index.html.
Let's structure it:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Mandelbrot Set</title>
<style>
body, html { margin:0; padding:0; overflow:hidden; }
canvas { display:block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// ... JavaScript code here ...
</script>
</body>
</html>
Now, we write the JavaScript code.
Important: We must set the canvas size initially and handle resizing.
We break the code into:
- Variables for center, scale, and dragging state.
- References to the canvas and context.
- The colormap function.
- The render function.
- Event listeners.
We'll also include a function for setting the canvas size.
Let's write the entire HTML file in the `write` tool call.
I'll help you create a self-contained Mandelbrot set viewer with smooth coloring and navigation controls. Here's the implementation:
index.html (6561 bytes)Successfully wrote 6561 bytes to index.html
thinking
We are creating a single-page web app that renders the Mandelbrot set.
The index.html file will contain:
- HTML structure
- CSS for styling (full-screen canvas, info overlay)
- JavaScript for:
1. Setting up the canvas and context
2. Calculating the Mandelbrot set with smooth coloring
3. Handling mouse events: pan (click and drag) and zoom (mouse wheel)
4. Rendering the set efficiently
We'll use:
- A canvas that fills the entire window.
- Complex number arithmetic for the Mandelbrot iteration.
- Smooth coloring using the normalized iteration count.
- Panning by adjusting the center coordinates when dragging.
- Zooming by adjusting the scale with the mouse wheel, and keeping the point under the mouse fixed.
Steps in the JavaScript:
1. Initialize canvas and context.
2. Set up event listeners for mousedown, mousemove, mouseup, and wheel (for zoom).
3. Implement the Mandelbrot function that returns the iteration count for a given complex number.
4. Implement a smooth coloring function that maps the iteration count to a color.
5. Render the Mandelbrot set by iterating over each pixel, converting to complex coordinates, and coloring.
6. Handle panning: on mousedown, record the start position; during drag, adjust center by the change in pixel coordinates.
7. Handle zooming: on wheel, adjust the scale and adjust the center so that the point under the mouse remains fixed.
Important: The Mandelbrot set is defined in the complex plane. We map:
Canvas x: [0, width] -> [centerX - scale/2, centerX + scale/2]
Canvas y: [0, height] -> [centerY - scale/2 * aspect, centerY + scale/2 * aspect]
(aspect is height/width, but note that the scale is in the x-direction; y is scaled by the aspect ratio to avoid distortion)
For smooth coloring, we use the normalized iteration count algorithm.
We write the entire content into index.html.
Note: We must not use external resources; everything must be in one file.
Let me write the code accordingly.
I've created a self-contained Mandelbrot set explorer that meets all your requirements. You can now run the index.html file to interact with it. Key features: 1. Smooth color gradient using a logarithmic smoothing algorithm 2. Click-and-drag to pan around the fractal 3. Mouse wheel to zoom in/out (with the mouse position as the zoom center) 4. Full-screen canvas that adjusts to window size 5. Performance optimized pixel-by-pixel rendering The interface shows zoom level and position information in the bottom-left corner. The color scheme transitions smoothly from dark blues to golds to bright whites based on iteration counts. Try clicking and dragging to explore different areas, and use your mouse wheel to zoom into and out of the intricate patterns of the Mandelbrot set. The file is completely self-contained with no external dependencies - just open index.html in your browser!
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Smooth-colored Mandelbrot renders full-screen; drag pans and wheel zooms correctly, keyboard inputs have no effect as expected.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.