Manim 的 Axes 和 NumberPlane 有什么区别?范围、刻度怎么配

2026 年 11 月 8 日 · 极坐标⋅XYZ

Manim 的 Axes 和 NumberPlane 有什么区别?范围、刻度怎么配

NumberPlane 就是 Axes 加上一层背景网格:两者都是由 x、y 两根数轴拼成的二维坐标系,c2p、plot 这些方法用法完全一样,区别只在 NumberPlane 多画了网格线。范围和刻度由 x_range、y_range 配置,每个都是 [起点, 终点, 步长]:起点和终点决定这根轴覆盖的数学范围,步长决定刻度(NumberPlane 里同时是网格)的间隔;x_length、y_length 则决定它在屏幕上占多大。

Manim 是 3Blue1Brown 开源的数学动画引擎,画面里的坐标轴、曲线、公式都是 Python 对象,由你用代码来描述。现在通用的是社区维护的 Manim Community。本文代码按 0.21.0 实测(极坐标⋅XYZ 工作区与教程用的就是这一版),PyPI 上 0.22.0 已于 2026 年 10 月 8 日发布。还不熟悉的话,可以先翻一下这篇介绍;想把画函数这一块系统过一遍,极坐标⋅XYZ 的教程是按 0.21.0 做的 2026 年最新中文版,坐标系和函数作图有专门一章。

下面的完整代码都能在极坐标⋅XYZ 的工作区里直接运行,工作区装的是同一版本,不用自己装环境。

最小可运行的例子:同样的范围,并排放一个 Axes 和一个 NumberPlane

两个坐标系用一模一样的 x_range、y_range、x_length、y_length,左边是 Axes,右边是 NumberPlane,再各画一条 y = 0.4x² 的抛物线。曲线的写法一个字都没差:

from manim import *


class AxesVsPlane(Scene):
    def construct(self):
        cfg = dict(
            x_range=[-4, 4, 1],
            y_range=[-3, 3, 1],
            x_length=5.6,
            y_length=4.2,
        )

        axes = Axes(**cfg, axis_config={"include_numbers": True}).shift(LEFT * 3.4)
        plane = NumberPlane(**cfg).shift(RIGHT * 3.4)

        t1 = Text("Axes", font_size=32).next_to(axes, UP)
        t2 = Text("NumberPlane", font_size=32).next_to(plane, UP)

        curve1 = axes.plot(lambda x: 0.4 * x**2, color=YELLOW)
        curve2 = plane.plot(lambda x: 0.4 * x**2, color=YELLOW)

        self.play(Create(axes), Create(plane), FadeIn(t1), FadeIn(t2))
        self.play(Create(curve1), Create(curve2))
        self.wait()

左边是两根带数字刻度的轴,右边是一整片网格。其余完全相同:同一条曲线、同一个范围。NumberPlane 是 Axes 的子类,所以 Axes 能做的事它都能做。

为什么是这样

差别在哪:多一层网格,以及默认值

课程里对 NumberPlane 的定义很直接:NumberPlane = Axes + 背景网格。网格的样子用 background_line_style 调:

plane = NumberPlane(
    x_range=[-4, 4, 1],
    y_range=[-3, 3, 1],
    background_line_style={
        "stroke_color": BLUE_D,
        "stroke_width": 1,
        "stroke_opacity": 0.5,
    },
)

网格线的间隔就是 x_range、y_range 的步长,所以步长既管刻度也管网格。

另一个容易忽略的差别是默认值:在默认画布下,NumberPlane() 不传参数会铺满整个画面,Axes() 不传参数则是一对默认长度的轴(12×6),不一定铺满。改过画布配置的话,铺满的范围在 import 时就定下了,细节见画布尺寸和像素换算。需要精确控制,两个都显式写出范围和长度,最省心。

x_range、y_range 和 x_length、y_length:范围和大小是两回事

参数 管什么 单位
x_range=[起点, 终点, 步长] x 轴覆盖的数学范围,刻度间隔 数学坐标
y_range=[起点, 终点, 步长] y 轴同理 数学坐标
x_length、y_length 这根轴在屏幕上多长 Manim 的单位长度
axes = Axes(
    x_range=[-4, 4, 1],   # 起点, 终点, 步长
    y_range=[-3, 3, 1],
    x_length=8,           # 屏幕上的宽
    y_length=6,           # 屏幕上的高
    axis_config={"include_numbers": True},
)

这里 x 范围是 8 个数学单位、屏幕上长 8,每格恰好是 1 个屏幕单位。把 x_length 改成 4,范围不变,图就缩成一半宽,坐标值依然是 -4 到 4。反过来,范围改了、长度不变,同样的屏幕宽度就装更多数学坐标。想让数学坐标和屏幕坐标一比一,就让长度等于范围的跨度。

刻度怎么配

刻度由三处共同决定。

1. 步长,x_range 的第三个数。 [0, 10, 2] 是 0、2、4、6、8、10 各一个刻度。步长不一定是整数,[-1, 1, 0.5] 就是每 0.5 一格。

2. 刻度下面要不要标数字。 axis_config={"include_numbers": True} 对两根轴一起生效,想只给一根轴标数字,用 x_axis_config、y_axis_config 分别写。

3. 数字的格式。 把小数位数放进对应轴的配置:

from manim import *


class TickConfig(Scene):
    def construct(self):
        axes = Axes(
            x_range=[0, 10, 2],
            y_range=[-1, 1, 0.5],
            x_length=9,
            y_length=4,
            x_axis_config={"include_numbers": True},
            y_axis_config={
                "include_numbers": True,
                "decimal_number_config": {"num_decimal_places": 1},
            },
            tips=False,
        )
        graph = axes.plot(lambda x: np.sin(x), x_range=[0, 10, 0.05], color=YELLOW)

        self.play(Create(axes))
        self.play(Create(graph), run_time=2)
        self.wait()

x 轴每 2 一个刻度、标整数;y 轴每 0.5 一个刻度,数字固定一位小数。tips=False 是去掉轴末端的箭头。

注意代码里 axes.plot 的 x_range=[0, 10, 0.05] 和坐标系自己的 x_range 不是一回事:这里的第三个数是采样步长。课程里专门讲过,曲线其实是由一段段小线段近似出来的,步长太大曲线会变成明显的折线,所以要取得小一些。

坐标怎么换算

课程里把 Axes 概括为:两根 NumberLine 组成的二维坐标系。每根轴本身就是一根数轴,所以 x_range、include_numbers 这些参数和单独用 NumberLine 时是同一套。

坐标系还牵涉两套参考系:points 是屏幕的绝对坐标,也就是相机真正看到的位置;coords 是坐标系自己的坐标,也就是数学里的 x、y,会随 x_range 和摆放位置而变。c2p(coords_to_point)把坐标换成屏幕上的点,p2c(point_to_coords)反过来。后面画函数、标点、画面积,都靠 axes.c2p 把数学坐标摆到屏幕上。NumberPlane 继承了这些,所以不管选哪个,换算的写法都不用改。

p = axes.c2p(2, 1)                  # 数学坐标 (2, 1) -> 屏幕上的点
x, y = axes.p2c(dot.get_center())   # 屏幕上的点 -> 数学坐标

常见写错的地方

把 x_range 的第三个数当成长度

axes = Axes(x_range=[-4, 4, 8])   # 想让 x 轴长 8

第三个数是步长,上面这样写,整根轴只有两个刻度。要让它在屏幕上变宽,改的是 x_length。

只写了范围,以为图会自动铺满

Axes 的屏幕大小由 x_length、y_length 决定,不是由范围决定。范围改得很大(比如 [-100, 100, 10]),轴的屏幕长度不变,每个数学单位就变得很细。要么同时调长度,要么接受缩小后的样子。

标点时忘了 c2p

自己用 Dot 标点时,直接写 Dot([2, 1, 0]),点会落在屏幕坐标 (2, 1),而不是坐标系里的 (2, 1)。两者只有在坐标系正好以屏幕原点为中心、长度等于范围时才碰巧一致。要用 axes.c2p(2, 1) 换算:

dot = Dot(axes.c2p(2, 1), color=YELLOW)

常见追问(FAQ)

Q:我只想要网格,不要刻度数字,用哪个? A:用 NumberPlane,它默认就只有网格和轴线,没有数字。要数字,再通过 axis_config 打开。

Q:画函数图像,Axes 和 NumberPlane 选哪个? A:都行,plot、c2p 的用法一样。想要干净的图用 Axes,想要方格纸的感觉、方便读数时用 NumberPlane。

Q:x_range 的起点必须小于终点吗? A:正常写法是起点小于终点、步长为正。起点、终点不必关于 0 对称,[0, 10, 2] 完全可以。

Q:怎么拿到某个数学坐标在屏幕上的位置? A:axes.c2p(x, y)。反过来用 axes.p2c(点)。

Q:NumberPlane 的网格线颜色、粗细怎么改? A:通过 background_line_style,设置 stroke_color、stroke_width、stroke_opacity。


这个知识点在教程《坐标系与函数作图》里有配套视频和可改的代码,里面按「数轴 → Axes → plot」的顺序一步步搭起来;想直接动手,打开工作区。

来源:极坐标⋅XYZ · jizuobiao.xyz · 更新于 2026-11-08 · 基于 Manim Community 0.21.0