Fibonacci Spiral

There is a fascinating relation between the Fibonacci sequence and natural patterns. A remarkable example is the Fibonacci spiral, which is constructed by adding arcs of quarter circles with radii corresponding to Fibonacci numbers, making a beautiful mathematical pattern often observed throughout nature.

Open in Colab

Setup

Visit colab.new to create a new Colab notebook.

Installation

The session will restart to apply changes.

%pip install -q mscene
import mscene
%mscene -l manim

Import

from mscene.manim import *

Objects

Functions to create objects for animation scenes.

def fseq(n, a=0, b=1):
    """Return the first n Fibonacci numbers starting with a and b."""
    seq = []
    for _ in range(n):
        seq.append(a)
        a, b = b, a + b
    return seq


def fsmob(n, width=None, height=None):
    """VGroup of Mobjects for the Fibonacci spiral.

    Args:
        n (int): Number of first Fibonacci terms.
        width (float | None): Width of the object (default: None).
        height (float | None): Height of the object (default: None).

    Returns:
        VGroup of Square, Text, ArcBetweenPoints and Dot.
    """
    sqr_color = ManimColor("#214761")
    txt_color = ManimColor("#516572")
    arc_color = ManimColor("#04d9ff")
    dot_color = ManimColor("#cfff04")

    seq = fseq(n, 1)

    if width and not height:
        scale = width / sum(seq[-2:])
    elif height and not width:
        scale = height / seq[-1]
    else:
        scale = 1

    mobjects = VGroup()
    squares = VGroup()

    if len(seq) % 2:
        angle = PI / 2
        direction = (RIGHT, UP, LEFT, DOWN)
        dot_index = (0, -1, -1, 0)
    else:
        angle = -PI / 2
        direction = (UP, RIGHT, DOWN, LEFT)
        dot_index = (0, 0, -1, -1)

    corner = (DL, UL)

    for i, t in enumerate(seq):
        square = Square(t * scale, stroke_width=6, color=sqr_color).next_to(
            squares,
            direction[i % 4],
            buff=0,
        )

        dots = VGroup(
            Dot(square.get_corner(corner[i % 2]), color=dot_color),
            Dot(square.get_corner(-corner[i % 2]), color=dot_color),
        )

        arc = ArcBetweenPoints(
            dots[dot_index[i % 4]].get_center(),
            dots[dot_index[i % 4] + 1].get_center(),
            angle=angle,
            color=arc_color,
            stroke_width=6,
        )

        text = (
            Text(f"{t}×{t}", color=txt_color)
            .scale_to_fit_width(square.width * 0.5)
            .move_to(square)
        )

        vgrp = VGroup(square, text, arc, dots)
        vgrp[2:].set_z_index(1)
        squares.add(square)
        mobjects.add(vgrp)

    mobjects.center()

    return mobjects


def fsmob_anim(mob, mode="IN", lag_ratio=0.125, **kwargs):
    """Return an AnimationGroup for the Fibonacci spiral."""

    if mode == "IN":

        anim = [(FadeIn(i[0]), Write(i[1]), Create(i[2]), FadeIn(i[3])) for i in mob]
    elif mode == "OUT":
        anim = [
            (FadeOut(i[0]), Unwrite(i[1]), Uncreate(i[2]), FadeOut(i[3]))
            for i in mob[::-1]
        ]
    else:
        raise ValueError("mode must be 'IN' or 'OUT'")

    return AnimationGroup(*anim, lag_ratio=lag_ratio, **kwargs)

Scenes

Scene One

%%manim -qm SceneOne

class SceneOne(Scene):
    def construct(self):
        seq = fseq(25)
        width = config.frame_width * 3 / 4
        seq_str = ", ".join(map(str, seq)) + ", ..."

        title = Text("Fibonacci Sequence").scale_to_fit_width(width * 3 / 4)
        text = MarkupText(seq_str, width=width, justify=True, font_size=130)
        VGroup(title, text).arrange(DOWN, buff=3 / 4)

        self.play(Write(title), Write(text, run_time=3))
        self.wait(3)

Scene Two

%%manim -qm SceneTwo

class SceneTwo(Scene):
    def construct(self):
        width = config.frame_width * 3 / 4
        mob = fsmob(6, width=width)

        self.play(fsmob_anim(mob))
        self.wait(2.5)

        self.play(fsmob_anim(mob, mode="OUT"))
        self.wait(0.5)

Scene Three

%%manim -qm SceneThree

class SceneThree(Scene):
    def construct(self):
        width = config.frame_width * 3 / 4
        terms = [4, 8, 12]
        term = None
        mob = None

        for n in terms:
            _mob = fsmob(n, width)

            if mob is None:
                self.play(fsmob_anim(_mob))
            else:
                i = term if term < n else -1
                self.play(ReplacementTransform(mob, _mob[:i]))
                self.wait(0.5)
                self.play(fsmob_anim(_mob[i:]))

            mob = _mob
            term = n
            self.wait(1.5)

        self.play(fsmob_anim(mob, mode="OUT", lag_ratio=0))
        self.wait(0.5)

Scene Four

%%manim -qm SceneFour

class SceneFour(Scene):
    def construct(self):
        n = 12
        width = config.frame_width * 3 / 4
        mob = fsmob(n, width)
        mob.save_state()

        width *= sum(fseq(n)[-2:]) * 3 / 4
        _mob = fsmob(n, width)
        _mob.shift(-_mob[0].get_center())

        self.add(mob)
        self.wait(0.5)

        self.play(Transform(mob, _mob, run_time=6))
        self.wait(2)

        self.play(Restore(mob, run_time=4))
        self.wait(0.5)