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>&copy; 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>&copy; 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:

  1. Open this file in Emacs.
  2. Use `M-x org-babel-tangle` or `C-c C-v t` to tangle all code blocks.
  3. 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)

Author: Jason Walsh

jwalsh@nexus

Last Updated: 2025-07-30 13:45:27

build: 2025-12-23 09:12 | sha: e32f33e