Where to Discuss?

Local Group

Preface

Goal: Animating Math Equation using matplotlib. Observe how each quadratic coefficient impact the shape.

The world is running so fast. The work requirement is also grow up. We need to catch-up with current visualization technique. It is a crime if I just stand in front of my notebook doing my job. I need to enhance my skill, from just static chart to dynamic plot.

We can visualize how each quadratic coefficient, impact the shape, impact the relative position the axes. In fact I just realize that my understanding of this parabolic, was wrong all these time. I could never righten my understanding without proper visualization.

There is other advantage as well. Instead of just sending PNG image to telegram? Why not sending mp4 video to telegram as well?


2D Plot Animation

The difficulties escalate quickly. Let’s animate previous simple plot.

Source Code

The python script is longer now. You can obtain the code in this following link:

Coefficient

We have only three coeffcients: a, b, c.

We are going to use three scene:

  1. Change a only: controls the curvature (parabola’s width and direction).
  2. Change b only: affects the tilt (linear shift).
  3. Change c only: shifts the entire curve up/down.
Scene Changing Coefficient Effect on Graph
0 a (quadratic term) Changes the curvature (parabola becomes wider or narrower).
1 b (linear term) Tilts the parabola left/right (affects the vertex position).
2 c (constant term) Moves the entire parabola up/down without changing its shape.

The vertex is the turning point of the parabola. Changing b does not move the parabola purely left or right. Instead, it moves diagonally.

Class Skeleton

class QuadraticAnimator:
    def __init__(self):
        ...

    def quadratic_function(self, x, a, b, c):
        ...

    def update(self, frame):
        ...

    def animate(self):
        ...

    def save_animation(self,
        ...

Now we can run the animation script

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

class QuadraticAnimator:
    ...

# Run animation
if __name__ == "__main__":
    animator = QuadraticAnimator()
    animator.save_animation()
    animator.animate()

Quadratic Function

What quadratic function that we use? Let’s compute y values for the quadratic value using this function.

    def quadratic_function(self, x, a, b, c):
        return a * x**2 + b * x + c

Initialization

The initialization has tdifferent parts.

  1. Common chart setting. Nothing special.
  2. Equation Text
  3. Initial Plot Values
  4. Animation Settings

Common chart setting.

Let’s start with common chart setting.

class QuadraticAnimator:
    def __init__(self):
        self.fig, self.ax = plt.subplots()
        self.x = np.linspace(-10, 20, 400)

        # Set up the plot
        self.ax.set_xlim(-10, 20)
        self.ax.set_ylim(-150, 100)
        self.ax.axhline(y=0, color='k')
        self.ax.axvline(x=0, color='k')
        self.ax.grid()
        self.ax.set_xlabel("X-Axis")
        self.ax.set_ylabel("Y-Axis")

Equation Text

I separate the equation text, and the dynamic coefficient text.

        self.eq_text = self.ax.text(
            0.05, 0.9, r"$y = ax^2 + bx + c$",
            transform=self.ax.transAxes, fontsize=16, color='b')

We need to pay attention to the dynamic coefficient. Because we are going to update it periodically.

        self.coeff_text = self.ax.text(
            0.05, 0.8, "", transform=self.ax.transAxes,
            fontsize=14, color='r')

Initial Plot Values

Both line and points.

        self.line, = self.ax.plot([], [], 'k')
        self.points, = self.ax.plot([], [], 'bo')

Animation Settings

At last, the frame settings. The total frames is 240.

        # Frames per scene
        self.scene_duration = 60

        # Frames to pause before next scene
        self.pause_duration = 20

        # Full cycle
        self.total_frames = 3 * (
            self.scene_duration + self.pause_duration)

Three is the number of scene.

Frame Breakdown Per Scene

We are going to separate the frames are split into three distinct regions! Each scene consists of:

  • 60 frames of animation (changes happen)
  • 20 frames of pause (nothing changes)

We are going to use modulo and division to compute this situation.

Frame Range Calculation Scene Animation or Pause?
0 – 59 0 // 80 0 Animate (Changing a)
60 – 79 79 // 80 0 Pause
80 – 139 80 // 80 1 Animate (Changing b)
140 – 159 159 // 80 1 Pause
160 – 219 160 // 80 2 Animate (Changing c)
220 – 239 239 // 80 2 Pause

Run The Animation

The animate() function creates and runs an animation, using matplotlib.animation.FuncAnimation.

    def animate(self):
        ani = animation.FuncAnimation(
            self.fig, self.update,
            frames=self.total_frames,
            interval=100, blit=False, repeat=True)
        plt.show()

FuncAnimation works internally like this:

  • FuncAnimation calls update(frame) for “each frame”.
  • The frame number starts from 0 and goes up to self.total_frames - 1.
  • Our update(frame) function changes the plotted data based on frame.
  • After reaching the last frame, it starts over (repeat=True).

Updating Animation.

To animate, we would rely on update function. The structure of the update function follows, common animation techniques used in Matplotlib’s FuncAnimation. Using this logic:

  • Breaking the frames into scenes,
  • Smoothly changing parameters, and
  • Using trigonometric functions for oscillation.

The specific implementation is based on the general techniques, that are widely used concepts in animation and visualization.

Update Function

Update function for animation.

The update function is long and containing multiple steps.

  1. Scene Management (Frames and Scenes)
  2. Handle Pauses Between Scenes
  3. Normalize Time for Smooth Oscillation
  4. Adjust Quadratic Coefficients (Based on Scene)
  5. Compute New Function Values
  6. Update Text Display
  7. Return Updated Elements

Let’s break down step by step:

Scene Management (Frames and Scenes)

We need to determines which scene is active, and where we are within that scene.

        scene = frame // (
            self.scene_duration + self.pause_duration)

        scene_frame = frame % (
            self.scene_duration + self.pause_duration)
  • The total animation consists of multiple scenes (each modifying one coefficient).

  • A scene lasts self.scene_duration frames, followed by a self.pause_duration.

  • scene tells us which coefficient is changing.

  • scene_frame tells us how far along we are in that scene.

Handle Pauses Between Scenes

We need to keeps the plot unchanged during pauses. Pause before switching to the next scene.

        if scene_frame >= self.scene_duration:
            return self.line, self.points, self.coeff_text
  • If scene_frame is within the pause period, nothing changes.

  • The function immediately returns, skipping the update.

Normalize Time for Smooth Oscillation

We need to create smooth oscillation of coefficients over time. Normalize time within the scene, moves from +1 to -1 to +1 smoothly.

        t = scene_frame / self.scene_duration        
        direction = np.cos(t * np.pi * 2)
  • t represents how far along we are in the scene (0 β†’ 1).

  • np.cos(t * np.pi * 2) generates a smooth oscillation from +1 to -1 and back.

Adjust Quadratic Coefficients (Based on Scene)

We need to change one coefficient at a time, while keeping others constant.

        if scene == 0:  # Change a only
            a = 2 * direction
            b, c = - 12, -64
        elif scene == 1:  # Change b only
            a, c = 1, -64
            b = - 12 * direction
        else:  # Change c only
            a, b = 1, -12
            c = -64 * direction
  • scene == 0: a oscillates between +2 and -2.
  • scene == 1: b oscillates between +12 and -12.
  • scene == 2: c oscillates between +64 and -64.

For simplicity you can rewrite as:

        a, b, c = 1, -12, -64
        if scene == 0:
            a = 2 * direction
        elif scene == 1:
            b = -12 * direction
        else:
            c = -64 * direction

Compute New Function Values

Now we need to updates the quadratic curve and sample points.

        y = self.quadratic_function(self.x, a, b, c)
        self.line.set_data(self.x, y)

        xdot = np.arange(-6, 16, 1)
        ydot = self.quadratic_function(xdot, a, b, c)
        self.points.set_data(xdot, ydot)
  • self.quadratic_function(self.x, a, b, c) calculates new y values.

  • self.line.set_data(self.x, y) updates the main curve.

  • xdot and ydot are sample points displayed as dots.

Update Text Display

We need to track by displaying the current values of a, b, and c.

        # Update dynamic text
        self.coeff_text.set_text(
            r"$a = {:.1f}, b = {:.1f}, c = {:.1f}$"\
                .format(a, b, c))
  • Formats the coefficient values to 1 decimal place.
  • Updates the equation text on the plot

Return Updated Elements

Now we should ensure FuncAnimation redraws the updated elements.

        return self.line, self.points, self.coeff_text
  • Returns the updated line, sample points, and text, so they appear in the animation.

Save Animation

As a bonus, we can save the animation result into video in mp4 format.

    def save_animation(self,
            filename="quadratic-animation-mpl.mp4", fps=30):
        # Save the animation as a video.
        ani = animation.FuncAnimation(
            self.fig, self.update, frames=self.total_frames,
            interval=100, blit=False, repeat=True)

        writer = animation.FFMpegWriter(fps=fps)
        ani.save(filename, writer=writer)
        print(f"Animation saved as {filename}")

Video Result

This will show you this animation below:


Using Scatter

There is other alternative as well.

Original Plot

You can spot this line:

        self.line, = self.ax.plot([], [], 'k')
        self.points, = self.ax.plot([], [], 'bo')

And updating it as:

        xdot = np.arange(-6, 16, 1)
        ydot = self.quadratic_function(xdot, a, b, c)
        self.points.set_data(xdot, ydot)        

Using Scatter

Actually we can setup scatter for the dots.

You can spot this line:

        self.line, = self.ax.plot([], [], 'k')
        self.points = self.ax.scatter([], [])

And updating it as:

        xdot = np.arange(-6, 16, 1)
        ydot = self.quadratic_function(xdot, a, b, c)
        self.points.set_offsets(np.c_[xdot, ydot])    

Seaborn Styling

Seaborn is great for static plots but not really designed for animations. It builds on Matplotlib but adds extra layers that make updating elements tricky. For animations, I’d better stick to pure Matplotlib.

However we can utilize the seaborn style in matplotlib. The saqme logic, but cleaner look.

Source Code

You can obtain the code in this following link:

What’s changed?

Seaborn Styling

  • Applied darkgrid style for a cleaner look.
  • Used Seaborn color palette for lines and points.
  • Improved text contrast for better readability.

The result looks like this below:


3D Surface Animation

We can apply the same method to the surface.

Source Code

You can obtain the code in this following link:

Coefficient

Now we have only six coeffcients: a, b, c, d, e, f.

We are still using meshgrid to plot the surface.

Adding background music

I put background music form pixabay.com, a jazz style piano made by WELC0MEИ0. And combine using ffmpeg command.

ffmpeg -i "/home/epsi/quadratic-surface.mp4" \
       -i "/home/epsi/171220-jazz-style-piano-20557.mp3" \
       -c:v copy -c:a aac -shortest \
       "/home/epsi/quadratic-surface-with-music.mp4"

Video Result

The result will show you this animation below:

Looks nice right?


3D View Animation

Instead of the surface, we can aimate the camera only. So you can see the shape from different perspective.

Source Code

You can obtain the code in this following link:

With large number of frame, the viseo result will be big.

Adding background music

I put background music form pixabay.com, named dancing rhythms driving elctro swing composition made by OpenMusicList. And reduce the size using ffmpeg command.

ffmpeg \
  -i "/home/epsi/mpl/quadratic-surface-view.mp4" \
  -i "/home/epsi/dancing-rhythms-driving-electro-swing-composition-149594.mp3" \
  -c:v libx264 -crf 28 -r 15 -c:a aac \
  -b:a 128k -b:v 500k \
  -vf "scale=640:-2,format=yuv420p" \
  -shortest -movflags +faststart \
  "/home/epsi/quadratic-surface-view-with-music.mp4"

Video Result

The result will show you this animation below:


What is Next πŸ€”?

I will continue with manim if I have time.


Conclusion

Thank you for visiting. We shall meet again. Farewell.