Graphing Functions – manim Series: Part 7

The post is part of a series on learning how to use manim.  You can find the previous tutorial post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

7.0 Graphing Functions

The easiest way to plot functions is to base your scene class on the GraphScene(). The scene creates a set of axes and has methods for creating graphs. One thing that confused me a little at first is that the axes belong to your scene class so you will need to use self to access the methods related to the axes. This caused me a few issues when I started out.

We will start off by looking at how to create the axes and graphs but we will come back to look at the CONFIG{} dictionary, which is used frequently in manim for initializing many of the class variables.

class PlotFunctions(GraphScene):
CONFIG = {
"x_min" : -10,
"x_max" : 10,
"y_min" : -1.5,
"y_max" : 1.5,
"graph_origin" : ORIGIN ,
"function_color" : RED ,
"axes_color" : GREEN,
"x_labeled_nums" :range(-10,12,2),

}
def construct(self):
self.setup_axes(animate=True)
func_graph=self.get_graph(self.func_to_graph,self.function_color)
func_graph2=self.get_graph(self.func_to_graph2)
vert_line = self.get_vertical_line_to_graph(TAU,func_graph,color=YELLOW)
graph_lab = self.get_graph_label(func_graph, label = "\\cos(x)")
graph_lab2=self.get_graph_label(func_graph2,label = "\\sin(x)", x_val=-10, direction=UP/2)
two_pi = TexMobject("x = 2 \\pi")
label_coord = self.input_to_graph_point(TAU,func_graph)
two_pi.next_to(label_coord,RIGHT+UP)

self.play(ShowCreation(func_graph),ShowCreation(func_graph2))
self.play(ShowCreation(vert_line), ShowCreation(graph_lab), ShowCreation(graph_lab2),ShowCreation(two_pi))

def func_to_graph(self,x):
return np.cos(x)

def func_to_graph2(self,x):
return np.sin(x)


self.setup_axes() will create a set of axes on screen. With the exception of whether the creation is animated or not, all other variables for the axes are set using CONFIG{}, which I’ll explain in a bit. The default values for the GraphScene() are shown below:

CONFIG = {
"x_min": -1,
"x_max": 10,
"x_axis_width": 9,
"x_tick_frequency": 1,
"x_leftmost_tick": None, # Change if different from x_min
"x_labeled_nums": None,
"x_axis_label": "$x$",
"y_min": -1,
"y_max": 10,
"y_axis_height": 6,
"y_tick_frequency": 1,
"y_bottom_tick": None, # Change if different from y_min
"y_labeled_nums": None,
"y_axis_label": "$y$",
"axes_color": GREY,
"graph_origin": 2.5 * DOWN + 4 * LEFT,
"exclude_zero_label": True,
"num_graph_anchor_points": 25,
"default_graph_colors": [BLUE, GREEN, YELLOW],
"default_derivative_color": GREEN,
"default_input_color": YELLOW,
"default_riemann_start_color": BLUE,
"default_riemann_end_color": GREEN,
"area_opacity": 0.8,
"num_rects": 50,
}


With our example we have changed x_min, x_max, y_min, y_max, graph_origin, axes_color, and x_labeled_num. The values assigned in our class take priority over values set by the parent class. Every value that we don’t change is automatically assigned the value defined in the parent class. The x_labeled_num property takes a list of numbers for labels along the x-axis. We’ve used range(-10,12,2) to generate a list of values from -10 to +10 in steps of 2. One issue I’ve noted with the y-axis is that setting the min and max values along either axis to numbers that are not integer multiples of 0.5 results in the tick marks along that axis not being symmetric about zero (e.g. try y_min = -1.2 and y_max = 1.2). I’m not sure what that is about but it isn’t a problem if you stick to integer multiples of 0.5 you don’t have any problems.

Once you have the axes set up you can use self.get_graph() to graph a function. The argument of get_graph() needs to be a pointer to a function, rather than a call to the function itself. In other words, since one of my functions is func_to_graph() I should use self.get_graph(func_to_graph) without any parenthese after func_to_graph.

Rather than defining separate functions for graphing we could use lambda functions. For example, if I define self.func = lambda x: np.cos(x) and then use self.get_graph(self.func) I will get the same result.

With get_graph() you do need to expicitly pass arguments rather than using CONFIG{}. The possible arguments, in addition to the function to graph, are color, x_min, and x_max. If you don’t specify a color GraphScene will cylce through BLUE, GREEN, and YELLOW for successive graphs. Since I didn’t specify a color for my second graph it was automatically assigned the first color, BLUE.

There is a handy method to draw a vertical line from the x-axis to the graph called get_vertical_line_to_graph(). I love that the method naming convention is descriptive enought that you can see what each method does at a glance. Good job, Grant! The arguments for get_vertical_line_to_graph() are the x-value where you want the line and the particular graph you want the line drawn to. Note that get_vertical_line_to_graph() is a method of the GraphScene and not the graph or axes so it is called with self.get_vertical_line_to_graph().

You can label graphs using get_graph_label() to set the text associated with the graph. This is similar to the get_text() method of the Braces() class in that it creates a texmobject at a specific location but does not draw it on the screen; you need to add or play to show the label. The arguments for get_graph_label() are the particular graph you want to add a label to and the text for the label. If you don’t specify an x-value and/or direction the label is placed at the end of the graph. The direction specifies where, relative to the x_value you want the label placed.

There are several other methods associated with the GraphScene() that are worth looking at, but I found the input_to_graph_point() helpful. By specifying an x-value on the graph, this method will return the coordinate on the screen where that graph point lies. This is handy if you want to place some text or other mobject to call out a particular point on a graph.

7.1 The CONFIG{} Dictionary

Whenever a scene or mobject are created a method called digest_config() gets called. This method starts with the class you defined and looks for a dictionary called self.CONFIG and compiles a list of all entries in the dictionary. It then goes to the parent class and looks for self.CONFIG there and adds those entries. If the method comes across keys that have already been found, it ignores the values from the parent class. digest_config() keeps traveling up the hierarchy to the top parent class, with is Container(). Each entry in this dictionary is then assigned a class variable based on the key and value. Thus the dictionary entry "x_min" : -1 becomes self.x_min = -1 and so on. Each dictionary entry becomes a class variable that can be accessed by the methods within the class. Understanding all of the CONFIG{} entries for a class is crucial to getting the most out of manim. For example, GraphScene() has the following CONFIG{} entries:

<br />class GraphScene(Scene):
CONFIG = {
"x_min": -1,
"x_max": 10,
"x_axis_width": 9,
"x_tick_frequency": 1,
"x_leftmost_tick": None, # Change if different from x_min
"x_labeled_nums": None,
"x_axis_label": "$x$",
"y_min": -1,
"y_max": 10,
"y_axis_height": 6,
"y_tick_frequency": 1,
"y_bottom_tick": None, # Change if different from y_min
"y_labeled_nums": None,
"y_axis_label": "$y$",
"axes_color": GREY,
"graph_origin": 2.5 * DOWN + 4 * LEFT,
"exclude_zero_label": True,
"num_graph_anchor_points": 25,
"default_graph_colors": [BLUE, GREEN, YELLOW],
"default_derivative_color": GREEN,
"default_input_color": YELLOW,
"default_riemann_start_color": BLUE,
"default_riemann_end_color": GREEN,
"area_opacity": 0.8,
"num_rects": 50,
}


The parent class for GraphScene() has the following dictionary:

class Scene(Container):
CONFIG = {
"camera_class": Camera,
"camera_config": {},
"frame_duration": LOW_QUALITY_FRAME_DURATION,
"construct_args": [],
"skip_animations": False,
"ignore_waits": False,
"write_to_movie": False,
"save_frames": False,
"save_pngs": False,
"pngs_mode": "RGBA",
"movie_file_extension": ".mp4",
"name": None,
"always_continually_update": False,
"random_seed": 0,
"start_at_animation_number": None,
"end_at_animation_number": None,
}


Container(), the parent to Scene as well as Mobject, has no CONFIG{} entries.

When talking about mobjects, the list of CONFIG{} entries can get a little long. I won’t go into those right now but it is worth you time to take a look at the hierarchy of some of the mobject subclasses to see what all the properties you can control are.

Next time we’ll take a look at more graphing.

Posted in Just for Fun, Programming | Tagged , , , | 3 Comments

Aligning Text – manim Series: Part 6

You can find the previous post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

6.0 Aligning Text and Using Braces

This post will show how to use braces to visually group equations or text together but also how to align text elements. We will first write a program to align elements of two equations but in a somewhat clunky fashion; this is not the most elegant way to accomplish this task. After looking at this first version we will rewrite the code in a more concise fashion that lines everything up even better.

Copy and paste the following code and run it.

class UsingBraces(Scene):
#Using braces to group text together
def construct(self):
eq1A = TextMobject("4x + 3y")
eq1B = TextMobject("=")
eq1C = TextMobject("0")
eq2A = TextMobject("5x -2y")
eq2B = TextMobject("=")
eq2C = TextMobject("3")
eq1B.next_to(eq1A,RIGHT)
eq1C.next_to(eq1B,RIGHT)
eq2A.shift(DOWN)
eq2B.shift(DOWN)
eq2C.shift(DOWN)
eq2A.align_to(eq1A,LEFT)
eq2B.align_to(eq1B,LEFT)
eq2C.align_to(eq1C,LEFT)

eq_group=VGroup(eq1A,eq2A)
braces=Brace(eq_group,LEFT)
eq_text = braces.get_text("A pair of equations")

self.play(GrowFromCenter(braces),Write(eq_text))


To line up parts of the equations on screen we use next_to() and align_to(). For this example we’ve broken the equation into smaller parts and then used next_to() to place the subparts of each equation next to each other and then align_to() to line up the left side of each part of the equation. You can also use UP, DOWN, and RIGHT to align different edges of the mobjects. We’ve also added a brace to show how to visually group a set of equations. In order to use the braces we must use VGroup() to combine the equations. When we instantiate the braces the first argument is the group and the second argument is where the braces are located relative to the grouping. You can set the text next to the braces using get_text(). This method does not draw the text on the screen, it is only used to set the location of the text relative to the braces so you will still need to add the text to the screen.

class UsingBracesConcise(Scene):
#A more concise block of code with all columns aligned
def construct(self):
eq1_text=["4","x","+","3","y","=","0"]
eq2_text=["5","x","-","2","y","=","3"]
eq1_mob=TexMobject(*eq1_text)
eq2_mob=TexMobject(*eq2_text)
eq1_mob.set_color_by_tex_to_color_map({
"x":RED_B,
"y":GREEN_C
})
eq2_mob.set_color_by_tex_to_color_map({
"x":RED_B,
"y":GREEN_C
})
for i,item in enumerate(eq2_mob):
item.align_to(eq1_mob[i],LEFT)
eq1=VGroup(*eq1_mob)
eq2=VGroup(*eq2_mob)
eq2.shift(DOWN)
eq_group=VGroup(eq1,eq2)
braces=Brace(eq_group,LEFT)
eq_text = braces.get_text("A pair of equations")

self.play(Write(eq1),Write(eq2))
self.play(GrowFromCenter(braces),Write(eq_text))


Here is a more concise version of the previous code. Each equation is written out as a list with each part of the equation as a separate string. This allows more control over the vertical alignment of the parts of the two equations. Inside the for loop we use align_to() to line up the left edge of the elements in eq1 and eq2.

Notice that when creating the texmobjects that we passed the variable name of the list with an asterisk in front of it eq1_mob=TexMobject(*eq1_text). The asterisk is a Python command to unpack the list and treat the argument as a comma-separated list. Thus eq1_mob=TexMobject(*eq1_text) is identical to eq1_mob=TexMobject("4","x","+","3","y","=","0").

Things to try:
– Arrange the equations on the screen

Next up: Plotting graphs in manim

Posted in Just for Fun, Programming | Tagged , , , | 6 Comments

Mathematical Equations – manim Series: Part 5

You can find the previous post in this series here and the overview of the entire series here.

Note:  This tutorial series was created using Python 2.7 and the May 9, 2018 commit of manim, which can be found here.  I hope to create posts on the updated version sometime in the future.

5.0 Mathematical Equations

A math animation package wouldn’t be much use if you couldn’t include nice looking equations. The best way I know of to typeset equations is using LaTeX ($\LaTeX$) , which manim makes use of. If you’d like to learn more about typesetting with LaTeX I’d recommend the tutorials at ShareLaTeX for a basic intro, but you don’t need to know much about LaTeX to use manim. You can find a list of commonly used symbols here, which is about all you need to know for manim.

Copy and paste the code into a .py file and use extract_scene.py to run the following scene:

class BasicEquations(Scene):
#A short script showing how to use Latex commands
def construct(self):
eq1=TextMobject("$\\vec{X}_0 \\cdot \\vec{Y}_1 = 3$")
eq1.shift(2*UP)
eq2=TexMobject("\\vec{F}_{net} = \\sum_i \\vec{F}_i")
eq2.shift(2*DOWN)

self.play(Write(eq1))
self.play(Write(eq2))


In LaTeX you normally enclose an equation with dollar signs  to denote an equation and that works here as well. The main difference is that, due to how manim parses the text, an extra backslash must be included in front of all LaTeX commands. For instance Greek letters can be created in LaTeX by typing out the name of the letter preceded by a backslash; lower case alpha $\alpha$ would be $\alpha$, the angle theta $\theta$ would be $\theta$. In manim, however, a double backslash is needed so $\alpha$ would be $\\alpha$ and $\theta$ would be written as $\\theta$.

You can place a vector arrow over a variable such as $\vec{A}$ using \vec{A} or, since manim requires double backslashes, \\vec{A}. Whatever you place inside the brackets will show up on screen with an arrow over it. Subscripts are denoted by the underscore so $\vec{X}_0$ would be written as $\vec{X}_0$. If the subscript consists of more than a single character you can enclose the subscript in brackets. Thus $\vec{F}_{net}$ in manim would be $\\vec{F}_{net}$.

It can get tedious having to always include the dollar signs so the TexMobject class assumes all strings are math objects. TEX ($\TeX$) is the typesetting language that LaTeX is based on so I assume TexMobject is named for TEX. The main difference between TextMobject() and TexMobject is the text math object assumes everything is plain text unless you specify an equation with dollar signs while the Tex math object assumes everything is an equation unless you specify something is plain text using \\text{}.

When mobjects of any sort are created the default position seems to be the center of the screen. Once created you can use shift() or move_to to change the location of the mobjects. For this example above I’ve moved the equations either two MUnits up or two MUnits down (remember that the MUnit or math unit is what I call the measure of length inside manim). Since the screen height is set to a default of 8 MUnits, a 2 MUnit shift corresponds to about a quarter of the screen height.

The Write() method, which is a sublcass of ShowCreation, takes a TextMobject or TexMobject and animates writing the text on the screen. You can also pass a string to Write() and it will create the TextMobject for you. Write() needs to be inside of play() in order to animate it.

5.1 Coloring Equations

class ColoringEquations(Scene):
#Grouping and coloring parts of equations
def construct(self):
line1=TexMobject("\\text{The vector }", "\\vec{F}_{net}", "\\text{ is the net force on object of mass }")
line1.set_color_by_tex("force", BLUE)
line2=TexMobject("m", "\\text{ and acceleration }", "\\vec{a}", ". ")
line2.set_color_by_tex_to_color_map({
"m": YELLOW,
"{a}": RED
})
sentence=VGroup(line1,line2)
sentence.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
self.play(Write(sentence))


For this example we have broken our text into blocks of plain text and equations. This allows us to color the equations using either set_color_by_tex() or set_color_by_tex_to_color_map(). The set_color_by_tex() method takes the individual string you want colors and the color as arguments. It looks like you only have to specify part of a string to match but the entire string gets colored. For instance, if we type in line1.set_color_by_tex("F",BLUE), the only place a capital F occurs is in the force variable so the whole net force variable is colored blue. If instead we try line1.set_color_by_tex("net",BLUE), the term net appears in two places in line1 so both the force variable and the string is the net force on object of mass are colored blue. If you want to change the color of multiple elements within a list of texmobjects you can use set_color_by_tex_to_color_map() and a dictionary. The key for the dictionary should be the text we want colored (or a unique part of the string) and the value should be the desired color.

Notice that, since we are using a texmobject and not a textmobject, we have to enclose plain text in the LaTeX command \\text{}. If you don’t do this the text is assumed to be part of an equation so the font and spacing are of the text looks funny. Thus “the net force on object of mass” would look like $the net force on object of mass$. The equation environment doesn’t recognize spaces between words, uses a different font, and spaces the letters differently than normal text.

By grouping the two lines together with VGroup(), we can use the arrange_submobjects() method to space out the two lines. The first argument is the direction you want the objects spaced out and buff (I assume) is the buffer distance between the mobjects. There are several default buffer distances defined in constants.py but you can also a single number. The smallest default buffer is SMALL_BUFF=0.1 and the largest is LARGE_BUFF=1. Although I didn’t dive into the code, I think the way the buffers work is as a multiplicative factor of one of the main directional vectors (e.g. UP, DOWN, LEFT, RIGHT) so that specifying SMALL_BUFF and LEFT would be equivalent to $0.1*(-1,0,0) = (-0.1,0,0)$.

Things to try:
– Create your own equations using the symbols here.
– Try changing the colors of different parts of the equations

In the next post in this series we’ll look at how to align text.

Posted in Just for Fun, Programming | Tagged , , , | 6 Comments

Creating Text – manim Series: Part 4

You can find the previous post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

4.0 Creating Text

There is a special subclass of Mobject called a TextMobject (a text math object). Add the following class to your manim_tutorial_1.py file and type python extract_scene.py manim_tutorial_1.py Shapes -pl at the command line. Note that the text looks really fuzzy because we are rending the animations at low quality to speed things up. With a small file like this you could render it at full resolution without taking too much time. To do this, replace -pl with -p (leaving off the low resolution tag).

class AddingText(Scene):
def construct(self):
my_first_text=TextMobject("Writing with manim is fun")
second_line=TextMobject("and easy to do!")
second_line.next_to(my_first_text,DOWN)
third_line=TextMobject("for me and you!")
third_line.next_to(my_first_text,DOWN)

self.wait(2)
self.play(Transform(second_line,third_line))
self.wait(2)
second_line.shift(3*DOWN)
self.play(ApplyMethod(my_first_text.shift,3*UP))



To create a textmobject you must pass it a valid string as an argument. Text rendering is based on Latex so you can use many Latex typesetting features; I’ll get into that in a later post. As a subclass of mobjects, any method such as move_to, shift, and next_to can be used with textmobjects.

The wait() method will prevent the next command for the scene from being executed for the desired number of seconds. The default time is 1 second so calling self.wait() will wait 1 second before executing the next command in your script.

You should notice that, during the animation, the second line jumps down while the top line gently glides up. This has to do with the fact that we applied the shift() method to the second line but we created an animation of the shift to the first line. When animating a mobject() method the ApplyMethod() animation is needed. Notice the arguments of ApplyMethod() is a pointer to the method (in this case my_first_text.shift without any parentheses) followed by a comma and then the what you would normally include as the argument to the shift() method. In other words, ApplyMethod(my_first_text.shift,3*UP) will create an animation of shifting my_first_text three MUnits up.

4.1 Changing Text

Add this code to your tutorial file and extract the following scene.

class AddingMoreText(Scene):
#Playing around with text properties
def construct(self):
quote = TextMobject("Imagination is more important than knowledge")
quote.set_color(RED)
quote.to_edge(UP)
quote2 = TextMobject("A person who never made a mistake never tried anything new")
quote2.set_color(YELLOW)
author=TextMobject("-Albert Einstein")
author.scale(0.75)
author.next_to(quote.get_corner(DOWN+RIGHT),DOWN)

self.wait(2)
self.play(Transform(quote,quote2),ApplyMethod(author.move_to,quote2.get_corner(DOWN+RIGHT)+DOWN+2*LEFT))
self.play(ApplyMethod(author.match_color,quote2),Transform(author,author.scale(1)))


Here we see how to change the color of text using set_color(). This uses the same colors discussed in relation to drawing geometric shapes, many of which are defined in the COLOR_MAP dictionary in constants.py. In addition to setting the color, you can also match the color to another object. In the second to last line of code above we use match_color() to change the color of the author to match quote2.

You can change the size of text using scale(). This method scales the mobject up by the numerical factor given. Thus scale(2) will double the size of a mobject while scale(0.3) will shrink the mobject down to 30% of its size. It doesn’t seem you can use scale() in an animation (ApplyMethod(author.scale,2) doesn’t work). There is probably a better way around this but what I would do if I wanted to animate a scale-up or down is create a second textmobject that
is the final size I want and use Transform() to smoothly shift from one scale to another. Hopefully I’ll figure out a better way to do this later on.

You can align mobjects with the center of the edge of the screen by telling to_edge() whether you want the object to be UP, DOWN, LEFT, or RIGHT. You can also use to_corner(), in which case you need to combine two directions such as UP+LEFT to indicate the corner.

Each mobject has a bounding box and you can get the coordinates of the corners of this bounding box using get_corner() and specifying a direction. Thus get_corner(DOWN+LEFT) will return the location of the lower left corner of a mobject. In our example we find the lower right corner of quote and place the author one unit down from that point. Later we move the author down and slightly left of quote2.

An important thing to note is that the Transform() animation still leaves the mobject quote on the screen but has just changed its display text and properties to be those of quote2. This is why FadeOut() refers to quote and not quote2. However, the corner of quote is where it was originally, which is why we have to find the corner of quote2 to move author to the correct location. Keep in mind that when you use Tranform, properties of the mobects involved might not be what you think they are so beware.

Another useful piece of information is that the scale() method changes the size of the objects as it currently is.  In other words, using scale(.5) followed by scale(.25) results in an object that is $0.5*0.25 = 0.125$ times the original size

Things to try:
– Try using the to_corner() method
– Check out COLOR_MAP in the constants.py file and change the color of the text

4.2 Rotating and Highlighting Text

The following code will demonstrate how to rotate text and give it some pizzazz. As usual, copy the code into your tutorial file and extract it using python extract_scene.py manim_tutorial_1.py RotateAndHighlight -p

class RotateAndHighlight(Scene):
#Rotation of text and highlighting with surrounding geometries
def construct(self):
square=Square(side_length=5,fill_color=YELLOW, fill_opacity=1)
label=TextMobject("Text at an angle")
label.bg=BackgroundRectangle(label,fill_opacity=1)
label_group=VGroup(label.bg,label) #Order matters
label_group.rotate(TAU/8)
label2=TextMobject("Boxed text",color=BLACK)
label2.bg=SurroundingRectangle(label2,color=BLUE,fill_color=RED, fill_opacity=.5)
label2_group=VGroup(label2,label2.bg)
label2_group.next_to(label_group,DOWN)
label3=TextMobject("Rainbow")
label3.scale(2)
label3.set_color_by_gradient(RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE)
label3.to_edge(DOWN)



We’ve added a square in the background to show what BackgroundRectangle does. Note that the opacity of the fill color defaults to zero so if you don’t define the fill_opacity you only see the edges of the square. To create a background rectange you need to specify the textmobject to apply this method to, as well as the opacity. You can’t change the color background to anything but black.

The VGroup class allows you to combine multiple mobjects into a single vectorized math object. This allows you to apply any VMobject methods to the all elements of the group. You are still able change properties of the orignal mobjects after they are groups. In other words, the original mobjects are not destroyed, the vmobject is just a higher level grouping of the mobjects. By grouping the text and the background rectangle we can then use rotate() to change the orientation of both objects together. Note that TAU is equal to $2 \pi$ (see the Tau Manifesto, which makes some interesting points).

The next_to() method can be thought of as a shift relative to some other object so label2_group.next_to(label_group,DOWN) places label2_group shifted down one unit from label1_group (remember that the unit of distance is set by the FRAME_HEIGHT variable in constants.py and the default screen height is 8 units).

You can create a a color gradient using set_color_by_gradient(). Pass the method any number of colors, separated by commas.

Things to play with
– Try changing the fill opacity for both the square and the background rectangle
– Try rotating the background rectangle separately from the the text
– Change the color of label2 to see how it affects the readability of the text
– Change the colors of “Rainbow”
– Place the “Rainbow” text on a different edge of the screen.

NOTE: If anything in these posts isn’t making sense, please leave a comment. I hope to continue to edit these posts to clarify and help people learn how to use manim so feedback is appreciated.

You can find a copy of this code at https://github.com/zimmermant/manim_tutorial

Posted in Just for Fun, Programming | Tagged , , , | 12 Comments

More Shapes – manim Series: Part 3

You can find the previous post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

3.0 More Shapes

You can create almost any geomtric shape using manim. You can create circles, squares, rectangles, ellipses, lines, and arrows. Let’s take a look at how to draw some of those shapes.

Copy and paste the code below into your manim_tutorial_1.py file and type the following into the command line to run this scene: python extract_scene.py manim_tutorial_1.py MoreShapes -pl.

class MoreShapes(Scene):
#A few more simple shapes
def construct(self):
circle = Circle(color=PURPLE_A)
square = Square(fill_color=GOLD_B, fill_opacity=1, color=GOLD_A)
square.move_to(UP+LEFT)
circle.surround(square)
rectangle = Rectangle(height=2, width=3)
ellipse=Ellipse(width=3, height=1, color=RED)
ellipse.shift(2*DOWN+2*RIGHT)
pointer = CurvedArrow(2*RIGHT,5*RIGHT,color=MAROON_C)
arrow = Arrow(LEFT,UP)
arrow.next_to(circle,DOWN+LEFT)
rectangle.next_to(arrow,DOWN+LEFT)
ring.next_to(ellipse, RIGHT)

self.play(GrowArrow(arrow))
self.play(GrowFromCenter(rectangle), GrowFromCenter(ellipse), GrowFromCenter(ring))


You’ll notice we have a few new shapes and we are using a couple of new commands. Previously we saw the Circle(), Square(), Line(), and Polygon() classes. Now we’ve added Rectangle(), Ellipse(), Annulus(), Arrow(), and CurvedArrow(). All shapes, with the exception of lines and arrows, are created at the origin (center of the screen, which is (0,0,0)). For the lines and arrows you need to specify the location of the two ends.

For starters, we’ve specified a color for the square using the keyword argument color=. Most of the shapes are subclasses of VMobject, which stands for a vectorized math object. VMobject is itself a subclass of the math object class Mobject. The best way to determine the keyword arguments you can pass to the classes are to take a look at the allowed arguments for the VMobject and Mobject class. Some possible keywords include radius, height, width, color, fill_color, and fill_opacity. For the Annulus() class we have inner_radius and outer_radius for keyword arguments.

A list of the named colors can be found in the COLOR_MAP dictionary located in the constant.py file. The named colors are keys to the COLOR_MAP dictionary which yield the hex color code. You can create your own colors using a hex color code picker (Google it and adding entries to COLOR_MAP.

3.1 Direction Vectors

The constants.py file contains other useful defintions, such as direction vectors that can be used to place objects in the scene. For example, UP is a numpy array (0,1,0), which corresponds to 1 unit of distance. To honor the naming convention used in manim I’ve decided to call the units of distance the MUnit or math unit (this is my own term, not a manim term). Thus the default screen height is 8 MUnits (as defined in constants.py). The default screen width is 14.2 MUnits.

If we are thinking in terms of x-, y-, and z-coordinates, UP is a vector pointing along the positive y-axis. RIGHT is the array (1,0,0) or a vector pointing along the positive x-axis. The other direction vectors are LEFT, DOWN, IN, and OUT. Each vector has a length of 1 MUnit. After creating an instance of an object you can use the .move_to() method to move the object to a specific location on the screen. Notice that the direction vectors can be added together (such as UP+LEFT) or multiplied by a scalar to scale it up (like 2*RIGHT). In other words, the direction vectors act like you would expect mathematical vectors to behave. If you want to specify your own vectors, they will need to be numpy arrays with three components. The center edge of each screen side is also defined by vectors TOP, BOTTOM, LEFT_SIDE, and RIGHT_SIDE.

The overall scale of the vectors (the relationship between pixels and MUnits) is set by the FRAME_HEIGHT variable defined in constants.py. The default value for this is 8. This means you would have to move an object 8*UP to go from the bottom of the screen to the top of the screen. At this time I don’t see a way to change it other than by changing it in constants.py.

Mobjects can also be located relative to another object using the next_to() method. The command arrow.next_to(circle,DOWN+LEFT) places the arrow one MUnit down and one to the left of the circle. The rectangle is then located one MUnit down and one left of the arrow.

The Circle() class has a surround() method that allows you to create a circle that completely encloses another mobject. The size of the circle will be determined by the largest dimension of the mobject surrounded.

3.2 Making Simple Animations

As previously mentioned, the .add() method places a mobject on screen at the start of the scene. The .play() method can be used animate things in your scene.

The names of the animations, such as FadeIn or GrowFromCenter, are pretty self-explanatory. What you should notice is that animations play sequentially in the order listed and that if you want multiple animations to occur simultaneously, you should include all those animations in the argument of a single .play() command separated by commas. I’ll show you how to use lists of animations to play multiple animations at the same time in a later entry in this series.

Things to try:
– Use the Polygon() class to create other shapes
– Try placing multiple objects on the screen at various locations
– Take a look at the different types of transformations available in /animation/transforms.py

Next time we will take a look at writing text on the screen.

Posted in Just for Fun, Programming | Tagged , , , | 3 Comments

Creating Your First Scene – manim Series: Part 2

This is part of a series of posts on my journey learning how to use manim, a mathematical animation toolbox created for the 3blue1brown video series. Check out the previous entry to install the required packages to get started.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

To give you an idea how I’m approaching learning manim, I started off by going through the example scenes in example_scenes.py in the top-level manim folder. I also found another Github repository by Adirockzz95, who had worked out several more example files. Once I had a good feel for those examples, I started watching the 3b1b videos and when I came to an animation I wanted to learn I’d go to the original code (located in the old_projects folder of the manim code) and try to figure out how it worked. Note that, due to changes in manim, much of the older code will throw an error. However, trying to fix the code enough to run will give you a better idea of how things work.

Copy and paste the code below into a new text file and save it as manim_tutorial_1.py in the top-level manim directory. The .py extension tells your operating system that this is a Python file.

Open up a  command line window and go to the top-level manim directory, and type python extract_scene.py manim_tutorial_1.py Shapes -pl

We are calling the Python interpreter with the python command. The first argument passed to Python, extract_scence.py is the part of the manim code that runs your script and creates a video file. This argument will always need to be called to create videos. The second argument, manim_tutorial_1.py is the name of the file (i.e. the module) where your script is stored. The third argument, Shapes is the name of the class (i.e. the scene name) defined within your file that gives instructions on how to construct a scene. The last arguments, -pl tell the extract_scene script to preview the animation by playing it once it is done and to render the animation in low quality, which speeds up the time to create the animation. See the README.md for directions on what other arguments you can pass to extract_scene.

from big_ol_pile_of_manim_imports import *

class Shapes(Scene):
#A few simple shapes
def construct(self):
circle = Circle()
square = Square()
line=Line(np.array([3,0,0]),np.array([5,0,0]))
triangle=Polygon(np.array([0,0,0]),np.array([1,1,0]),np.array([1,-1,0]))

self.play(ShowCreation(circle))
self.play(GrowFromCenter(square))
self.play(Transform(square,triangle))


If everything works you should see the following messages (or something similar) in your terminal:

The video should look like this:

All of the various manim modules are contained in big_ol_pile_of_manim_imports.py so importing this gives you all of the basic features of manim. This doesn’t include every single module from manim but it contains the core modules. It is worth your time to dive into some of the modules to see how things are put together. I’ve learned a surprising amount of Python by trying to figure out how things work. Incidentally I find using the search box at https://github.com/3b1b/manim very helpful for finding different classes and figuring out what arguments they take and how they work.

2.1 Scenes and Animation

The Scene is the script that tells manim how to place and animate your objects on the screen. I read that each 3blue1brown video is created as individual scenes which are stiched together using video editing software. You must define each scene as a separate class that is a subclass of Scene. This class must have a construct() method, along with any other code required for creating objects, adding them to the screen, and animating them. The construct() method is essentially the main method in the class that gets called when run through extract_scene.py. It is similar to __init__; it is the method that is automatically called when you create an instance of the class. Within this method you should define all of your objects, any code needed to control the objects, and code to place the objects onscreen and animate them.

For this first scene we’ve created a circle, a square, a line, and a triangle. Note that the coordinates are specified using numpy arrays np.array(). You can pass a 3-tuple like (3,0,0), which works sometimes, but some of the transformation methods expect the coordinates to be a numpy array.

One of the more important methods from the Scene() class is the play() method. play() is what processes the various animations you ask manim to perform. My favorite animation is Transform, which does a spectacular job of morphing one mobject into another. This scene shows a square changing into a triangle, but you can use the transform to morph any two objects together. To have objects appear on the screen without any animation you can use add() to place them. The line has been added and shows up in the very first frame, while the other objects are fade in or grow. The naming of the transformations is pretty straight forward so it’s usually obvious what each one does.

Things to try
– Change the order of the add() and play() commands.
– Try using the Transform() method on other shapes.
– Check out the shapes defined in geometry.py which is located in the /manim/mobject/ folder.

Next time: In the next entry in this series I’ll look at other shapes and how to change their properties.

Posted in Just for Fun, Programming | Tagged , , , | 7 Comments

Installing manim and Python – manim Series: Part 1

This post is part of a series on what I learned as I taught myself how to use manim, the mathematical animation software behind the beautiful videos at 3blue1brown. Check them out if you haven’t seen any of them yet.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

1.0 Installing manim and Python

To get started you will need to have Python 2.7 installed and a copy of the manim repository cloned to your computer. I recommend using Anaconda to install Python and use the virtual environment commands within Anaconda to manage the packages you need to install. I should note that I’m working in macOS so things might be a bit different in Windows or Linux.

• Open a terminal and navigate to the folder you want to clone the manim files to.
• In the terminal type git clone https://github.com/3b1b/manim.git
• Create a virtual environment to house all the Python packages by typing conda create -n manim27 python=2.7
• This will create an environment named manim27 (feel free to choose another name) and it will install a clean version of Python 2.7 in the environment.
• To activate your environment type source activate manim27 on macOS and Linux systems or activate manim27 on Windows systems. If you gave your environment a different name you can replace manim27 with the name you gave to your environment.
• In the terminal, navigate to the top-level folder of manim. You can identify this folder because it should have a file named requirements.txt.
• To install the packages required for manim, type pip install -r requirements.txt. These packages will be installed in your virtual environment and won’t affect any other installations of Python on your system.
• You will need to tell manim where to save your video files. Open constants.py, which is in the top-level manim directory and change the directory listed under MEDIA_DIR to be the place you want videos saved to. You will want to change Dropbox (3Blue1Brown)/3Blue1Brown Team Folder shown below to be your desired output directory:
# Things anyone wishing to use this repository for their
# own use will want to change
MEDIA_DIR = os.path.join(
os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
)

• Type python extract_scene.py example_scenes.py SquareToCircle -pl. This should result in a video that shows a square turning into a circle. If this doesn’t work for you I can’t provide any tech support; I recommend you go to https://github.com/3b1b/manim/issues?q=is%3Aissue+is%3Aclosed to see the issues others have had getting manim to run.
• The output video file will show up in a subdirectory named /animations/example_scenes/480p15/ in the directory you specified for outputs in constants.py.

There is also a Docker image available (see https://github.com/3b1b/manim at the bottom of the page) but I ran into issues getting it working on my machine.

In my next entry in this series we’ll start off creating our own animated scene.

UPDATE:  Brian Howell has put together a great set of instructions installing manim and Python 3.7 together over at http://bhowell4.com/manic-install-tutorial-for-mac/.  All of my posts are based on an older version of manim and using Python 2.7

Posted in Just for Fun, Programming | Tagged , , , | 11 Comments