Mandelbrot Explorer
Table of Contents
1. Setup
To ensure proper tangling, make sure you have org-babel loaded. You can do this by adding the following to your Emacs init file:
(require 'org)
(require 'ob-tangle)
If you encounter issues with automatic tangling, you can manually tangle each block using `C-c C-v t` while the cursor is inside the block.
2. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mandelbrot Explorer</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="stylesheet" href="mandelbrot.css">
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="mandelbrot.js"></script>
<footer>
<p>© 2024 Mandelbrot Explorer. All rights reserved.</p>
</footer>
</body>
</html>
2.1. HTML Preamble
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mandelbrot Explorer</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="stylesheet" href="mandelbrot.css">
</head>
<body>
2.2. HTML Postamble
<script type="text/babel" src="mandelbrot.js"></script>
<footer>
<p>© 2024 Mandelbrot Explorer. All rights reserved.</p>
</footer>
</body>
</html>
3. JavaScript (React)
const { useState, useEffect, useRef } = React;
const Slider = ({ min, max, step, value, onValueChange }) => (
<input
type="range"
min={min}
max={max}
step={step}
value={value[0]}
onChange={(e) => onValueChange([parseInt(e.target.value)])}
/>
);
const Button = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const Card = ({ children }) => <div className="card">{children}</div>;
const CardHeader = ({ children }) => <div className="card-header">{children}</div>;
const CardContent = ({ children }) => <div className="card-content">{children}</div>;
const MandelbrotExplorer = () => {
const [maxIterations, setMaxIterations] = useState(100);
const [zoom, setZoom] = useState(200);
const [centerX, setCenterX] = useState(-0.5);
const [centerY, setCenterY] = useState(0);
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width;
const height = canvas.height;
const drawMandelbrot = () => {
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
let a = (x - width / 2) / zoom + centerX;
let b = (y - height / 2) / zoom + centerY;
let ca = a;
let cb = b;
let n = 0;
while (n < maxIterations) {
let aa = a * a - b * b;
let bb = 2 * a * b;
a = aa + ca;
b = bb + cb;
if (a * a + b * b > 4) break;
n++;
}
let brightness = n < maxIterations ? Math.sqrt(n / maxIterations) : 0;
let rgb = hslToRgb(0.5 + brightness * 0.5, 0.7, brightness);
ctx.fillStyle = `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
ctx.fillRect(x, y, 1, 1);
}
}
};
drawMandelbrot();
}, [maxIterations, zoom, centerX, centerY]);
const hslToRgb = (h, s, l) => {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Mandelbrot Fractal Explorer</h1>
<div className="flex flex-col md:flex-row gap-4">
<Card>
<CardHeader>Mandelbrot Set</CardHeader>
<CardContent>
<canvas ref={canvasRef} width={400} height={400} className="border border-gray-300" />
</CardContent>
</Card>
<Card>
<CardHeader>Controls</CardHeader>
<CardContent>
<div className="mb-4">
<label className="block mb-2">Max Iterations: {maxIterations}</label>
<Slider
min={10}
max={1000}
step={10}
value={[maxIterations]}
onValueChange={(value) => setMaxIterations(value[0])}
/>
</div>
<div className="mb-4">
<label className="block mb-2">Zoom: {zoom}</label>
<Slider
min={100}
max={1000}
step={10}
value={[zoom]}
onValueChange={(value) => setZoom(value[0])}
/>
</div>
<div className="mb-4">
<Button onClick={() => {setCenterX(-0.5); setCenterY(0); setZoom(200);}}>
Reset View
</Button>
</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>About the Mandelbrot Set</CardHeader>
<CardContent>
<p>The Mandelbrot set is a famous example of a fractal in mathematics. It's defined as the set of complex numbers c for which the function f(z) = z^2 + c does not diverge when iterated from z = 0.</p>
<p>In the visualization above:</p>
<ul className="list-disc pl-5 mt-2">
<li>Each pixel represents a complex number in the complex plane.</li>
<li>The color of each pixel is determined by how quickly the sequence diverges for that point.</li>
<li>Black points are in the Mandelbrot set (the sequence doesn't diverge).</li>
<li>Colored points are outside the set, with the color indicating how quickly the sequence diverges.</li>
</ul>
<p className="mt-2">Use the controls to explore different aspects of the fractal:</p>
<ul className="list-disc pl-5 mt-2">
<li>Increase "Max Iterations" for more detailed boundaries.</li>
<li>Adjust "Zoom" to focus on specific areas of the fractal.</li>
<li>Click "Reset View" to return to the default view.</li>
</ul>
</CardContent>
</Card>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MandelbrotExplorer />);
4. CSS
.p-4 { padding: 1rem; }
.text-2xl { font-size: 1.5rem; }
.font-bold { font-weight: bold; }
.mb-4 { margin-bottom: 1rem; }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.gap-4 { gap: 1rem; }
.flex-1 { flex: 1 1 0%; }
.border { border-width: 1px; }
.border-gray-300 { border-color: #d1d5db; }
.block { display: block; }
.mb-2 { margin-bottom: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.list-disc { list-style-type: disc; }
.pl-5 { padding-left: 1.25rem; }
.mt-2 { margin-top: 0.5rem; }
@media (min-width: 768px) {
.md\:flex-row { flex-direction: row; }
}
footer {
margin-top: 2rem;
text-align: center;
font-size: 0.875rem;
color: #666;
}
5. Tangling Instructions
If automatic tangling doesn't work, you can manually tangle the file:
- Open this file in Emacs.
- Use `M-x org-babel-tangle` or `C-c C-v t` to tangle all code blocks.
- Alternatively, place your cursor inside a specific code block and use `C-c C-v t` to tangle just that block.
After tangling, you should find three new files in the same directory as this org file:
- mandelbrot.html
- mandelbrot.js
- mandelbrot.css
These files together constitute the Mandelbrot Explorer web application.
6. Serving and Viewing the Mandelbrot Explorer
To serve the Mandelbrot Explorer using Emacs' built-in web server and view it in a browser, follow these steps:
6.1. Setup simple-httpd
If you haven't already, add the following to your Emacs init file (usually ~/.emacs or ~/.emacs.d/init.el):
(require 'simple-httpd)
(setq httpd-port 8088)
(setq httpd-root "~/sandbox/wal.sh/research/")
Restart Emacs or evaluate these lines to apply the changes.
6.2. Start the Server
To start the server, use the following command in Emacs:
(httpd-start)
You should see a message similar to:
Started simple-httpd on 0.0.0.0:8088, serving: /Users/jasonwalsh/sandbox/wal.sh/research/
6.3. View in Browser
Open your web browser and navigate to:
http://localhost:8088/mandelbrot.html
You should now see the Mandelbrot Explorer running in your browser.
6.4. Stopping the Server
When you're done, you can stop the server with:
(httpd-stop)