+
22
-

回答

python中可以使用Manim或Matplotlib

我们用ManimCE (Community Edition) 编写正弦函数介绍动画的示例代码。

这个动画将包括:

标题介绍。

展示单位圆。

在单位圆上动态显示一个点 P,并标出角度 θ。

强调点 P 的 y 坐标即为 sin(θ)。

将单位圆上的点 P 的 y 坐标映射到右侧的坐标系中,动态绘制出正弦函数图像。

标注一些关键点,如周期。

前提条件:

你已经安装好了 ManimCE (Community Edition)。

你已经安装了 LaTeX (如 MiKTeX, MacTeX, TeX Live) 和 FFmpeg。

from manim import *
import numpy as np

class SineFunctionIntroduction(Scene):
    def construct(self):
        # 0. 配置和初始文本
        self.camera.background_color = WHITE # 白色背景
        default_font_size = 24
        Text.set_default(font_size=default_font_size, color=BLACK)
        MathTex.set_default(font_size=default_font_size, color=BLACK)
        Line.set_default(color=BLACK)
        Dot.set_default(color=BLACK)
        # 有些全局设置在 VGroup 或特定对象中设置更安全

        # 1. 标题
        title = Text("正弦函数 (Sine Function)", font_size=48, color=BLUE_E)
        self.play(Write(title))
        self.wait(1)
        self.play(FadeOut(title))
        self.wait(0.5)

        # 2. 创建单位圆和相关元素
        # 单位圆坐标系
        unit_circle_axes = Axes(
            x_range=[-1.5, 1.5, 1],
            y_range=[-1.5, 1.5, 1],
            x_length=4,
            y_length=4,
            axis_config={"color": GRAY, "include_tip": False}
        ).to_edge(LEFT, buff=1)
        unit_circle_axes.add_coordinates(font_size=20, color=GRAY) # 添加坐标轴刻度

        # 单位圆
        circle = Circle(radius=unit_circle_axes.x_range[1] * unit_circle_axes.x_length / (unit_circle_axes.x_range[1]-unit_circle_axes.x_range[0]),
                        color=BLUE_D, stroke_width=2) # 根据坐标轴长度调整圆的实际半径
        circle.move_to(unit_circle_axes.c2p(0,0)) # 将圆心移动到坐标原点

        # 标签:单位圆
        circle_label = Text("单位圆 (Unit Circle)", font_size=20, color=DARK_GRAY)
        circle_label.next_to(unit_circle_axes, DOWN, buff=0.2)

        self.play(Create(unit_circle_axes), Create(circle), Write(circle_label))
        self.wait(1)

        # 3. 动态点 P, 角度 theta, 和 y 坐标
        theta_tracker = ValueTracker(0) # 追踪角度 theta

        # 点 P 在单位圆上
        dot_p = Dot(color=RED_D, radius=0.08)
        dot_p.add_updater(
            lambda m: m.move_to(
                unit_circle_axes.c2p(
                    np.cos(theta_tracker.get_value()),
                    np.sin(theta_tracker.get_value())
                )
            )
        )

        # 半径 OP
        radius_op = Line(unit_circle_axes.c2p(0,0), dot_p.get_center(), color=GREEN_D, stroke_width=2)
        radius_op.add_updater(lambda m: m.put_start_and_end_on(unit_circle_axes.c2p(0,0), dot_p.get_center()))

        # 角度弧线和标签
        angle_arc = Arc(
            radius=0.4, # 弧线半径
            start_angle=0,
            angle=theta_tracker.get_value(),
            color=ORANGE,
            arc_center=unit_circle_axes.c2p(0,0)
        )
        angle_arc.add_updater(
            lambda m: m.become(
                Arc(
                    radius=0.4, start_angle=0, angle=theta_tracker.get_value(),
                    color=ORANGE, arc_center=unit_circle_axes.c2p(0,0)
                )
            )
        )
        theta_label = MathTex(r"\theta", font_size=28, color=ORANGE)
        theta_label.add_updater(
            lambda m: m.move_to(
                Arc(radius=0.6, start_angle=0, angle=theta_tracker.get_value()).point_from_proportion(0.5)
            ).shift(unit_circle_axes.c2p(0,0)) # 确保标签相对于圆心定位
        )


        # P点到x轴的垂线 (表示 y 坐标)
        line_pm_y = DashedLine(dot_p.get_center(), unit_circle_axes.c2p(np.cos(theta_tracker.get_value()), 0), color=RED_D)
        line_pm_y.add_updater(
            lambda m: m.put_start_and_end_on(
                dot_p.get_center(),
                unit_circle_axes.c2p(np.cos(theta_tracker.get_value()), 0) # 投影到x轴
            )
        )
        # y坐标标签
        y_coord_label = MathTex(r"y_P = \sin(\theta)", font_size=24, color=RED_E)
        y_coord_label.add_updater(
            lambda m: m.next_to(line_pm_y, RIGHT, buff=0.1) if np.cos(theta_tracker.get_value()) > 0 else m.next_to(line_pm_y, LEFT, buff=0.1)
        )


        self.play(
            Create(dot_p), Create(radius_op),
            Create(angle_arc), Write(theta_label),
            Create(line_pm_y), Write(y_coord_label)
        )
        self.wait(1)

        # 动画展示 theta 变化
        self.play(theta_tracker.animate.set_value(PI / 3), run_time=2)
        self.wait(0.5)
        self.play(theta_tracker.animate.set_value(PI * 2/3), run_time=2)
        self.wait(0.5)
        self.play(theta_tracker.animate.set_value(PI), run_time=2) # 重置到初始位置,为下一步准备
        self.wait(1)

        # 4. 创建正弦函数图像坐标系
        graph_axes = Axes(
            x_range=[0, 2 * PI + 0.1, PI / 2], # x 轴范围和步长
            y_range=[-1.5, 1.5, 0.5],        # y 轴范围和步长
            x_length=6,                      # x 轴在屏幕上的长度
            y_length=4,                      # y 轴在屏幕上的长度
            axis_config={"color": GRAY, "include_tip": True},
            x_axis_config={"numbers_to_include": np.arange(0, 2 * PI + 0.01, PI/2)}, # x轴刻度
            y_axis_config={"numbers_to_include": np.arange(-1, 1.01, 0.5)},       # y轴刻度
        ).to_edge(RIGHT, buff=1)

        # x轴标签使用MathTex以显示pi
        x_labels_val = [0, PI/2, PI, 3*PI/2, 2*PI]
        x_labels_tex = {
            0: MathTex("0", font_size=20, color=DARK_GRAY),
            PI/2: MathTex(r"\frac{\pi}{2}", font_size=20, color=DARK_GRAY),
            PI: MathTex(r"\pi", font_size=20, color=DARK_GRAY),
            3*PI/2: MathTex(r"\frac{3\pi}{2}", font_size=20, color=DARK_GRAY),
            2*PI: MathTex(r"2\pi", font_size=20, color=DARK_GRAY),
        }
        graph_axes.add_coordinates(x_labels_val, x_labels_tex) # 添加特定坐标标签
        graph_axes.add_coordinates(y_axis_config={"numbers_to_include": [-1, -0.5, 0, 0.5, 1]}) # 添加y轴标准数字标签

        graph_title = MathTex(r"y = \sin(\theta)", font_size=28, color=GREEN_E)
        graph_title.next_to(graph_axes, UP, buff=0.2)

        self.play(Create(graph_axes), Write(graph_title))
        self.wait(1)

        # 5. 动态绘制正弦函数图像
        # 重置theta_tracker以便从头开始绘制
        theta_tracker.set_value(0)

        # 正弦函数图像路径
        sine_graph = graph_axes.plot(lambda x: np.sin(x), x_range=[0, 0.001], color=GREEN_E, stroke_width=2) # 初始很短

        # 图像上的动态点
        moving_dot_on_graph = Dot(color=GREEN_E, radius=0.08)
        moving_dot_on_graph.move_to(graph_axes.c2p(0, np.sin(0))) # 初始位置

        # 从单位圆 P 点到图像 y 值的水平辅助线
        horizontal_line = DashedLine(
            dot_p.get_center(),
            moving_dot_on_graph.get_center(),
            color=PINK, stroke_width=1.5
        )

        # 从图像x轴到图像上点的垂直辅助线
        vertical_line_on_graph = DashedLine(
            graph_axes.c2p(theta_tracker.get_value(),0), # x轴上的theta点
            moving_dot_on_graph.get_center(),
            color=PINK, stroke_width=1.5
        )

        # 更新器
        def update_sine_graph(graph):
            current_theta = theta_tracker.get_value()
            new_graph = graph_axes.plot(lambda x: np.sin(x), x_range=[0, current_theta], color=GREEN_E, stroke_width=2)
            graph.become(new_graph)

        def update_moving_dot_on_graph(dot):
            current_theta = theta_tracker.get_value()
            dot.move_to(graph_axes.c2p(current_theta, np.sin(current_theta)))

        def update_horizontal_line(line):
            line.put_start_and_end_on(dot_p.get_center(), moving_dot_on_graph.get_center())

        def update_vertical_line_on_graph(line):
            line.put_start_and_end_on(graph_axes.c2p(theta_tracker.get_value(),0), moving_dot_on_graph.get_center())


        sine_graph.add_updater(update_sine_graph)
        moving_dot_on_graph.add_updater(update_moving_dot_on_graph)
        horizontal_line.add_updater(update_horizontal_line)
        vertical_line_on_graph.add_updater(update_vertical_line_on_graph)

        # 显示这些动态元素
        self.play(
            Create(sine_graph),
            Create(moving_dot_on_graph),
            Create(horizontal_line),
            Create(vertical_line_on_graph),
        )

        # 动画:theta 从 0 变化到 2*PI,同时绘制图像
        self.play(theta_tracker.animate.set_value(2 * PI), run_time=8, rate_func=linear)
        self.wait(1)

        # 移除updater,固定图像
        sine_graph.remove_updater(update_sine_graph)
        moving_dot_on_graph.remove_updater(update_moving_dot_on_graph)
        horizontal_line.remove_updater(update_horizontal_line)
        vertical_line_on_graph.remove_updater(update_vertical_line_on_graph)
        # 可以选择性地 FadeOut 辅助线
        self.play(FadeOut(horizontal_line), FadeOut(vertical_line_on_graph), FadeOut(moving_dot_on_graph))
        self.wait(0.5)

        # 6. 标注周期
        period_brace = Brace(
            Line(graph_axes.c2p(0, -1.2), graph_axes.c2p(2 * PI, -1.2)), # 在图像下方创建一条线用于brace
            direction=DOWN, color=PURPLE_D
        )
        period_text = period_brace.get_text(r"周期 (Period) $T = 2\pi$", font_size=24, color=PURPLE_D)

        self.play(Create(period_brace), Write(period_text))
        self.wait(2)

        # 结束语
        final_text = Text("正弦函数之旅", font_size=36, color=BLUE_E)
        final_text.to_edge(DOWN, buff=1)
        self.play(Write(final_text))
        self.wait(3)

        # 清场
        self.play(
            FadeOut(unit_circle_axes), FadeOut(circle), FadeOut(circle_label),
            FadeOut(dot_p), FadeOut(radius_op), FadeOut(angle_arc), FadeOut(theta_label),
            FadeOut(line_pm_y), FadeOut(y_coord_label),
            FadeOut(graph_axes), FadeOut(graph_title), FadeOut(sine_graph),
            FadeOut(period_brace), FadeOut(period_text),
            FadeOut(final_text)
        )
        self.wait(1)

将上述代码保存为 sine_introduction.py 文件。

打开终端或命令提示符。

导航到保存文件的目录。

运行命令:manim -pql sine_introduction.py SineFunctionIntroduction

-p 表示预览 (preview) 视频。

-q 表示质量 (quality),l 代表低质量 (low),渲染更快。你可以用 m (medium), h (high), k (4k) 替换。

SineFunctionIntroduction 是场景类的名称。

网友回复

我知道答案,我要回答