APAW Week 11: Visualizations In Python with Manim
What is Manim?
Manim is a python library for creating 2D animations, particularly mathematical and technological visualizations. It was created by the YouTube creator 3Blue1Brown for his videos which visualize mathematical concepts, and is now maintained by a wider community. It can be seen in actions in the video below:
Manim is very handy and has lots of useful features for mathematical and computing based visualizations, such as TeX and LaTeX support and 3D graphs.
Installation
Manim can be installed through any standard python package manager, for example using pip:
python3 -m pip install manim
And can then be run directly from the command line, for example to render a high quality video of a scene called ‘HelloManim’ from the file ‘mycode.py’:
python3 -m manim -pqh mycode.py HelloManim
Scenes
Manim works by inheriting from a core Scene class which takes instructions to play and add elements to the visualisation. A simple scene to construct the manim logo is below:
from manim import *
class ManimCELogo(Scene):
def construct(self):
self.camera.background_color = "#ece6e2"
logo_green = "#87c2a5"
logo_blue = "#525893"
logo_red = "#e07a5f"
logo_black = "#343434"
ds_m = MathTex(r"\mathbb{M}", fill_color=logo_black).scale(7)
ds_m.shift(2.25 * LEFT + 1.5 * UP)
circle = Circle(color=logo_green, fill_opacity=1).shift(LEFT)
square = Square(color=logo_blue, fill_opacity=1).shift(UP)
triangle = Triangle(color=logo_red, fill_opacity=1).shift(RIGHT)
logo = VGroup(triangle, square, circle, ds_m) # order matters
logo.move_to(ORIGIN)
self.add(logo)
And it produces:
My Goal With Manim
My goal with Manim was to learn to produce good visuals for presentations and videos I would have to make. To this end I decided that my goal should be to make a simple animation that displays the file format of the .DIR files used in the Harry Potter PS1 games that I reverse-engineered in a prior week.
The resultant animation is below:
And its code is as follows:
from manim import *
class VisualizeBinary(Scene):
def draw_hex_dump(self):
# The hex string is split into an array of hex pairs
hex_values = "24 00 00 00 44 00 60 00 73 00 60 00 20 00 57 00 68 00 6B 00 6B 00 20 00 42 00 64 00 20 00 4C 00 6E 00 72 00 73 00 00 00 1A 00 00 00 41 00 71 00 64 00 60 00 20 00 62 00 6B 00 64 00 60 00 71 00 64 00 63 00 00 00 1C 00".split(" ")
# Group hex values into rows of 8 and format them as a string
formatted_hex = ""
for i in range(len(hex_values)):
formatted_hex += " ".join(hex_values[i * 8 : (i * 8) + 8])
formatted_hex += "\n"
# Display the formatted hex dump
hex_text = Text(formatted_hex)
self.play(Write(hex_text))
return hex_text
def highlight_file_count(self, hex_text):
# Create a rectangle to highlight the file count in the hex dump
file_count_box = Rectangle(width=1.65, height=0.6, stroke_width=0)
file_count_box.align_to(hex_text, UL)
file_count_box.shift(UP * 0.05 + LEFT * 0.05)
file_count_box.set_fill(RED, opacity=0.5)
self.play(Create(file_count_box))
# Add a label for the file count
file_count_label = Text("File count", font_size=24)
file_count_label.next_to(
file_count_box, UP, buff=0.1
) # Adjust the buffer for spacing
self.play(Write(file_count_label))
def highlight_file_name(self, hex_text):
# Create rectangles to highlight the file name in two separate locations
file_name_box1 = Rectangle(width=(1.6 * 3) + 0.25, height=0.6, stroke_width=0)
file_name_box1.align_to(hex_text, UL)
file_name_box1.shift(UP * 0.05 + LEFT * 0.05 + RIGHT * 1.7)
file_name_box1.set_fill(GREEN, opacity=0.5)
file_name_box2 = Rectangle(width=(1.6 * 3) + 0.25, height=0.6, stroke_width=0)
file_name_box2.align_to(hex_text, UL)
file_name_box2.shift(UP * 0.05 + LEFT * 0.05 + DOWN * 0.65)
file_name_box2.set_fill(GREEN, opacity=0.5)
# Animate the creation of both file name boxes
self.play(Create(file_name_box1), Create(file_name_box2))
# Add a label for the file name
file_name_label = Text("File name", font_size=24)
file_name_label.next_to(
file_name_box1, UP + UP, buff=0.1
) # Adjust the buffer for spacing
self.play(Write(file_name_label))
def highlight_file_size(self, hex_text):
# Create a rectangle to highlight the file size in the hex dump
file_size_box = Rectangle(width=1.65, height=0.6, stroke_width=0)
file_size_box.align_to(hex_text, UL)
file_size_box.shift(UP * 0.05 + LEFT * 0.05 + DOWN * 0.65 + RIGHT * 5.1)
file_size_box.set_fill(YELLOW, opacity=0.5)
self.play(Create(file_size_box))
# Add a label for the file size
file_size_label = Text("File size", font_size=24)
file_size_label.next_to(
file_size_box, RIGHT, buff=0.1
) # Adjust the buffer for spacing
self.play(Write(file_size_label))
def highlight_file_offset(self, hex_text):
# Create a rectangle to highlight the file offset in the hex dump
file_offset_box = Rectangle(width=1.65, height=0.6, stroke_width=0)
file_offset_box.align_to(hex_text, UL)
file_offset_box.shift(UP * 0.05 + LEFT * 0.05 + (DOWN * 0.65 * 2))
file_offset_box.set_fill(BLUE, opacity=0.5)
self.play(Create(file_offset_box))
# Add a label for the file offset
file_offset_label = Text("File offset", font_size=24)
file_offset_label.next_to(
file_offset_box, LEFT, buff=0.1
) # Adjust the buffer for spacing
self.play(Write(file_offset_label))
def construct(self):
# Visualize the hex dump and then highlight different sections
hex_text = self.draw_hex_dump()
self.highlight_file_count(hex_text)
self.highlight_file_name(hex_text)
self.highlight_file_size(hex_text)
self.highlight_file_offset(hex_text)
# Pause the animation to keep the final frame visible
self.wait(5)
Future Use Cases For Manim
In the future I hope to use Manim for some of my University and work presentations, and I also think it has potential in automatically generating visualisations of data formats, for example it could be used to visualise binary information automatically using Kaitai Struct a format for specifying binary file layouts, which would allow intuitive visualization and demonstration of binary file layouts based on their contents using the parsers.