Mandelbrot Explorer
Table of Contents
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.
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>
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>
HTML Postamble
<script type="text/babel" src="mandelbrot.js"></script> <footer> <p>© 2024 Mandelbrot Explorer. All rights reserved.</p> </footer> </body> </html>
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 />);
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; }
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.
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:
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.
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/
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.
Stopping the Server
When you're done, you can stop the server with:
(httpd-stop)