Basic 2D Graphs

In the previous versions of Manim there was a special scene called GraphScene, however, in the most recent versions it is no longer necessary, since all the functionalities of GraphScene were transferred to the Axes class, so we can have as many graphs as we want in the same scene (which could not be done in GraphScene).

Axes

The Axes are made up of two NumberLines, and you can configure each axis with all the NumberLine options. To define the axes the main thing is to define the scales and the size of the axes.

Configurations

axes = Axes(
    #    [start,end,step]
    x_range=[-1,5,1],
    y_range=[-1,5,1],
    # Size of each axis
    x_length=6,
    y_length=6,
    # axis_config: the settings you make here
    # will apply to both axis, you have to use the
    # NumberLine options
    axis_config={"include_numbers": True},
    # While axis_config applies to both axis,
    # x_axis_config and y_axis_config only apply
    # to their respective axis.
    x_axis_config={
        "color": RED,
        "numbers_to_exclude": [2,3],
        "decimal_number_config": {
            "color": TEAL,
            "unit": "\\rm m",
            "num_decimal_places": 0
        }
    },
    y_axis_config={
        "color": YELLOW,
        "include_tip": False,
        "decimal_number_config": {
            "color": PINK,
            "unit": "^\\circ",
            "num_decimal_places": 1,
            "include_sign": True
        }
    },
)
Show result ../_images/axes1.png

Sometimes you will not want to define the length of the axes, sometimes you will want to define the size of each unit, if you do not specify the width of the axes you can define the unit_size.

axes = Axes(
    #    [start,end,step]
    x_range=[-1,5,0.5],
    y_range=[-1,5,1],
    # Size of each axis
    y_length=6,
    x_axis_config={
        # Instead x_lenght we can define "unit_size"
        "unit_size": 2,
        "numbers_with_elongated_ticks": list(range(-1,5)),
        "longer_tick_multiple": 3,
        # gap between axes and numbers
        "line_to_number_buff": 0.6,
        "numbers_to_include": list(range(-1,5)),
        "decimal_number_config": {
            "num_decimal_places": 0,
        },
        "font_size": 70
    },
    y_axis_config={
        "include_numbers": True
    },
)
self.add(axes)
Show result ../_images/axes2.png

Get coord from axes

There are two concepts to understand, the “coords” reference system and the “points” reference system.

  • Points: It is the reference system of the camera, and therefore it is absolute, we could say that it is our inertial system, the fixed system.

  • Coords: It is the reference system of each axis, if you create more than one axes then each one will have its own reference system.

Axes has the method Axes.coords_to_point and Axes.point_to_coords.

  • coords_to_point/c2p: It receives a two-dimensional vector \((x,y)\), and returns a three-dimensional vector \((x,y,z)\) which refers to the coordinates of your Axes using the reference system of the camera: Points.


  • point_to_coords/p2c: This method is the inverse, we enter a three-dimensional vector \((x,y,z)\) and it returns a two-dimensional coordinate \((x,y)\) that refers to the axes system coords.


Line graph

Making graphs with points should be easy for the student, we only need to make a polyline by changing the reference system using c2p, it is left as an exercise.

Show solution
def construct(self):

    axes = Axes(
        x_range = (0, 7),
        y_range = (0, 5),
        x_length = 7,
        axis_config={"include_numbers": True},
    )
    x_values = [0, 1.5, 2, 3, 4, 6.3]
    y_values = [1, 3, 2.5, 4, 2, 1.2]
    coords = [axes.c2p(x,y) for x,y in zip(x_values,y_values)]
    plot = VMobject(color=BLUE).set_points_as_corners(coords)

    self.add(axes,plot)
../_images/axes4.png

However, Manim already has a functionality that allows us to do the same.

def construct(self):
    axes = Axes(
        x_range = (0, 7),
        y_range = (0, 5),
        x_length = 7,
        axis_config={"include_numbers": True},
    )
    axes.center()
    line_graph = axes.get_line_graph(
        x_values = [0, 1.5, 2, 3, 4, 6.3],
        y_values = [1, 3, 2.5, 4, 2, 1.2],
        line_color=BLUE,
        vertex_dot_style={"stroke_width": 3, "fill_color": RED},
        stroke_width = 4,
    )
    self.add(axes, line_graph)
Show result ../_images/axes3.png

Use Text instead numbers in labels

def construct(self):
    x_labels = [
        "-\\frac{3\\pi}{2}", #  -3pi/2
        "-\\pi", #              -pi
        "-\\frac{\\pi}{2}", #   -pi/2
        "0", #                   Blank
        "\\frac{\\pi}{2}", #     pi/2
        "\\pi",#                 pi
        "\\frac{3\\pi}{2}" #     3pi/2
    ]
    axes = Axes(
        x_range = (-3*PI/2, 3*PI/2, PI/2),
        y_range = (-1.5, 1.5, 0.5),
        x_length = 10,
        axis_config={"include_tip": False}
    )
    axes.center()

    x_tex_lables = VGroup(*[
        MathTex(t).next_to(axes.x_axis.n2p(x),DOWN) if x >= 0 else
        # Shift pi<0 labels to left
        MathTex(t).next_to(axes.x_axis.n2p(x),DOWN).shift(LEFT*0.2)
        for t,x in zip(x_labels,np.arange(-3*PI/2, 3*PI/2+PI/2, PI/2)) if t != "0"
        #                                                              Ignore 0 value
    ])
    self.add(axes,x_tex_lables)
Show result ../_images/axes5.png

\(f(x)\) plots

Resolution

The plots are only approximations by means of bézier curves, so it is good to indicate the resolution of the graph:

def construct(self):
    # Define axes
    axes_left = Axes(
        x_range = (0, 7, 1),
        y_range = (0, 5, 1),
        x_length = 7,
        axis_config={"include_numbers": True},
    )
    axes_right = axes_left.copy()
    axes = VGroup(axes_left,axes_right).arrange(RIGHT)
    axes.width=config.frame_width-1
    # Define graphs
    function = lambda x: np.sqrt(x)

    # good resolution
    left_graph  = axes_left.get_graph(function, x_range=(0, 7, 0.05))
    # bad resolution                                           |-----> Resolution
    right_graph = axes_right.get_graph(function,x_range=(0, 7, 3))

    self.add(
        axes,
        left_graph,right_graph
    )
Show result ../_images/axes6.png

Style

The plots are like any VMobject, so it accepts stroke_width, stroke_color, it can be converted to DashedVMobject, etc.

More methods

This is another VMobject that has many more options, you already have the knowledge to be able to read the official documentation.

Parametric functions

def construct(self):
    # Define axes
    axes = Axes(
        x_range = (-3, 3, 1),
        y_range = (-3, 3, 1),
        x_length = 7,
        y_length = 7,
        axis_config={"include_numbers": True},
    )

    parametric_func = axes.get_parametric_curve(
        lambda t: np.array([
            np.cos(t),   # x
            2*np.sin(t), # y
        ]),
        t_range=(0,2*PI,0.1),
        # Domain <---| |----> resolution
        color=RED
    )

    equations = MathTex(r"""
      c:\begin{cases}
        \cos(t)\\
        2\sin(t)\\
        t\in [0,2\pi)
    \end{cases}
    """).scale(1.3)
    equations.to_corner(UR)

    self.add(axes,parametric_func,equations)
Show result ../_images/axes7.png

NumberPlane

It is the same as Axes but with background lines, it is ideal for making linear transformations, which we will see in the next course.

Official documentation.

Transform Riemann rectangles

With this we can do it using:

def construct(self):
    axes = Axes(
        x_range = (-1, 12, 1),
        y_range = (-1, 3, 1),
        x_length = 12,
        y_length = 7,
    )

    func = axes.get_graph(lambda x: 0.7*np.sqrt(x),x_range=[0,12,0.05])

    rects = VGroup(*[
            axes.get_riemann_rectangles(
            func,
            x_range=[1,11],
            dx=dx,
            input_sample_type="left",
            stroke_width=dx if dx > 0.1 else 0.8,
        )
            .set_color_by_gradient(PURPLE,ORANGE)
            .set_stroke(color=BLACK if dx > 0.1 else None)
        for dx in [1/(i) for i in range(1,15)]
    ])

    r = rects[0]

    self.add(axes,func,r)

    for rect in rects[1:]:
        self.play(
            Transform(r,rect)
        )
        self.wait(0.3)
    self.wait()
Show result