Search code examples
pythonmatplotlibanimation

Epicycloid animation with Matplotlib in Python


I want the animation epicycloid.gif to be framed depending in the size of the epicycloid, so depending on the radio of the spinning circule. As you see at ratio = 2 the animation in format .gif does not show the whole curve.

enter image description here

Here is the modified code to Thomas Kuhn hypocyloid

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
    

class Epicycloid:

    def __init__(self, ratio, frames, ncycles):
        self.frames = frames
        self.ncycles = ncycles
        self.fig, self.ax = plt.subplots()
        self.ax.set_aspect('equal')

        # Big circle (stationary):
        theta = np.linspace(0, 2 * np.pi, 100)
        x = np.cos(theta)
        y = np.sin(theta)

        self.big_circle, = self.ax.plot(x, y, 'b-')

        # Small circle (rolling outside):
        self.small_r = 1. / ratio
        r = self.small_r
        x = r * np.cos(theta) + (1 + r)
        y = r * np.sin(theta)
        self.small_circle, = self.ax.plot(x, y, 'k-')

        # Line and dot:
        self.line, = self.ax.plot([1 + r, 1], [0, 0], 'k-')
        self.dot, = self.ax.plot([1 + r], [0], 'go', ms=5)

        # Epicycloid curve:
        self.epicycloid, = self.ax.plot([], [], 'r-')

        # Create the animation
        self.animation = FuncAnimation(
            self.fig, self.animate,
            frames=self.frames * self.ncycles,
            interval=50, blit=False,
            repeat_delay=2000,
        )

    def update_small_circle(self, phi):
        # Update the small rolling circle (outside the big circle)
        theta = np.linspace(0, 2 * np.pi, 100)
        x = self.small_r * np.cos(theta) + (1 + self.small_r) * np.cos(phi)
        y = self.small_r * np.sin(theta) + (1 + self.small_r) * np.sin(phi)
        self.small_circle.set_data(x, y)

    def update_epicycloid(self, phis):
        # Update the epicycloid path based on current phase phi
        R = 1
        r = self.small_r
        x = (R + r) * np.cos(phis) - r * np.cos((R + r) / r * phis + np.pi)
        y = (R + r) * np.sin(phis) - r * np.sin((R + r) / r * phis + np.pi)
        self.epicycloid.set_data(x, y)

        # Update the line connecting the center of the small circle to the point
        center = [(R + r) * np.cos(phis[-1]), (R + r) * np.sin(phis[-1])]
        self.line.set_data([center[0], x[-1]], [center[1], y[-1]])
        self.dot.set_data([x[-1]], [y[-1]])

    def animate(self, frame):
        frame = frame + 1
        phi = 2 * np.pi * frame / self.frames
        self.update_small_circle(phi)
        self.update_epicycloid(np.linspace(0, phi, frame))


# Create the Epicycloid with a ratio, frames, and cycles
epicycloid = Epicycloid(ratio=3, frames=50, ncycles=4)

# Uncomment the next line if you want to save the animation as a gif
epicycloid.animation.save('epicycloid.gif', writer='imagemagick', fps=10, dpi=75)

# Show the animation
plt.show()

Solution

  • As mentioned in the comments, you can update the limits. Since you know the radius of the big circle is 1 and the radius of the small circle is self.small_r, you can set the limits to be 5% bigger (that number seemed to work, but you could play around with).

    In code, you can add this to __init__ somewhere after self.small_r is defined:

    limit = (1+self.small_r*2)*1.05
    self.ax.set_xlim(-limit, limit)
    self.ax.set_ylim(-limit, limit)