Like many people, I have long been a fan of 3blue1brown’s videos, so when he put out a ‘how to’ for the Manim animation library he built in mid 2024, I put a placeholder in my mind, with the goal of having a go when I had some time. Unfortunately, I had somewhat forgotten about it until someone posting online that Claude 3.7, Anthropic’s LLM, knew Manim, which reminded me.
My first thought was that if Claude knew it, then I could one-shot it and I wouldn’t have to learn it. But as I found out, Claude knows it, but isn’t good at it. Since I know Python, I decided to watch the video and see what I could make. I was quickly surprised how logical and simple it was.
It wasn’t long before I was able to put together this animation.
My learning process
Installation
The first step was to install the packages required to make Manim work. If you are on Linux, you can just install ffmpeg, LaTeX, and sound exchange.
There is an installation guide for Manim Community here:
https://docs.manim.community/en/stable/index.html I suggest you follow their guide.
To run Manim you will need ffmpeg, latex, and sound exchange. In Linux you can just install these via the terminal.
I was using Windows and WSL, so I had to go hunting, in my hunt I found out that there is a Windows package manager called Chocolatey https://community.chocolatey.org/. Which will download and add them to Path for you! Which saves a few clicks.
- https://community.chocolatey.org/packages/ffmpeg-shared
- https://community.chocolatey.org/packages/manim-latex
- https://community.chocolatey.org/packages/sox.portable
Development Environment
Watching Grant from 3blue1brown use Manim was remarkably similar to the Jeremy Howard ‘Solve it’ approach. This method is all about breaking problems down into the smallest possible steps, ensuring a rapid feedback loop, and iterating quickly. For Manim specifically, this means being able to write code and immediately see the visual output of your animations, allowing for quick experimentation and refinement of your visualisations.
A rapid feedback loop and being able to see the result of your changes quickly is the most important thing to reduce your frustration and increase your learning rate.
Grant sets up his workspace so he could quickly type code and see the immediate consequences of that code by viewing the output. He has some special key mappings for this. Some people in VS Code have mimicked his key-mappings with VS Code Manim extensions.
Initially I tried using Jupyter notebooks, but it just seemed a little clunky, as it would require moving up and down to see the output from the cell.
So instead, I switched to VS Code and Manim Viewer. There is also a Manim Sideview – which is a much more popular extension, but I found it more buggy.
Basic Shapes and Positioning
In his videos, Grant starts by showing how to create simple shapes, how to move them and how to transform them. My first step was to understand what the commands did. Here I am going to start the way he did, by making a circle and doing some things to it.
If you are iterating and want to see output fast, then self.add(your_thing)
displays “your_thing” on the screen.
You can do things to "your_thing"
before or after adding it to the screen.
For example, to add a circle, you start by defining it. In Manim, a circle is Circle()
, which can take some arguments like:
from manim import *
class my_animation(Scene):
def construct(self):
my_circle = Circle(radius=2, colour=RED)
self.add(my_circle)

Each animation Scene in Manim is a Class, and inside of that class you call a method called construct, which enables you to create the animation.
class MyFirstScene(Scene): # Create a class that inherits from Scene
def construct(self): # The construct method is called when the scene runs
The Scene class provides several methods you’ll commonly use:
self.add()
: Adds objects to the scene without animationself.play()
: Creates animated transitions between statesself.wait()
: Pauses the animation for a specified durationself.remove()
: Removes objects from the scene
When you run your Manim code, it is processed sequentially from top to bottom, generating each frame of the animation based on the instructions you’ve provided.
In this case the circle is only added to the screen once you have called self.add(my_circle)
, which means you can position the circle, scale it, and do things to it prior to it appearing on screen.
For example, here I set its size, colour, and move it to the upper left corner.
class circle(Scene):
def construct(self):
my_circle = Circle(radius=2, color=RED)
my_circle.to_corner(UP + LEFT) ## You can also use (UL) instead of (UP + LEFT)
self.add(my_circle)

There are innumerable ways to position things or to ‘shift’ them. Notice the multiplier on .shift(DOWN *3)
:
class circle(Scene):
def construct(self):
my_circle = Circle(radius=2, color=RED)
my_circle.shift(DOWN*3)
self.add(my_circle)

There are many things you can do, and the best way to learn about them appears to be thinking about what you want to do and trying it. Then look in the documentation, or ask your favourite LLM. Be warned though, the LLMs do make mistakes on a few things.
Animations
Should you want to animate your objects, you can do so by using self.play()
with an instruction, for example:
from manim import *
class my_animation(Scene):
def construct(self):
my_circle = Circle(radius=2)
self.play(FadeIn(my_circle), run_time=3)
self.play(FadeOut(my_circle), run_time=3)
The command above will take my circle, fade it in over 3 seconds, then fade it back out.
For rapid iteration you should use the self.add()
command, rather than self.play()
. Using only .add()
will render a .png image instantly, whereas the .play()
renders an animation and slows your iteration speed.
What I found useful was to have two environments open. One for doing quick experiments with self.add()
and the other producing the formal animation with self.play()
.
You can also move the shape you created, transform the circle, scale it, rotate it, etc.
Below I show a little more. I’m not an expert at Manim, but what is good about it is that you can piece things together in logical steps.
In the following example, I create a circle, fade it in, then move it to the right edge while scaling it to half size. Then I transform it into a square, before moving it once again and fading it out.
from manim import *
class my_animation(Scene):
def construct(self):
my_circle = Circle(radius=2)
self.play(FadeIn(my_circle), run_time=3)
self.play(my_circle.animate.to_edge(RIGHT).scale(0.5), run_time=2)
#Here I transformed my_circle into a square
#but it is still "my_circle"
self.play(Transform(my_circle, Square(side_length=2)), run_time=2)
self.play(my_circle.animate.to_corner(UP + RIGHT), run_time=2)
self.play(FadeOut(my_circle), run_time=3)
self.wait(3)
Creating Graphs
Once I had the hang of how things moved, I decided to try and move onto something more interesting. Shifting shapes around is great, but it’s not especially useful by itself.
The first step was to learn how to create a graph. Fortunately, like most things with Manim, it’s fairly simple; however, you can add a lot of complexity if you wish.
This is about as simple as it gets, it is an axis from 0 to 10, with tick marks at intervals of 1:
class my_animation(Scene):
def construct(self):
axis = Axes(
x_range=[0, 10, 1],
y_range=[0, 10, 1],
)
self.add(axis)
In this instance, there is no animation for the axis, it just appears as I have used self.add(axis)
.
A Complete Example: UK Income Tax Visualisation
Below I show a graph of income tax vs salary for the UK – not including national insurance. I’ve chosen this particular example as it’s very compact code that demonstrates how much you can accomplish with Manim in relatively few lines. It also highlights one of the nice things about animating with Manim: everything can be written sequentially and read like a script.
I have added a few explanations below for things that might be useful for people learning. I thought about adding it to the code as comments, but it adds too much clutter.
Axis range from 0 to 200,000 with ticks at 10000 x_range=[0, 200000, 10000]
Include arrow tips on axis, the font size for the axis labels is set here, not elsewhere. axis_config={"include_tip": True, "font_size": 20},
You can’t just put x and y values directly into the graph
Option 1: Use a lambda function (used below)graph = axis.plot(lambda x: current_tax(x), x_range=[0, 200000], color=BLUE)
Option 2: Use plot_line_graph with explicit values wage = list(range(0, 200000, 1000))
current_taxes = [current_tax(i) for i in wage]
graph = axis.plot_line_graph(x_values=wage, y_values=current_taxes, color=BLUE)
from manim import *
class tax(Scene):
def construct(self):
axis = Axes(
x_range=[0, 200000, 10000], # range from 0 to 200,000
y_range=[0, 100000, 10000],
axis_config={"include_tip": True, "font_size": 20},
x_axis_config={"numbers_to_include": range(0, 200000, 50000)},
y_axis_config={"numbers_to_include": range(0, 100000, 10000)})
self.play(Create(axis))
self.wait(2)
def current_tax(x):
if x < 12500: return 0
elif x < 50000: return (x - 12500) * 0.20
elif x < 125000: return (50000 - 12500) * 0.20 + (x - 50000) * 0.4
else: return ((50000 - 12500) * 0.20 + (125000 - 50000) * 0.4
+ (x - 125000) * 0.45)
graph = axis.plot(lambda x: current_tax(x), x_range=[0, 200000], color=BLUE)
self.play(Create(graph))
self.wait(2)
Animating the graph is pretty straightforward, as shown above. You can add extra variables like run_time
to slow down the animation:
self.play(Create(graph), run_time=5)
Working with Groups
Should you want to do anything to the graph and the axis together, you will need to create a vector group. The simplest way to create a vector group is something like this:
graph_group = VGroup(axis, graph)
self.play(graph_group.animate.scale(0.5).to_corner(UL))
self.wait(2)
The axis and the data are independent, so if you don’t make them a vector group, they will scale and move differently.
After scaling an object, you might think that you can just scale it back; however, I encountered difficulties in doing so. The workaround I found was to make a copy of the original object, then using “Transform” to rescale:
graph_group = VGroup(axis, graph)
graph_group_copy = graph_group.copy()
self.play(graph_group.animate.scale(0.5).to_corner(UL), run_time=2)
self.wait(2)
# transform from scaled graph_group back to original copy
self.play(Transform(graph_group, graph_group_copy), run_time=2)
Adding Text and Descriptions
In my experience, adding text to Manim is a bit of an art, which I have not completely mastered. It is easy to make mistakes like the following. This doesn’t just apply to text, but to any vector group you make:
text = VGroup(
Text("This is a graph of the tax paid by an individual"),
Text("Blue line goes uppppppp")
)
self.play(Write(text))
To avoid overlapping text and to position correctly, you must use .arrange()
. This lets you set how the VGroup arranges on screen. So in this case below, it puts each Text()
vector below the other:
text = VGroup(
Text("This is a graph of the tax paid by an individual", color=RED, font="Arial", font_size=20),
Text("Blue line goes uppppppp", color=BLUE, font_size=25),
).arrange(DOWN)
self.play(Write(text))
Here’s a complete example that puts everything together:
from manim import *
class tax(Scene):
def construct(self):
axis = Axes(
x_range=[0, 200000, 10000],
y_range=[0, 100000, 10000],
axis_config={"include_tip": True, "font_size": 20},
x_axis_config={"numbers_to_include": range(0, 200000, 50000)},
y_axis_config={"numbers_to_include": range(0, 100000, 10000)})
self.play(FadeIn(axis))
def current_tax(x):
if x < 12500: return 0
elif x < 50000: return (x - 12500) * 0.20
elif x < 125000: return (50000 - 12500) * 0.20 + (x - 50000) * 0.4
else: return ((50000 - 12500) * 0.20 + (125000 - 50000) * 0.4
+ (x - 125000) * 0.45)
graph = axis.plot(lambda x: current_tax(x), x_range=[0, 200000], color=BLUE)
self.play(FadeIn(graph))
graph_group = VGroup(axis, graph)
graph_group_copy = graph_group.copy()
self.play(graph_group.animate.scale(0.5).to_corner(DL), run_time=2)
self.wait(2)
text = VGroup(
Text("This is a graph of the tax paid by an individual", color=RED, font="Arial", font_size=20),
Text("Blue line goes uppppppp", color=BLUE, font_size=25),
).arrange(DOWN).to_corner(UR)
self.play(Write(text))
self.play(Transform(graph_group, graph_group_copy), run_time=2)
self.play(FadeOut(graph_group), FadeOut(text), run_time=2)
That’s all there is to it. My goal with this wasn’t to give an exhaustive tutorial on Manim, but more to show how simple it is to create animations with Manim.
There are many other people who are more knowledgable, who have created more in-depth tutorials, but hopefully this will provide people with a small launching point to go onto making better things!