Python Manim 绘制动态函数切线

1. 前言

  关于 Manim 的安装,github 上有多种版本,使用起来各不相同(运行方式、API等等)踩了一些坑,最终用的 Manim 社区版,里面有详细的教程和API介绍。

2. 绘制动态函数切线

在这里插入图片描述

from manim import *


def func(x):
    return np.sin(x*PI)


class PlotTangent(MovingCameraScene):
    def construct(self):
        self.camera.frame.save_state()
        
        # 标题函数公式, 放置在左上角
        title = MathTex("f(x) = sin(x)")
        title.to_corner(UL)
        
        # 绘制坐标系
        ax = Axes(x_range = [-2.5, 2.5, 0.5], 
                  y_range = [-1.5, 1.5], 
                  x_length = 10, 
                  axis_config={
    
    "include_numbers": True}, 
                 )
        graph = ax.get_graph(func, color=BLUE)  # 函数曲线
        x_space = np.linspace(-1.5, 1.5, 200)  # 设定切线的 x 取值范围
        
        '''
        绘制切线, get_secant_slope_group 是用来绘制割线的, 这里 dx 设的足够小看起来就像切线
        这里的 slopes 里面有3条线, 第三条是需要的切线, 另外两条是 dx 和 dy
        '''
        slopes = ax.get_secant_slope_group(
            x = x_space[0], 
            graph = graph,
            dx = 0.0001, 
            secant_line_length = 5,
            secant_line_color = RED_D,
        )
        dot = Dot(point=[ax.coords_to_point(x_space[0], func(x_space[0]))])  # 初始切点
		
		# 切点处标记的信息, 用 VGroup() 把公式和动态斜率数值绑定成一组
		text, decimal = label = VGroup(
            MathTex("f'(x) = "),
            DecimalNumber(
                0,
                num_decimal_places=3,
                include_sign=True
            )
        )
        label.arrange(RIGHT)		# 没有这行的话公式和数值会重合在一起
        label.scale(0.6)			# 比例缩放
        label.set_color(YELLOW_C)	# 颜色
        
        label.add_updater(lambda d: d.next_to(dot, UL))		# label 在 dot 的左上角
        # decimal 的数值设定为 dot 点的斜率
        decimal.add_updater(lambda d: d.set_value(
            np.cos(ax.point_to_coords(dot.get_center())[0] * PI)
        ))
        
        self.add(title, ax, graph, dot)  # 标题、坐标系、函数曲线、初始切点
        
        # 切点的xy轴垂线
        h_line = always_redraw(lambda: ax.get_lines_to_point(dot.get_center()))

        self.play(Create(h_line), Create(slopes[2]), Write(label))  # 切点垂线、切线、标签
        
        self.play(self.camera.frame.animate.scale(0.8).move_to(dot))  # 镜头跟踪切点位置
        def update_curve(mob):
            mob.move_to(dot.get_center())
        
        '''
        这里用循环来变换切点坐标和切线, 感觉不是最优的方法
        其实点可以通过 dot.add_updater(lambda x: x.move_to()) 来动态更新
        但是不知道切线怎么用 add_updater 的方式来变换, 只好先用循环实现
        '''
        for x in x_space[1:]:
            self.camera.frame.add_updater(update_curve)
            
            slopes_d = ax.get_secant_slope_group(
                x = x, 
                graph = graph, 
                dx = 0.0001, 
                secant_line_length = 5, 
                secant_line_color = RED_D,
            )
            dot_d = Dot(point=[ax.coords_to_point(x, func(x))])
            
            self.play(
                ReplacementTransform(slopes[2], slopes_d[2]), 
                ReplacementTransform(dot, dot_d), 
                run_time=0.015
            )
            
            slopes = slopes_d
            dot = dot_d
            
        self.camera.frame.remove_updater(update_curve)
        self.play(Restore(self.camera.frame))
        self.wait()
        
# jupyter notebook 的魔法函数运行方式
%manim -ql -v WARNING -i PlotTangent

猜你喜欢

转载自blog.csdn.net/weixin_43605641/article/details/120601851