Chaz Arvizu - PortfolioChaz Arvizu - Portfolio
Home
Resume
  • LiftJourn (Mobile Application)
  • MealApp (Name Pending)
  • Gym Rat (Senior Design Class)
  • Lorenz Attractor
  • Solar System Simulation
  • Stranger Things Intro
Home
Resume
  • LiftJourn (Mobile Application)
  • MealApp (Name Pending)
  • Gym Rat (Senior Design Class)
  • Lorenz Attractor
  • Solar System Simulation
  • Stranger Things Intro
  • Lorenz Attractor

Lorenz Attractor

"The flapping of the wings of a butterfly can be felt on the other side of the world."

Introduction

I was inspired to create this animation by a YouTuber named Veritasium, as seen in his video about chaos theory. The video sparked my curiosity about how small changes in the inputs of an equation can drastically alter the system's output. This behavior is evident in our daily lives, influencing our decisions, whether big or small. Chaotic equations offer a unique way of mapping such behavior.

Technical Details

To create this animation in JavaScript, I followed the steps in the video found here. I made some adjustments to the code to integrate it with VuePress' template syntax, and for correct styling, size ratios.

Challenges

Here are some specific challenges I faced and how I resolved them:

  • Compatibility: To get the Vue.js component to work with VuePress, some extra configurations were required in order to register it properly. In the config.js file I had to add the following code:
plugins: [
    registerComponentsPlugin({
      LorenzAttractor: path.resolve(__dirname, './components/LorenzAttractor.vue'),
    })
],
  • Styling Adjustments: Ensuring the animation looks good within the constraints of VuePress required tweaking the CSS and layout.

Source Code

Again, the code below was adapted from the video found here.

<template>
    <canvas ref="canvas"></canvas>
</template>

<script>
export default {
    name: 'LorenzAttractor',
    mounted() {
        this.DrawLorenz()
    },
    methods: {
        DrawLorenz() {
            const canvas = this.$refs.canvas;
            const ctx = canvas.getContext('2d');

            const dt = 0.01;
            const sigma = 10;
            const rho = 28;
            const beta = 8 / 3;

            function integrate({ x, y, z }) {
                x += (sigma * (y - x)) * dt;
                y += (x * (rho - z) - y) * dt;
                z += (x * y - beta * z) * dt;
                return { x, y, z };
            }

            function extendPath(path, steps) {
                [...Array(steps)].forEach(() => {
                    const lastP = path[path.length - 1];
                    const p = integrate(lastP);
                    path.push(p);
                });
                return path;
            }

            function scale(points, size, scaleFactor) {
                const mx = Math.min(...points.map(({ x, y, z }) => x));
                const Mx = Math.max(...points.map(({ x, y, z }) => x));
                const my = Math.min(...points.map(({ x, y, z }) => y));
                const My = Math.max(...points.map(({ x, y, z }) => y));
                const mz = Math.min(...points.map(({ x, y, z }) => z));
                const Mz = Math.max(...points.map(({ x, y, z }) => z));

                const s = (v, mv, Mv) => size * scaleFactor * (v - mv) / (Mv - mv);
                return points.map(({ x, y, z }) => {
                    x = s(x, mx, Mx);
                    y = s(y, my, My);
                    z = s(z, mz, Mz);
                    return { x, y, z };
                });
            }

            var q = 0;
            function draw(path) {
                q += 0.01; // Smaller increment value
                const rotationCenterX = 150; // X-coordinate of the new rotation center
                const rotationCenterY = canvas.height / 2; // Y-coordinate of the new rotation center

                const map = ({ x, y, z }) => {
                    // Apply rotation around the new center point
                    const xx = (x - rotationCenterX) * Math.cos(q) - (y - rotationCenterY) * Math.sin(q) + rotationCenterX;
                    const yy = z;

                    // Clip to canvas bounds
                    const xClipped = Math.max(0, Math.min(canvas.width, xx));
                    const yClipped = Math.max(0, Math.min(canvas.height, yy));

                    return [xClipped, yClipped];
                };

                // Set the strokeStyle to blue
                ctx.strokeStyle = 'blue';

                ctx.lineWidth = 1;

                ctx.beginPath();
                path.map(map).forEach(p => ctx.lineTo(p[0], p[1]));
                ctx.stroke();
            }

            const path = [{ x: 1, y: 1, z: 1 }];
            function step() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                extendPath(path, 10);
                const scaled = scale(path, 600, 0.2);
                draw(scaled);
                while (path.length > 1000) path.shift();
                setTimeout(step, 1000 / 60);
            }
            step();

        }
    }
}
</script>

<style scoped>
canvas {
    border: 1px solid #ccc;
    width: 100%;
}
</style>
Last Updated:
Contributors: ChazArvizu