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 是场景类的名称。
网友回复
为啥所有的照片分辨率提升工具都会修改照片上的图案细节?
js如何在浏览器中将webm视频的声音分离为单独音频?
微信小程序如何播放第三方域名url的mp4视频?
ai多模态大模型能实时识别视频中的手语为文字吗?
如何远程调试别人的chrome浏览器获取调试信息?
为啥js打开新网页window.open设置窗口宽高无效?
浏览器中js的navigator.mediaDevices.getDisplayMedia屏幕录像无法录制SpeechSynthesisUtterance产生的说话声音?
js中mediaRecorder如何录制window.speechSynthesis声音音频并下载?
python如何直接获取抖音短视频的音频文件url?
js在浏览器中如何使用MediaStream与MediaRecorder实现声音音频多轨道混流?