from big_ol_pile_of_manim_imports import *
from old_projects.eoc.chapter2 import DISTANCE_COLOR, TIME_COLOR, \
    VELOCITY_COLOR, Car, MoveCar

OUTPUT_COLOR = DISTANCE_COLOR
INPUT_COLOR = TIME_COLOR
DERIVATIVE_COLOR = VELOCITY_COLOR

class Chapter3OpeningQuote(OpeningQuote):
    CONFIG = {
        "quote" : [
            "You know, for a mathematician, he did not have \\\\ enough",
            "imagination.", 
            "But he has become a poet and \\\\ now he is fine.",
        ],
        "highlighted_quote_terms" : {
            "imagination." : BLUE,
        },
        "author" : "David Hilbert"
    }

class PoseAbstractDerivative(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            Given $f(x) = x^2 \\sin(x)$, \\\\
            compute $\\frac{df}{dx}(x)$
        """)
        content_copy = self.teacher.bubble.content.copy()
        self.change_student_modes("sad", "confused", "erm")
        self.wait()
        self.student_says(
            "Why?", target_mode = "sassy",
            added_anims = [
                content_copy.scale, 0.8,
                content_copy.to_corner, UP+LEFT
            ]
        )
        self.play(self.teacher.change_mode, "pondering")
        self.wait(2)

class ContrastAbstractAndConcrete(Scene):
    def construct(self):
        v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS)
        l_title = TextMobject("Abstract functions")
        l_title.shift(FRAME_X_RADIUS*LEFT/2)
        l_title.to_edge(UP)
        r_title = TextMobject("Applications")
        r_title.shift(FRAME_X_RADIUS*RIGHT/2)
        r_title.to_edge(UP)
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        h_line.shift((r_title.get_bottom()[1]-MED_SMALL_BUFF)*UP)

        functions = VGroup(*list(map(TexMobject, [
            "f(x) = 2x^2 - x^3",
            "f(x) = \\sin(x)",
            "f(x) = e^x",
            "\\v_dots"
        ])))
        functions.arrange(
            DOWN, 
            aligned_edge = LEFT,
            buff = LARGE_BUFF
        )
        functions.shift(FRAME_X_RADIUS*LEFT/2)
        functions[-1].shift(MED_LARGE_BUFF*RIGHT)

        self.add(l_title, r_title)
        self.play(*list(map(ShowCreation, [h_line, v_line])))
        self.play(Write(functions))
        self.wait()
        anims = [
            method(func_mob)
            for func_mob, method in zip(functions, [
                self.get_car_anim,
                self.get_spring_anim,
                self.get_population_anim,
            ])
        ]
        for anim in anims:
            self.play(FadeIn(anim.mobject))
            self.play(anim)
            self.play(FadeOut(anim.mobject))


    def get_car_anim(self, alignement_mob):
        car = Car()
        point = 2*RIGHT + alignement_mob.get_bottom()[1]*UP
        target_point = point + 5*RIGHT
        car.move_to(point)
        return MoveCar(
            car, target_point, 
            run_time = 5,
        )

    def get_spring_anim(self, alignement_mob):
        compact_spring, extended_spring = [
            ParametricFunction(
                lambda t : (t/denom)*RIGHT+np.sin(t)*UP+np.cos(t)*OUT,
                t_max = 12*np.pi,
            )
            for denom in (12.0, 4.0)
        ]
        for spring in compact_spring, extended_spring:
            spring.scale(0.5)
            spring.rotate(np.pi/6, UP)
            spring.set_color(GREY)
            spring.next_to(ORIGIN, RIGHT)
            spring.shift(
                alignement_mob.get_center()[1]*UP + SMALL_BUFF*RIGHT \
                -spring.points[0]
            )
            weight = Square(
                side_length = 0.5,
                stroke_width = 0, 
                fill_color = LIGHT_GREY,
                fill_opacity = 1,
            )
            weight.move_to(spring.points[-1])
            spring.add(weight)

        return Transform(
            compact_spring, extended_spring, 
            rate_func = lambda t : 1+np.sin(6*np.pi*t),
            run_time = 5
        )

    def get_population_anim(self, alignement_mob):
        colors = color_gradient([BLUE_B, BLUE_E], 12)
        pis = VGroup(*[
            Randolph(
                mode = "happy",
                color = random.choice(colors)
            ).shift(
                4*x*RIGHT + 4*y*UP + \
                2*random.random()*RIGHT + \
                2*random.random()*UP
            )
            for x in range(20)
            for y in range(10)
        ])
        pis.set_height(3)
        pis.center()
        pis.to_edge(DOWN, buff = SMALL_BUFF)
        pis.shift(FRAME_X_RADIUS*RIGHT/2.)

        anims = []
        for index, pi in enumerate(pis):
            if index < 2:
                anims.append(FadeIn(pi))
                continue
            mom_index, dad_index = random.choice(
                list(it.combinations(list(range(index)), 2))
            )
            pi.parents = VGroup(pis[mom_index], pis[dad_index]).copy()
            pi.parents.set_fill(opacity = 0)
        exp = 1
        while 2**exp < len(pis):
            low_index = 2**exp
            high_index = min(2**(exp+1), len(pis))
            these_pis = pis[low_index:high_index]
            anims.append(Transform(
                VGroup(*[pi.parents for pi in these_pis]),
                VGroup(*[VGroup(pi, pi.copy()) for pi in these_pis]),
                lag_ratio = 0.5,
                run_time = 2,
            ))
            exp += 1

        return Succession(*anims, rate_func=linear)

class ApplicationNames(Scene):
    def construct(self):
        for name in "Velocity", "Oscillation", "Population growth":
            mob = TextMobject(name)
            mob.scale(2)
            self.play(Write(mob))
            self.wait(2)
            self.play(FadeOut(mob))

class ListOfRules(PiCreatureScene):
    CONFIG = {
        "use_morty" : False,
    }
    def construct(self):
        rules = VGroup(*list(map(TexMobject, [
            "\\frac{d}{dx} x^n = nx^{n-1}",
            "\\frac{d}{dx} \\sin(x) = \\cos(x)",
            "\\frac{d}{dx} \\cos(x) = -\\sin(x)",
            "\\frac{d}{dx} a^x = \\ln(a) a^x",
            "\\vdots"
        ])))
        rules.arrange(
            DOWN, buff = MED_LARGE_BUFF,
            aligned_edge = LEFT,
        )
        rules[-1].shift(MED_LARGE_BUFF*RIGHT)
        rules.set_height(FRAME_HEIGHT-1)
        rules.next_to(self.pi_creature, RIGHT)
        rules.to_edge(DOWN)

        self.play(
            Write(rules),
            self.pi_creature.change_mode, "pleading",
        )
        self.change_mode("tired")
        self.wait()

class DerivativeOfXSquaredAsGraph(GraphScene, ZoomedScene, PiCreatureScene):
    CONFIG = {
        "start_x" : 2,
        "big_x" : 3,
        "dx" : 0.1,
        "x_min" : -9,
        "x_labeled_nums" : list(range(-8, 0, 2)) + list(range(2, 10, 2)),
        "y_labeled_nums" : list(range(2, 12, 2)),
        "little_rect_nudge" : 0.5*(1.5*UP+RIGHT),
        "graph_origin" : 2.5*DOWN + LEFT,
        "zoomed_canvas_corner" : UP+LEFT,
        "zoomed_canvas_frame_shape" : (4, 4),
    }
    def construct(self):
        self.draw_graph()
        self.ask_about_df_dx()
        self.show_differing_slopes()
        self.mention_alternate_view()

    def draw_graph(self):
        self.setup_axes(animate = True)
        graph = self.get_graph(lambda x : x**2)
        label = self.get_graph_label(
            graph, "f(x) = x^2",
        )
        self.play(ShowCreation(graph))
        self.play(Write(label))
        self.wait()
        self.graph = graph

    def ask_about_df_dx(self):
        ss_group = self.get_secant_slope_group(
            self.start_x, self.graph,
            dx = self.dx,
            dx_label = "dx",
            df_label = "df",
        )
        secant_line = ss_group.secant_line
        ss_group.remove(secant_line)

        v_line, nudged_v_line = [
            self.get_vertical_line_to_graph(
                x, self.graph,
                line_class = DashedLine,
                color = RED,
                dash_length = 0.025
            )
            for x in (self.start_x, self.start_x+self.dx)
        ]

        df_dx = TexMobject("\\frac{df}{dx} ?")
        VGroup(*df_dx[:2]).set_color(ss_group.df_line.get_color())
        VGroup(*df_dx[3:5]).set_color(ss_group.dx_line.get_color())
        df_dx.next_to(
            self.input_to_graph_point(self.start_x, self.graph),
            DOWN+RIGHT,
            buff = MED_SMALL_BUFF
        )

        derivative_q = TextMobject("Derivative?")
        derivative_q.next_to(self.pi_creature.get_corner(UP+LEFT), UP)


        self.play(
            Write(derivative_q, run_time = 1),
            self.pi_creature.change_mode, "speaking"
        )
        self.wait()
        self.play(
            FadeOut(derivative_q),
            self.pi_creature.change_mode, "plain"
        )
        self.play(ShowCreation(v_line))
        self.wait()
        self.play(Transform(v_line.copy(), nudged_v_line))
        self.remove(self.get_mobjects_from_last_animation()[0])
        self.add(nudged_v_line)
        self.wait()
        self.activate_zooming()
        self.little_rectangle.replace(self.big_rectangle)
        self.play(
            FadeIn(self.little_rectangle),
            FadeIn(self.big_rectangle),
        )
        self.play(
            ApplyFunction(
                lambda r : self.position_little_rectangle(r, ss_group),
                self.little_rectangle
            ),
            self.pi_creature.change_mode, "pondering",
            self.pi_creature.look_at, ss_group
        )
        self.play(
            ShowCreation(ss_group.dx_line),
            Write(ss_group.dx_label),
        )
        self.wait()
        self.play(
            ShowCreation(ss_group.df_line),
            Write(ss_group.df_label),
        )
        self.wait()
        self.play(Write(df_dx))
        self.wait()
        self.play(*list(map(FadeOut, [
            v_line, nudged_v_line,
        ])))
        self.ss_group = ss_group

    def position_little_rectangle(self, rect, ss_group):
        rect.set_width(3*self.dx)
        rect.move_to(
            ss_group.dx_line.get_left()
        )
        rect.shift(
            self.dx*self.little_rect_nudge
        )
        return rect

    def show_differing_slopes(self):
        ss_group = self.ss_group
        def rect_update(rect):
            self.position_little_rectangle(rect, ss_group)

        self.play(
            ShowCreation(ss_group.secant_line),
            self.pi_creature.change_mode, "thinking"
        )
        ss_group.add(ss_group.secant_line)
        self.wait()
        for target_x in self.big_x, -self.dx/2, 1, 2:
            self.animate_secant_slope_group_change(
                ss_group, target_x = target_x,
                added_anims = [
                    UpdateFromFunc(self.little_rectangle, rect_update)
                ]
            )
            self.wait()

    def mention_alternate_view(self):
        self.remove(self.pi_creature)
        everything = VGroup(*self.get_mobjects())
        self.add(self.pi_creature)
        self.disactivate_zooming()
        self.play(
            ApplyMethod(
                everything.shift, FRAME_WIDTH*LEFT,
                rate_func = lambda t : running_start(t, -0.1)
            ),
            self.pi_creature.change_mode, "happy"
        )
        self.say("Let's try \\\\ another view.", target_mode = "speaking")
        self.wait(2)

class NudgeSideLengthOfSquare(PiCreatureScene):
    CONFIG = {
        "square_width" : 3,
        "alt_square_width" : 5,
        "dx" : 0.25,
        "alt_dx" : 0.01,
        "square_color" : GREEN,
        "square_fill_opacity" : 0.75,
        "three_color" : GREEN,
        "dx_color" : BLUE_B,
        "is_recursing_on_dx" : False,
        "is_recursing_on_square_width" : False,
    }
    def construct(self):
        ApplyMethod(self.pi_creature.change_mode, "speaking").update(1)
        self.add_function_label()
        self.introduce_square()
        self.increase_area()
        self.write_df_equation()
        self.set_color_shapes()
        self.examine_thin_rectangles()
        self.examine_tiny_square()
        self.show_smaller_dx()
        self.rule_of_thumb()
        self.write_out_derivative()

    def add_function_label(self):
        label = TexMobject("f(x) = x^2")
        label.next_to(ORIGIN, RIGHT, buff = (self.square_width-3)/2.)
        label.to_edge(UP)
        self.add(label)
        self.function_label = label

    def introduce_square(self):
        square = Square(
            side_length = self.square_width,
            stroke_width = 0,
            fill_opacity = self.square_fill_opacity,
            fill_color = self.square_color,
        )
        square.to_corner(UP+LEFT, buff = LARGE_BUFF)
        x_squared = TexMobject("x^2")
        x_squared.move_to(square)

        braces = VGroup()
        for vect in RIGHT, DOWN:
            brace = Brace(square, vect)
            text = brace.get_text("$x$")
            brace.add(text)
            braces.add(brace)

        self.play(
            DrawBorderThenFill(square),
            self.pi_creature.change_mode, "plain"
        )
        self.play(*list(map(GrowFromCenter, braces)))
        self.play(Write(x_squared))
        self.change_mode("pondering")
        self.wait()

        self.square = square
        self.side_braces = braces

    def increase_area(self):
        color_kwargs = {
            "fill_color" : YELLOW,
            "fill_opacity" : self.square_fill_opacity,
            "stroke_width" : 0,
        }
        right_rect = Rectangle(
            width = self.dx,
            height = self.square_width,
            **color_kwargs
        )
        bottom_rect = right_rect.copy().rotate(-np.pi/2)
        right_rect.next_to(self.square, RIGHT, buff = 0)
        bottom_rect.next_to(self.square, DOWN, buff = 0)
        corner_square = Square(
            side_length = self.dx,
            **color_kwargs
        )
        corner_square.next_to(self.square, DOWN+RIGHT, buff = 0)

        right_line = Line(
            self.square.get_corner(UP+RIGHT),
            self.square.get_corner(DOWN+RIGHT),
            stroke_width = 0
        )
        bottom_line = Line(
            self.square.get_corner(DOWN+RIGHT),
            self.square.get_corner(DOWN+LEFT),
            stroke_width = 0
        )
        corner_point = VectorizedPoint(
            self.square.get_corner(DOWN+RIGHT)
        )

        little_braces = VGroup()
        for vect in RIGHT, DOWN:
            brace = Brace(
                corner_square, vect, 
                buff = SMALL_BUFF,
            )
            text = brace.get_text("$dx$", buff = SMALL_BUFF)
            text.set_color(self.dx_color)
            brace.add(text)
            little_braces.add(brace)

        right_brace, bottom_brace = self.side_braces
        self.play(
            Transform(right_line, right_rect),
            Transform(bottom_line, bottom_rect),
            Transform(corner_point, corner_square),
            right_brace.next_to, right_rect, RIGHT, SMALL_BUFF,
            bottom_brace.next_to, bottom_rect, DOWN, SMALL_BUFF,
        )
        self.remove(corner_point, bottom_line, right_line)
        self.add(corner_square, bottom_rect, right_rect)
        self.play(*list(map(GrowFromCenter, little_braces)))
        self.wait()
        self.play(*it.chain(*[
            [mob.shift, vect*SMALL_BUFF]
            for mob, vect in [
                (right_rect, RIGHT),
                (bottom_rect, DOWN),
                (corner_square, DOWN+RIGHT),
                (right_brace, RIGHT),
                (bottom_brace, DOWN),
                (little_braces, DOWN+RIGHT)
            ]
        ]))
        self.change_mode("thinking")
        self.wait()
        self.right_rect = right_rect
        self.bottom_rect = bottom_rect
        self.corner_square = corner_square
        self.little_braces = little_braces

    def write_df_equation(self):
        right_rect = self.right_rect
        bottom_rect = self.bottom_rect
        corner_square = self.corner_square

        df_equation = VGroup(
            TexMobject("df").set_color(right_rect.get_color()),
            TexMobject("="),
            right_rect.copy(),
            TextMobject("+"),
            right_rect.copy(),
            TexMobject("+"),
            corner_square.copy()
        )
        df_equation.arrange()
        df_equation.next_to(
            self.function_label, DOWN, 
            aligned_edge = LEFT,
            buff = SMALL_BUFF
        )
        df, equals, r1, plus1, r2, plus2, s = df_equation

        pairs = [
            (df, self.function_label[0]),
            (r1, right_rect), 
            (r2, bottom_rect), 
            (s, corner_square),
        ]
        for mover, origin in pairs:
            mover.save_state()
            Transform(mover, origin).update(1)
        self.play(df.restore)
        self.wait()
        self.play(
            *[
                mob.restore
                for mob in (r1, r2, s)
            ]+[
                Write(symbol)
                for symbol in (equals, plus1, plus2)
            ], 
            run_time = 2
        )
        self.change_mode("happy")
        self.wait()

        self.df_equation = df_equation

    def set_color_shapes(self):
        df, equals, r1, plus1, r2, plus2, s = self.df_equation

        tups = [
            (self.right_rect, self.bottom_rect, r1, r2),
            (self.corner_square, s)
        ]
        for tup in tups:
            self.play(
                *it.chain(*[
                    [m.scale_in_place, 1.2, m.set_color, RED]
                    for m in tup
                ]), 
                rate_func = there_and_back
            )
            self.wait()

    def examine_thin_rectangles(self):
        df, equals, r1, plus1, r2, plus2, s = self.df_equation

        rects = VGroup(r1, r2)
        thin_rect_brace = Brace(rects, DOWN)
        text = thin_rect_brace.get_text("$2x \\, dx$")
        VGroup(*text[-2:]).set_color(self.dx_color)
        text.save_state()
        alt_text = thin_rect_brace.get_text("$2(3)(0.01)$")
        alt_text[2].set_color(self.three_color)
        VGroup(*alt_text[-5:-1]).set_color(self.dx_color)

        example_value = TexMobject("=0.06")
        example_value.next_to(alt_text, DOWN)

        self.play(GrowFromCenter(thin_rect_brace))
        self.play(
            Write(text),
            self.pi_creature.change_mode, "pondering"
        )
        self.wait()

        xs = VGroup(*[
            brace[-1] 
            for brace in self.side_braces
        ])
        dxs = VGroup(*[
            brace[-1]
            for brace in self.little_braces
        ])
        for group, tex, color in (xs, "3", self.three_color), (dxs, "0.01", self.dx_color):
            group.save_state()            
            group.generate_target()            
            for submob in group.target:
                number = TexMobject(tex)
                number.set_color(color)
                number.move_to(submob, LEFT)
                Transform(submob, number).update(1)
        self.play(MoveToTarget(xs))
        self.play(MoveToTarget(dxs))
        self.wait()
        self.play(Transform(text, alt_text))
        self.wait()
        self.play(Write(example_value))
        self.wait()
        self.play(
            FadeOut(example_value),
            *[
                mob.restore
                for mob in (xs, dxs, text)
            ]
        )
        self.remove(text)
        text.restore()
        self.add(text)

        self.wait()
        self.dxs = dxs
        self.thin_rect_brace = thin_rect_brace
        self.thin_rect_area = text        

    def examine_tiny_square(self):
        text = TexMobject("dx^2")
        VGroup(*text[:2]).set_color(self.dx_color)
        text.next_to(self.df_equation[-1], UP)
        text.save_state()
        alt_text = TextMobject("0.0001")
        alt_text.move_to(text)

        self.play(Write(text))
        self.change_mode("surprised")
        self.wait()
        self.play(
            MoveToTarget(self.dxs),
            self.pi_creature.change_mode, "plain"
        )
        for submob in self.dxs.target:
            number = TexMobject("0.01")
            number.set_color(self.dx_color)
            number.move_to(submob, LEFT)
            Transform(submob, number).update(1)
        self.play(MoveToTarget(self.dxs))
        self.play(
            Transform(text, alt_text),
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.wait(2)
        self.play(*[
            mob.restore
            for mob in (self.dxs, text)
        ] + [
            self.pi_creature.change_mode, "erm"
        ])
        self.dx_squared = text

    def show_smaller_dx(self):
        self.mobjects_at_start_of_show_smaller_dx = [
            mob.copy() for mob in self.get_mobjects()
        ]
        if self.is_recursing_on_dx:
            return

        alt_scene = self.__class__(
            skip_animations = True,
            dx = self.alt_dx,
            is_recursing_on_dx = True
        )
        for mob in self.get_mobjects():
            mob.save_state()
        self.play(*[
            Transform(*pair)
            for pair in zip(
                self.get_mobjects(),
                alt_scene.mobjects_at_start_of_show_smaller_dx,
            )
        ])
        self.wait()
        self.play(*[
            mob.restore
            for mob in self.get_mobjects()
        ])
        self.change_mode("happy")
        self.wait()

    def rule_of_thumb(self):
        circle = Circle(color = RED)
        dx_squared_group = VGroup(self.dx_squared, self.df_equation[-1])
        circle.replace(dx_squared_group, stretch = True)
        dx_squared_group.add(self.df_equation[-2])
        circle.scale_in_place(1.5)
        safe_to_ignore = TextMobject("Safe to ignore")
        safe_to_ignore.next_to(circle, DOWN, aligned_edge = LEFT)
        safe_to_ignore.set_color(circle.get_color())

        self.play(ShowCreation(circle))
        self.play(
            Write(safe_to_ignore, run_time = 2),
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.play(
            FadeOut(circle),
            FadeOut(safe_to_ignore),
            dx_squared_group.fade, 0.5,
            dx_squared_group.to_corner, UP+RIGHT,
            self.pi_creature.change_mode, "plain"
        )
        self.wait()

    def write_out_derivative(self):
        df, equals, r1, plus1, r2, plus2, s = self.df_equation
        frac_line = TexMobject("-")
        frac_line.stretch_to_fit_width(df.get_width())
        frac_line.move_to(df)
        dx = VGroup(*self.thin_rect_area[-2:]) 
        x = self.thin_rect_area[1]

        self.play(
            Transform(r1, self.right_rect),
            Transform(r2, self.bottom_rect),
            FadeOut(plus1),
            FadeOut(self.thin_rect_brace)
        )
        self.play(
            self.thin_rect_area.next_to, VGroup(df, equals),
            RIGHT, MED_SMALL_BUFF, UP,
            self.pi_creature.change_mode, "thinking"
        )
        self.wait(2)
        self.play(
            ApplyMethod(df.next_to, frac_line, UP, SMALL_BUFF),
            ApplyMethod(dx.next_to, frac_line, DOWN, SMALL_BUFF),
            Write(frac_line),            
            path_arc = -np.pi
        )
        self.wait()

        brace_xs = [
            brace[-1]
            for brace in self.side_braces
        ]
        xs = list(brace_xs) + [x]
        for x_mob in xs:
            number = TexMobject("(%d)"%self.square_width)
            number.move_to(x_mob, LEFT)
            number.shift(
                (x_mob.get_bottom()[1] - number[1].get_bottom()[1])*UP
            )
            x_mob.save_state()
            x_mob.target = number
        self.play(*list(map(MoveToTarget, xs)))
        self.wait(2)

        #Recursively transform to what would have happened
        #with a wider square width
        self.mobjects_at_end_of_write_out_derivative = self.get_mobjects()
        if self.is_recursing_on_square_width or self.is_recursing_on_dx:
            return
        alt_scene = self.__class__(
            skip_animations = True,
            square_width = self.alt_square_width,
            is_recursing_on_square_width = True,
        )
        self.play(*[
            Transform(*pair)
            for pair in zip(
                self.mobjects_at_end_of_write_out_derivative,
                alt_scene.mobjects_at_end_of_write_out_derivative
            )
        ])
        self.change_mode("happy")
        self.wait(2)

class ChangeInAreaOverChangeInX(Scene):
    def construct(self):
        fractions = []
        for pair in ("Change in area", "Change in $x$"), ("$d(x^2)$", "$dx$"):
            top, bottom = list(map(TextMobject, pair))
            top.set_color(YELLOW)
            bottom.set_color(BLUE_B)
            frac_line = TexMobject("-")
            frac_line.stretch_to_fit_width(top.get_width())
            top.next_to(frac_line, UP, SMALL_BUFF)
            bottom.next_to(frac_line, DOWN, SMALL_BUFF)
            fractions.append(VGroup(
                top, frac_line, bottom
            ))
        words, symbols = fractions

        self.play(Write(words[0], run_time = 1))
        self.play(*list(map(Write, words[1:])), run_time = 1)
        self.wait()
        self.play(Transform(words, symbols))
        self.wait()

class NudgeSideLengthOfCube(Scene):
    CONFIG = {
        "x_color" : BLUE,
        "dx_color" : GREEN,
        "df_color" : YELLOW,
        "use_morty" : False,
        "x" : 3,
        "dx" : 0.2,
        "alt_dx" : 0.02,
        "offset_vect" : OUT,
        "pose_angle" : np.pi/12,
        "pose_axis" : UP+RIGHT,
        "small_piece_scaling_factor" : 0.7,
        "allow_recursion" : True,
    }
    def construct(self):
        self.states = dict()
        if self.allow_recursion:
            self.alt_scene = self.__class__(
                skip_animations = True,
                allow_recursion = False,
                dx = self.alt_dx,
            )

        self.add_title()
        self.introduce_cube()
        self.write_df_equation()
        self.write_derivative()

    def add_title(self):
        title = TexMobject("f(x) = x^3")
        title.shift(FRAME_X_RADIUS*LEFT/2)
        title.to_edge(UP)
        self.play(Write(title))
        self.wait()

    def introduce_cube(self):
        cube = self.get_cube()
        cube.to_edge(LEFT, buff = 2*LARGE_BUFF)
        cube.shift(DOWN)

        dv_pieces = self.get_dv_pices(cube)
        original_dx = self.dx
        self.dx = 0
        alt_dv_pieces = self.get_dv_pices(cube)
        self.dx = original_dx
        alt_dv_pieces.set_fill(opacity = 0)

        x_brace = Brace(cube, LEFT, buff = SMALL_BUFF)
        dx_brace = Brace(
            dv_pieces[1], LEFT, buff = SMALL_BUFF,
        )
        dx_brace.stretch_in_place(1.5, 1)
        for brace, tex in (x_brace, "x"), (dx_brace, "dx"):
            brace.scale_in_place(0.95)
            brace.rotate_in_place(-np.pi/96)
            brace.shift(0.3*(UP+LEFT))
            brace.add(brace.get_text("$%s$"%tex))


        cube_group = VGroup(cube, dv_pieces, alt_dv_pieces)
        self.pose_3d_mobject(cube_group)

        self.play(DrawBorderThenFill(cube))
        self.play(GrowFromCenter(x_brace))
        self.wait()
        self.play(Transform(alt_dv_pieces, dv_pieces))
        self.remove(alt_dv_pieces)
        self.add(dv_pieces)
        self.play(GrowFromCenter(dx_brace))
        self.wait()
        for piece in dv_pieces:
            piece.on_cube_state = piece.copy()
        self.play(*[
            ApplyMethod(
                piece.shift, 
                0.5*(piece.get_center()-cube.get_center())
            )
            for piece in dv_pieces
        ]+[
            ApplyMethod(dx_brace.shift, 0.7*UP)
        ])
        self.wait()

        self.cube = cube
        self.dx_brace = dx_brace
        self.faces, self.bars, self.corner_cube = [
            VGroup(*[
                piece 
                for piece in dv_pieces
                if piece.type == target_type
            ])
            for target_type in ("face", "bar", "corner_cube")
        ]

    def write_df_equation(self):
        df_equation = VGroup(
            TexMobject("df"),
            TexMobject("="),
            self.organize_faces(self.faces.copy()),
            TexMobject("+"),
            self.organize_bars(self.bars.copy()),
            TexMobject("+"),
            self.corner_cube.copy()
        )
        df, equals, faces, plus1, bars, plus2, corner_cube = df_equation
        df.set_color(self.df_color)
        for three_d_mob in faces, bars, corner_cube:
            three_d_mob.scale(self.small_piece_scaling_factor)
            # self.pose_3d_mobject(three_d_mob)
        faces.set_fill(opacity = 0.3)
        df_equation.arrange(RIGHT)
        df_equation.next_to(ORIGIN, RIGHT)
        df_equation.to_edge(UP)

        faces_brace = Brace(faces, DOWN)
        derivative = faces_brace.get_tex("3x^2", "\\, dx")
        extras_brace = Brace(VGroup(bars, corner_cube), DOWN)
        ignore_text = extras_brace.get_text(
            "Multiple \\\\ of $dx^2$"
        )
        ignore_text.scale_in_place(0.7)
        x_squared_dx = TexMobject("x^2", "\\, dx")


        self.play(*list(map(Write, [df, equals])))
        self.grab_pieces(self.faces, faces)
        self.wait()
        self.shrink_dx("Faces are introduced")
        face = self.faces[0]
        face.save_state()
        self.play(face.shift, FRAME_X_RADIUS*RIGHT)
        x_squared_dx.next_to(face, LEFT)
        self.play(Write(x_squared_dx, run_time = 1))
        self.wait()
        for submob, sides in zip(x_squared_dx, [face[0], VGroup(*face[1:])]):
            self.play(
                submob.set_color, RED,
                sides.set_color, RED,
                rate_func = there_and_back
            )
            self.wait()
        self.play(
            face.restore,
            Transform(
                x_squared_dx, derivative,
                replace_mobject_with_target_in_scene = True
            ),
            GrowFromCenter(faces_brace)
        )
        self.wait()
        self.grab_pieces(self.bars, bars, plus1)
        self.grab_pieces(self.corner_cube, corner_cube, plus2)
        self.play(
            GrowFromCenter(extras_brace),
            Write(ignore_text)
        )
        self.wait()
        self.play(*[
            ApplyMethod(mob.fade, 0.7)
            for mob in [
                plus1, bars, plus2, corner_cube, 
                extras_brace, ignore_text
            ]
        ])
        self.wait()

        self.df_equation = df_equation
        self.derivative = derivative

    def write_derivative(self):
        df, equals, faces, plus1, bars, plus2, corner_cube = self.df_equation
        df = df.copy()
        equals = equals.copy()        
        df_equals = VGroup(df, equals)        

        derivative = self.derivative.copy()
        dx = derivative[1]

        extra_stuff = TexMobject("+(\\dots)", "dx^2")
        dx_squared = extra_stuff[1]

        derivative.generate_target()
        derivative.target.shift(2*DOWN)
        extra_stuff.next_to(derivative.target)
        self.play(
            MoveToTarget(derivative),
            df_equals.next_to, derivative.target[0], LEFT,
            df_equals.shift, 0.07*DOWN
        )
        self.play(Write(extra_stuff))
        self.wait()

        frac_line = TexMobject("-")
        frac_line.replace(df)
        extra_stuff.generate_target()
        extra_stuff.target.next_to(derivative[0])
        frac_line2 = TexMobject("-")
        frac_line2.stretch_to_fit_width(
            extra_stuff.target[1].get_width()
        )
        frac_line2.move_to(extra_stuff.target[1])
        extra_stuff.target[1].next_to(frac_line2, UP, buff = SMALL_BUFF)
        dx_below_dx_squared = TexMobject("dx")
        dx_below_dx_squared.next_to(frac_line2, DOWN, buff = SMALL_BUFF)
        self.play(
            FadeIn(frac_line),
            FadeIn(frac_line2),
            df.next_to, frac_line, UP, SMALL_BUFF,
            dx.next_to, frac_line, DOWN, SMALL_BUFF,
            MoveToTarget(extra_stuff),
            Write(dx_below_dx_squared),
            path_arc = -np.pi
        )
        self.wait()
        inner_dx = VGroup(*dx_squared[:-1])
        self.play(
            FadeOut(frac_line2),
            FadeOut(dx_below_dx_squared),
            dx_squared[-1].set_color, BLACK,
            inner_dx.next_to, extra_stuff[0], RIGHT, SMALL_BUFF
        )
        self.wait()
        self.shrink_dx("Derivative is written", restore = False)
        self.play(*[
            ApplyMethod(mob.fade, 0.7)
            for mob in (extra_stuff, inner_dx)
        ])
        self.wait(2)

        anims = []
        for mob in list(self.faces)+list(self.bars)+list(self.corner_cube):
            vect = mob.get_center()-self.cube.get_center()
            anims += [
                mob.shift, -(1./3)*vect
            ]
        anims += self.dx_brace.shift, 0.7*DOWN
        self.play(*anims)
        self.wait()

    def grab_pieces(self, start_pieces, end_pices, to_write = None):
        for piece in start_pieces:
            piece.generate_target()
            piece.target.rotate_in_place(
                np.pi/12, piece.get_center()-self.cube.get_center()
            )
            piece.target.set_color(RED)
        self.play(*list(map(MoveToTarget, start_pieces)), rate_func = wiggle)
        self.wait()
        added_anims = []
        if to_write is not None:
            added_anims.append(Write(to_write))
        self.play(
            Transform(start_pieces.copy(), end_pices),
            *added_anims
        )

    def shrink_dx(self, state_name, restore = True):
        mobjects = self.get_mobjects()
        mobjects_with_points = [
            m for m in mobjects
            if m.get_num_points() > 0
        ]
        #Alt_scene will reach this point, and save copy of self
        #in states dict
        self.states[state_name] = [
            mob.copy() for mob in mobjects_with_points
        ] 
        if not self.allow_recursion:
            return
        if restore:
            movers = self.states[state_name]
            for mob in movers:
                mob.save_state()
            self.remove(*mobjects)
        else:
            movers = mobjects_with_points
        self.play(*[
            Transform(*pair)
            for pair in zip(
                movers,
                self.alt_scene.states[state_name]
            )
        ])
        self.wait()
        if restore:
            self.play(*[m.restore for m in movers])
            self.remove(*movers)
            self.mobjects = mobjects

    def get_cube(self):
        cube = self.get_prism(self.x, self.x, self.x)
        cube.set_fill(color = BLUE, opacity = 0.3)
        cube.set_stroke(color = WHITE, width = 1)
        return cube

    def get_dv_pices(self, cube):
        pieces = VGroup()
        for vect in it.product([0, 1], [0, 1], [0, 1]):
            if np.all(vect == ORIGIN):
                continue
            args = [
                self.x if bit is 0 else self.dx
                for bit in vect
            ]
            piece = self.get_prism(*args)
            piece.next_to(cube, np.array(vect), buff = 0)
            pieces.add(piece)
            if sum(vect) == 1:
                piece.type = "face"
            elif sum(vect) == 2:
                piece.type = "bar"
            else:
                piece.type = "corner_cube"

        return pieces

    def organize_faces(self, faces):
        self.unpose_3d_mobject(faces)
        for face in faces:
            dimensions = [
                face.length_over_dim(dim)
                for dim in range(3)
            ]
            thin_dim = np.argmin(dimensions)
            if thin_dim == 0:
                face.rotate(np.pi/2, DOWN)
            elif thin_dim == 1:
                face.rotate(np.pi/2, RIGHT)
        faces.arrange(OUT, buff = LARGE_BUFF)
        self.pose_3d_mobject(faces)
        return faces

    def organize_bars(self, bars):
        self.unpose_3d_mobject(bars)
        for bar in bars:
            dimensions = [
                bar.length_over_dim(dim)
                for dim in range(3)
            ]
            thick_dim = np.argmax(dimensions)
            if thick_dim == 0:
                bar.rotate(np.pi/2, OUT)
            elif thick_dim == 2:
                bar.rotate(np.pi/2, LEFT)
        bars.arrange(OUT, buff = LARGE_BUFF)
        self.pose_3d_mobject(bars)
        return bars

    def get_corner_cube(self):
        return self.get_prism(self.dx, self.dx,  self.dx)

    def get_prism(self, width, height, depth):
        color_kwargs = {
            "fill_color" : YELLOW,
            "fill_opacity" : 0.4,
            "stroke_color" : WHITE,            
            "stroke_width" : 0.1,
        }
        front = Rectangle(
            width = width,
            height = height,
            **color_kwargs
        )
        face = VGroup(front)
        for vect in LEFT, RIGHT, UP, DOWN:
            if vect is LEFT or vect is RIGHT:
                side = Rectangle(
                    height = height, 
                    width = depth, 
                    **color_kwargs
                )
            else:
                side = Rectangle(
                    height = depth,
                    width = width, 
                    **color_kwargs
                )
            side.next_to(front, vect, buff = 0)
            side.rotate(
                np.pi/2, rotate_vector(vect, -np.pi/2),
                about_point = front.get_edge_center(vect)
            )
            face.add(side)
        return face

    def pose_3d_mobject(self, mobject):
        mobject.rotate_in_place(self.pose_angle, self.pose_axis)
        return mobject

    def unpose_3d_mobject(self, mobject):
        mobject.rotate_in_place(-self.pose_angle, self.pose_axis)
        return mobject

class ShowCubeDVIn3D(Scene):
    def construct(self):
        raise Exception("This scene is only here for the stage_scenes script.")

class GraphOfXCubed(GraphScene):
    CONFIG = {
        "x_min" : -6,
        "x_max" : 6,
        "x_axis_width" : FRAME_WIDTH,
        "x_labeled_nums" : list(range(-6, 7)),
        "y_min" : -35,
        "y_max" : 35,
        "y_axis_height" : FRAME_HEIGHT,
        "y_tick_frequency" : 5,
        "y_labeled_nums" : list(range(-30, 40, 10)),
        "graph_origin" : ORIGIN,
        "dx" : 0.2,
        "deriv_x_min" : -3,
        "deriv_x_max" : 3,
    }
    def construct(self):
        self.setup_axes(animate = False)
        graph = self.get_graph(lambda x : x**3)
        label = self.get_graph_label(
            graph, "f(x) = x^3",
            direction = LEFT,
        )


        deriv_graph, full_deriv_graph = [
            self.get_derivative_graph(
                graph,
                color = DERIVATIVE_COLOR,
                x_min = low_x,
                x_max = high_x,
            )
            for low_x, high_x in [
                (self.deriv_x_min, self.deriv_x_max),
                (self.x_min, self.x_max),
            ]
        ]
        deriv_label = self.get_graph_label(
            deriv_graph,
            "\\frac{df}{dx}(x) = 3x^2",
            x_val = -3, 
            direction = LEFT
        )
        deriv_label.shift(0.5*DOWN)

        ss_group = self.get_secant_slope_group(
            self.deriv_x_min, graph, 
            dx = self.dx,
            dx_line_color = WHITE,
            df_line_color = WHITE,
            secant_line_color = YELLOW,
        )

        self.play(ShowCreation(graph))
        self.play(Write(label, run_time = 1))
        self.wait()
        self.play(Write(deriv_label, run_time = 1))
        self.play(ShowCreation(ss_group, lag_ratio = 0))
        self.animate_secant_slope_group_change(
            ss_group,
            target_x = self.deriv_x_max,
            run_time = 10,
            added_anims = [
                ShowCreation(deriv_graph, run_time = 10)
            ]
        )
        self.play(FadeIn(full_deriv_graph))
        self.wait()
        for x_val in -2, -self.dx/2, 2:
            self.animate_secant_slope_group_change(
                ss_group,
                target_x = x_val,
                run_time = 2
            )
            if x_val != -self.dx/2:
                v_line = self.get_vertical_line_to_graph(
                    x_val, deriv_graph,
                    line_class = DashedLine
                )
                self.play(ShowCreation(v_line))
                self.play(FadeOut(v_line))

class PatternForPowerRule(PiCreatureScene):
    CONFIG = {
        "num_exponents" : 5,
    }
    def construct(self):
        self.introduce_pattern()
        self.generalize_pattern()
        self.show_hopping()

    def introduce_pattern(self):
        exp_range = list(range(1, 1+self.num_exponents))
        colors = color_gradient([BLUE_D, YELLOW], self.num_exponents)
        derivatives = VGroup()
        for exponent, color in zip(exp_range, colors):
            derivative = TexMobject(
                "\\frac{d(x^%d)}{dx} = "%exponent,
                "%d x^{%d}"%(exponent, exponent-1)
            )
            VGroup(*derivative[0][2:4]).set_color(color)
            derivatives.add(derivative)
        derivatives.arrange(
            DOWN, aligned_edge = LEFT,
            buff = MED_LARGE_BUFF
        )
        derivatives.set_height(FRAME_HEIGHT-1)
        derivatives.to_edge(LEFT)

        self.play(FadeIn(derivatives[0]))
        for d1, d2 in zip(derivatives, derivatives[1:]):
            self.play(Transform(
                d1.copy(), d2,
                replace_mobject_with_target_in_scene = True  
            ))
        self.change_mode("thinking")
        self.wait()
        for derivative in derivatives[-2:]:
            derivative.save_state()
            self.play(
                derivative.scale, 2,
                derivative.next_to, derivative,
                RIGHT, SMALL_BUFF, DOWN,
            )
            self.wait(2)
            self.play(derivative.restore)
            self.remove(derivative)
            derivative.restore()
            self.add(derivative)

        self.derivatives = derivatives
        self.colors = colors

    def generalize_pattern(self):
        derivatives = self.derivatives


        power_rule = TexMobject(
            "\\frac{d (x^n)}{dx} = ",
            "nx^{n-1}"
        )
        title = TextMobject("``Power rule''")        
        title.next_to(power_rule, UP, MED_LARGE_BUFF)
        lines = VGroup(*[
            Line(
                deriv.get_right(), power_rule.get_left(),
                buff = MED_SMALL_BUFF,
                color = deriv[0][2].get_color()
            )
            for deriv in derivatives
        ])

        self.play(
            Transform(
                VGroup(*[d[0].copy() for d in derivatives]),
                VGroup(power_rule[0]),
                replace_mobject_with_target_in_scene = True
            ),
            ShowCreation(lines),
            lag_ratio = 0.5,
            run_time = 2,
        )
        self.wait()
        self.play(Write(power_rule[1]))
        self.wait()
        self.play(
            Write(title),
            self.pi_creature.change_mode, "speaking"
        )
        self.wait()

    def show_hopping(self):
        exp_range = list(range(2, 2+self.num_exponents-1))
        self.change_mode("tired")
        for exp, color in zip(exp_range, self.colors[1:]):
            form = TexMobject(
                "x^",
                str(exp),
                "\\rightarrow",
                str(exp),
                "x^",
                str(exp-1)
            )
            form.set_color(color)
            form.to_corner(UP+RIGHT, buff = LARGE_BUFF)
            lhs = VGroup(*form[:2])
            lhs_copy = lhs.copy()
            rhs = VGroup(*form[-2:])
            arrow = form[2]

            self.play(Write(lhs))
            self.play(
                lhs_copy.move_to, rhs, DOWN+LEFT,
                Write(arrow)
            )
            self.wait()
            self.play(
                ApplyMethod(
                    lhs_copy[1].replace, form[3],
                    path_arc = np.pi,
                    rate_func = running_start,
                ),
                FadeIn(
                    form[5],
                    rate_func = squish_rate_func(smooth, 0.5, 1)
                )   
            )
            self.wait()
            self.play(
                self.pi_creature.change_mode, "hesitant",
                self.pi_creature.look_at, lhs_copy
            )
            self.play(*list(map(FadeOut, [form, lhs_copy])))

class PowerRuleAlgebra(Scene):
    CONFIG = {
        "dx_color" : YELLOW,
        "x_color" : BLUE,
    }
    def construct(self):
        x_to_n = TexMobject("x^n")
        down_arrow = Arrow(UP, DOWN, buff = MED_LARGE_BUFF)
        paren_strings = ["(", "x", "+", "dx", ")"]
        x_dx_to_n = TexMobject(*paren_strings +["^n"])
        equals = TexMobject("=")
        equals2 = TexMobject("=")
        full_product = TexMobject(
            *paren_strings*3+["\\cdots"]+paren_strings
        )

        x_to_n.set_color(self.x_color)
        for mob in x_dx_to_n, full_product:
            mob.set_color_by_tex("dx", self.dx_color)
            mob.set_color_by_tex("x", self.x_color)

        nudge_group = VGroup(x_to_n, down_arrow, x_dx_to_n)
        nudge_group.arrange(DOWN)
        nudge_group.to_corner(UP+LEFT)
        down_arrow.next_to(x_to_n[0], DOWN)
        equals.next_to(x_dx_to_n)
        full_product.next_to(equals)
        equals2.next_to(equals, DOWN, 1.5*LARGE_BUFF)

        nudge_brace = Brace(x_dx_to_n, DOWN)
        nudged_output = nudge_brace.get_text("Nudged \\\\ output")
        product_brace = Brace(full_product, UP)
        product_brace.add(product_brace.get_text("$n$ times"))

        self.add(x_to_n)
        self.play(ShowCreation(down_arrow))
        self.play(
            FadeIn(x_dx_to_n),
            GrowFromCenter(nudge_brace),
            GrowFromCenter(nudged_output)
        )
        self.wait()
        self.play(
            Write(VGroup(equals, full_product)),
            GrowFromCenter(
                product_brace,
                rate_func = squish_rate_func(smooth, 0.6, 1)
            ),
            run_time = 3
        )
        self.wait()
        self.workout_product(equals2, full_product)

    def workout_product(self, equals, full_product):
        product_part_tex_pairs = list(zip(full_product, full_product.expression_parts))
        xs, dxs = [
            VGroup(*[
                submob
                for submob, tex in product_part_tex_pairs
                if tex == target_tex
            ])
            for target_tex in ("x", "dx")
        ]

        x_to_n = TexMobject("x^n")
        extra_stuff = TexMobject("+(\\text{Multiple of }\\, dx^2)")
        # extra_stuff.scale(0.8)
        VGroup(*extra_stuff[-4:-2]).set_color(self.dx_color)

        x_to_n.next_to(equals, RIGHT, align_using_submobjects = True)
        x_to_n.set_color(self.x_color)

        xs_copy = xs.copy()
        full_product.save_state()
        self.play(full_product.set_color, WHITE)
        self.play(xs_copy.set_color, self.x_color)
        self.play(
            Write(equals),
            Transform(xs_copy, x_to_n)
        )
        self.wait()
        brace, derivative_term = self.pull_out_linear_terms(
            x_to_n, product_part_tex_pairs, xs, dxs
        )
        self.wait()

        circle = Circle(color = DERIVATIVE_COLOR)
        circle.replace(derivative_term, stretch = True)
        circle.scale_in_place(1.4)
        circle.rotate_in_place(
            Line(
                derivative_term.get_corner(DOWN+LEFT),
                derivative_term.get_corner(UP+RIGHT),
            ).get_angle()
        )

        extra_stuff.next_to(brace, aligned_edge = UP)

        self.play(Write(extra_stuff), full_product.restore)
        self.wait()
        self.play(ShowCreation(circle))
        self.wait()

    def pull_out_linear_terms(self, x_to_n, product_part_tex_pairs, xs, dxs):
        last_group = None
        all_linear_terms = VGroup()
        for dx_index, dx in enumerate(dxs):
            if dx is dxs[-1]:
                v_dots = TexMobject("\\vdots")
                v_dots.next_to(last_group[0], DOWN)
                h_dots_list = [
                    submob
                    for submob, tex in product_part_tex_pairs
                    if tex == "\\cdots"
                ]
                h_dots_copy = h_dots_list[0].copy()
                self.play(ReplacementTransform(
                    h_dots_copy, v_dots,
                ))
                last_group.add(v_dots)
                all_linear_terms.add(v_dots)

            dx_copy = dx.copy()
            xs_copy = xs.copy()
            xs_copy.remove(xs_copy[dx_index])
            self.play(
                dx_copy.set_color, self.dx_color,
                xs_copy.set_color, self.x_color,
                rate_func = squish_rate_func(smooth, 0, 0.5)
            )

            dx_copy.generate_target()
            xs_copy.generate_target()
            target_list = [dx_copy.target] + list(xs_copy.target)
            target_list.sort(
                key=lambda m: m.get_center()[0]
            )
            dots = TexMobject("+", ".", ".", "\\dots")
            for dot_index, dot in enumerate(dots):
                target_list.insert(2*dot_index, dot)
            group = VGroup(*target_list)
            group.arrange(RIGHT, SMALL_BUFF)
            if last_group is None:
                group.next_to(x_to_n, RIGHT)
            else:
                group.next_to(last_group, DOWN, aligned_edge = LEFT)
            last_group = group

            self.play(
                MoveToTarget(dx_copy),
                MoveToTarget(xs_copy),
                Write(dots)
            )
            all_linear_terms.add(dx_copy, xs_copy, dots)

        all_linear_terms.generate_target()
        all_linear_terms.target.scale(0.7)
        brace = Brace(all_linear_terms.target, UP)
        compact = TexMobject("+\\,", "n", "x^{n-1}", "\\,dx")
        compact.set_color_by_tex("x^{n-1}", self.x_color)
        compact.set_color_by_tex("\\,dx", self.dx_color)
        compact.next_to(brace, UP)
        brace.add(compact)
        derivative_term = VGroup(*compact[1:3])

        VGroup(brace, all_linear_terms.target).shift(
            x_to_n[0].get_right()+MED_LARGE_BUFF*RIGHT - \
            compact[0].get_left()
        )

        self.play(MoveToTarget(all_linear_terms))
        self.play(Write(brace, run_time = 1))
        return brace, derivative_term

class ReactToFullExpansion(Scene):
    def construct(self):
        randy = Randolph()
        self.add(randy)

        self.play(randy.change_mode, "pleading")
        self.play(Blink(randy))
        self.play(randy.change_mode, "angry")
        self.wait()
        self.play(randy.change_mode, "thinking")
        self.play(Blink(randy))
        self.wait()

class OneOverX(PiCreatureScene, GraphScene):
    CONFIG = {
        "unit_length" : 3.0,    
        "graph_origin" : (FRAME_X_RADIUS - LARGE_BUFF)*LEFT + 2*DOWN,
        "rectangle_color_kwargs" : {
            "fill_color" : BLUE,
            "fill_opacity" : 0.5,
            "stroke_width" : 1,
            "stroke_color" : WHITE,
        },

        "x_axis_label" : "",
        "y_axis_label" : "",
        "x_min" : 0,
        "y_min" : 0,
        "x_tick_frequency" : 0.5,
        "y_tick_frequency" : 0.5,
        "x_labeled_nums" : list(range(0, 4)),
        "y_labeled_nums" : [1],
        "y_axis_height" : 10,
        "morty_scale_val" : 0.8,
        "area_label_scale_factor" : 0.75,
        "dx" : 0.1,
        "start_x_value" : 1.3,
        "dx_color" : GREEN,
        "df_color" : RED,
    }
    def setup(self):
        for c in self.__class__.__bases__:
            c.setup(self)
        self.x_max = self.x_axis_width/self.unit_length
        self.y_max = self.y_axis_height/self.unit_length

    def construct(self):
        self.force_skipping()

        self.introduce_function()
        self.introduce_puddle()
        self.introduce_graph()
        self.perform_nudge()

    def introduce_function(self):
        func = TexMobject("f(x) = ", "\\frac{1}{x}")
        func.to_edge(UP)
        recip_copy = func[1].copy()
        x_to_neg_one = TexMobject("x^{-1}")
        x_to_neg_one.submobjects.reverse()
        neg_one = VGroup(*x_to_neg_one[:2])
        neg_two = TexMobject("-2")

        self.play(
            Write(func),
            self.pi_creature.change_mode, "pondering"
        )
        self.wait()
        self.play(
            recip_copy.next_to, self.pi_creature, UP+LEFT,
            self.pi_creature.change_mode, "raise_right_hand"
        )
        x_to_neg_one.move_to(recip_copy)
        neg_two.replace(neg_one)
        self.play(ReplacementTransform(recip_copy, x_to_neg_one))
        self.wait()
        self.play(
            neg_one.scale, 1.5,
            neg_one.next_to, x_to_neg_one, LEFT, SMALL_BUFF, DOWN,
            rate_func = running_start,
            path_arc = np.pi
        )
        self.play(FadeIn(neg_two))
        self.wait()
        self.say(
            "More geometry!",
            target_mode = "hooray",
            added_anims = [
                FadeOut(x_to_neg_one),
                FadeOut(neg_two),
            ],
            run_time = 2
        )
        self.wait()
        self.play(RemovePiCreatureBubble(self.pi_creature))

    def introduce_puddle(self):
        rect_group = self.get_rectangle_group(self.start_x_value)

        self.play(
            DrawBorderThenFill(rect_group.rectangle),
            Write(rect_group.area_label),
            self.pi_creature.change_mode, "thinking"
        )
        self.play(
            GrowFromCenter(rect_group.x_brace),
            Write(rect_group.x_label),
        )
        self.wait()
        self.play(
            GrowFromCenter(rect_group.recip_brace),
            Write(rect_group.recip_label),
        )
        self.setup_axes(animate = True)
        self.wait()

        for d in 2, 3:
            self.change_rectangle_group(
                rect_group, d,
                target_group_kwargs = {
                    "x_label" : str(d),
                    "one_over_x_label" : "\\frac{1}{%d}"%d,
                },
                run_time = 2
            )
            self.wait()
        self.change_rectangle_group(rect_group, 3)
        self.wait()

        self.rect_group = rect_group

    def introduce_graph(self):
        rect_group = self.rect_group
        graph = self.get_graph(lambda x : 1./x)
        graph.points = np.array(list(reversed(graph.points)))

        self.change_rectangle_group(
            rect_group, 0.01,
            added_anims = [
                ShowCreation(graph)
            ],
            run_time = 5,
        )
        self.change_mode("happy")
        self.change_rectangle_group(rect_group, self.start_x_value)
        self.wait()

        self.graph = graph

    def perform_nudge(self):
        rect_group = self.rect_group
        graph = self.graph

        rect_copy = rect_group.rectangle.copy()
        rect_copy.set_fill(opacity = 0)
        new_rect = self.get_rectangle(
            self.start_x_value + self.dx
        )

        recip_brace = rect_group.recip_brace
        recip_brace.generate_target()
        recip_brace.target.next_to(
            new_rect, RIGHT, 
            buff = SMALL_BUFF,
            aligned_edge = DOWN,
        )
        recip_label = rect_group.recip_label
        recip_label.generate_target()
        recip_label.target.next_to(recip_brace.target, RIGHT)

        h_lines = VGroup(*[
            DashedLine(
                ORIGIN, (rect_copy.get_width()+LARGE_BUFF)*RIGHT, 
                color = self.df_color,
                stroke_width = 2
            ).move_to(rect.get_corner(UP+LEFT), LEFT)
            for rect in (rect_group.rectangle, new_rect)
        ])

        v_lines = VGroup(*[
            DashedLine(
                ORIGIN, (rect_copy.get_height()+MED_LARGE_BUFF)*UP,
                color = self.dx_color,
                stroke_width = 2
            ).move_to(rect.get_corner(DOWN+RIGHT), DOWN)
            for rect in (rect_group.rectangle, new_rect)
        ])

        dx_brace = Brace(v_lines, UP, buff = 0)
        dx_label = dx_brace.get_text("$dx$")
        dx_brace.add(dx_label)

        df_brace = Brace(h_lines, RIGHT, buff = 0)
        df_label = df_brace.get_text("$d\\left(\\frac{1}{x}\\right)$")
        df_brace.add(df_label)

        negative = TextMobject("Negative")
        negative.set_color(RED)
        negative.next_to(df_label, UP+RIGHT)
        negative.shift(RIGHT)
        negative_arrow = Arrow(
            negative.get_left(),
            df_label.get_corner(UP+RIGHT),
            color = RED
        )

        area_changes = VGroup()
        point_pairs = [
            (new_rect.get_corner(UP+RIGHT), rect_copy.get_corner(DOWN+RIGHT)),
            (new_rect.get_corner(UP+LEFT), rect_copy.get_corner(UP+RIGHT))
        ]
        for color, point_pair in zip([self.dx_color, self.df_color], point_pairs):
            area_change_rect = Rectangle(
                fill_opacity = 1,
                fill_color = color,
                stroke_width = 0
            )
            area_change_rect.replace(
                VGroup(*list(map(VectorizedPoint, point_pair))),
                stretch = True
            )
            area_changes.add(area_change_rect)
        area_gained, area_lost = area_changes

        area_gained_label = TextMobject("Area gained")
        area_gained_label.scale(0.75)
        area_gained_label.next_to(
            rect_copy.get_corner(DOWN+RIGHT), 
            UP+LEFT, buff = SMALL_BUFF
        )
        area_gained_arrow = Arrow(
            area_gained_label.get_top(),
            area_gained.get_center(),
            buff = 0,
            color = WHITE
        )
        
        area_lost_label = TextMobject("Area lost")
        area_lost_label.scale(0.75)
        area_lost_label.next_to(rect_copy.get_left(), RIGHT)
        area_lost_arrow = Arrow(
            area_lost_label.get_top(),
            area_lost.get_center(),
            buff = 0,
            color = WHITE
        )

        question = TexMobject(
            "\\frac{d(1/x)}{dx} = ???"
        )
        question.next_to(
            self.pi_creature.get_corner(UP+LEFT), 
            UP, buff = MED_SMALL_BUFF,
        )

        self.play(
            FadeOut(rect_group.area_label),
            ReplacementTransform(rect_copy, new_rect),
            MoveToTarget(recip_brace),
            MoveToTarget(recip_label),
            self.pi_creature.change_mode, "pondering"
        )
        self.play(
            GrowFromCenter(dx_brace),
            *list(map(ShowCreation, v_lines))
        )
        self.wait()
        self.play(
            GrowFromCenter(df_brace),
            *list(map(ShowCreation, h_lines))
        )
        self.change_mode("confused")
        self.wait()

        self.play(
            FadeIn(area_gained),
            Write(area_gained_label, run_time = 2),
            ShowCreation(area_gained_arrow)
        )
        self.wait()
        self.play(
            FadeIn(area_lost),
            Write(area_lost_label, run_time = 2),
            ShowCreation(area_lost_arrow)
        )
        self.wait()
        self.revert_to_original_skipping_status()###
        self.play(
            Write(negative),
            ShowCreation(negative_arrow)
        )
        self.wait()
        self.play(
            Write(question),
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.wait(2)


    ########

    def create_pi_creature(self):
        morty = PiCreatureScene.create_pi_creature(self)
        morty.scale(
            self.morty_scale_val, 
            about_point = morty.get_corner(DOWN+RIGHT)
        )
        return morty

    def draw_graph(self):
        self.setup_axes()
        graph = self.get_graph(lambda x : 1./x)

        rect_group = self.get_rectangle_group(0.5)

        self.add(rect_group)
        self.wait()
        self.change_rectangle_group(
            rect_group, 2,
            target_group_kwargs = {
                "x_label" : "2",
                "one_over_x_label" : "\\frac{1}{2}",
            },
            added_anims = [ShowCreation(graph)]
        )
        self.wait()

    def get_rectangle_group(
        self, x, 
        x_label = "x", 
        one_over_x_label = "\\frac{1}{x}"
        ):
        result = VGroup()
        result.x_val = x
        result.rectangle = self.get_rectangle(x)

        result.x_brace, result.recip_brace = braces = [
            Brace(result.rectangle, vect)
            for vect in (UP, RIGHT)
        ]
        result.labels = VGroup()
        for brace, label in zip(braces, [x_label, one_over_x_label]):
            brace.get_text("$%s$"%label)
            result.labels.add(brace.get_text("$%s$"%label))
        result.x_label, result.recip_label = result.labels

        area_label = TextMobject("Area = 1")
        area_label.scale(self.area_label_scale_factor)
        max_width = max(result.rectangle.get_width()-2*SMALL_BUFF, 0)
        if area_label.get_width() > max_width:
            area_label.set_width(max_width)
        area_label.move_to(result.rectangle)
        result.area_label = area_label

        result.add(
            result.rectangle,
            result.x_brace,
            result.recip_brace,
            result.labels,
            result.area_label,
        )
        return result

    def get_rectangle(self, x):
        try:
            y = 1./x
        except ZeroDivisionError:
            y = 100

        rectangle = Rectangle(
            width = x*self.unit_length,
            height = y*self.unit_length,
            **self.rectangle_color_kwargs
        )
        rectangle.move_to(self.graph_origin, DOWN+LEFT)
        return rectangle

    def change_rectangle_group(
        self, 
        rect_group, target_x,
        target_group_kwargs = None,
        added_anims = [],
        **anim_kwargs
        ):
        target_group_kwargs = target_group_kwargs or {}
        if "run_time" not in anim_kwargs:
            anim_kwargs["run_time"] = 3

        target_group = self.get_rectangle_group(target_x, **target_group_kwargs)
        target_labels = target_group.labels
        labels_transform = Transform(
            rect_group.labels,
            target_group.labels
        )

        start_x = float(rect_group.x_val)
        def update_rect_group(group, alpha):
            x = interpolate(start_x, target_x, alpha)
            new_group = self.get_rectangle_group(x, **target_group_kwargs)
            Transform(group, new_group).update(1)
            labels_transform.update(alpha)
            for l1, l2 in zip(rect_group.labels, new_group.labels):
                l1.move_to(l2)
            return rect_group

        self.play(
            UpdateFromAlphaFunc(rect_group, update_rect_group),
            *added_anims,
            **anim_kwargs
        )
        rect_group.x_val = target_x

class AskRecipriocalQuestion(Scene):
    def construct(self):
        tex = TexMobject(
            "(\\text{What number?})",  
            "\\cdot x = 1"
        )
        arrow = Arrow(DOWN+LEFT, UP+RIGHT)
        arrow.move_to(tex[0].get_top(), DOWN+LEFT)
        self.play(Write(tex))
        self.play(ShowCreation(arrow))
        self.wait()

class SquareRootOfX(Scene):
    CONFIG = {
        "square_color_kwargs" : {
            "stroke_color" : WHITE,
            "stroke_width" : 1,
            "fill_color" : BLUE_E,
            "fill_opacity" : 1,
        },
        "bigger_square_color_kwargs" : {
            "stroke_color" : WHITE,
            "stroke_width" : 1,
            "fill_color" : YELLOW,
            "fill_opacity" : 0.7,
        },
        "square_corner" : 6*LEFT+3*UP,
        "square_width" : 3,
        "d_sqrt_x" : 0.3,
    }
    def construct(self):
        self.add_title()
        self.introduce_square()
        self.nudge_square()

    def add_title(self):
        title = TexMobject("f(x) = \\sqrt{x}")
        title.next_to(ORIGIN, RIGHT)
        title.to_edge(UP)
        self.add(title)

    def introduce_square(self):
        square = Square(
            side_length = self.square_width,
            **self.square_color_kwargs
        )
        square.move_to(self.square_corner, UP+LEFT)
        area_label = TextMobject("Area $ = x$")
        area_label.move_to(square)

        bottom_brace, right_brace = braces = VGroup(*[
            Brace(square, vect)
            for vect in (DOWN, RIGHT)
        ])
        for brace in braces:
            brace.add(brace.get_text("$\\sqrt{x}$"))


        self.play(
            DrawBorderThenFill(square),
            Write(area_label)
        )
        self.play(*list(map(FadeIn, braces)))
        self.wait()

        self.square = square
        self.area_label = area_label
        self.braces = braces

    def nudge_square(self):
        square = self.square
        area_label = self.area_label
        bottom_brace, right_brace = self.braces

        bigger_square = Square(
            side_length = self.square_width + self.d_sqrt_x,
            **self.bigger_square_color_kwargs
        )
        bigger_square.move_to(self.square_corner, UP+LEFT)

        square_copy = square.copy()

        lines = VGroup(*[
            DashedLine(
                ORIGIN,
                (self.square_width + MED_LARGE_BUFF)*vect,
                color = WHITE,
                stroke_width = 3
            ).shift(s.get_corner(corner))
            for corner, vect in [(DOWN+LEFT, RIGHT), (UP+RIGHT, DOWN)]
            for s in [square, bigger_square]
        ])
        little_braces = VGroup(*[
            Brace(VGroup(*line_pair), vect, buff = 0)
            for line_pair, vect in [(lines[:2], RIGHT), (lines[2:], DOWN)]
        ])
        for brace in little_braces:
            tex = brace.get_text("$d\\sqrt{x}$", buff = SMALL_BUFF)
            tex.scale_in_place(0.8)
            brace.add(tex)

        area_increase = TextMobject("$dx$ = New area")
        area_increase.set_color(bigger_square.get_color())
        area_increase.next_to(square, RIGHT, 4)

        question = TexMobject("\\frac{d\\sqrt{x}}{dx} = ???")
        VGroup(*question[5:7]).set_color(bigger_square.get_color())
        question.next_to(
            area_increase, DOWN, 
            aligned_edge = LEFT, 
            buff = LARGE_BUFF
        )
        
        self.play(
            Transform(square_copy, bigger_square),
            Animation(square),
            Animation(area_label),
            bottom_brace.next_to, bigger_square, DOWN, SMALL_BUFF, LEFT,
            right_brace.next_to, bigger_square, RIGHT, SMALL_BUFF, UP,
        )
        self.play(Write(area_increase))
        self.play(*it.chain(
            list(map(ShowCreation, lines)),
            list(map(FadeIn, little_braces))
        ))
        self.play(Write(question))
        self.wait()

class MentionSine(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("Let's tackle $\\sin(\\theta)$")
        self.change_student_modes("pondering", "hooray", "erm")
        self.wait(2)
        self.student_thinks("")
        self.zoom_in_on_thought_bubble()

class NameUnitCircle(Scene):
    def construct(self):
        words = TextMobject("Unit circle")
        words.scale(2)
        words.set_color(BLUE)
        self.play(Write(words))
        self.wait()

class DerivativeOfSineIsSlope(Scene):
    def construct(self):
        tex = TexMobject(
            "\\frac{d(\\sin(\\theta))}{d\\theta} = ",
            "\\text{Slope of this graph}"
        )
        tex.set_width(FRAME_WIDTH-1)
        tex.to_edge(DOWN)
        VGroup(*tex[0][2:8]).set_color(BLUE)
        VGroup(*tex[1][-9:]).set_color(BLUE)

        self.play(Write(tex, run_time = 2))
        self.wait()

class IntroduceUnitCircleWithSine(GraphScene):
    CONFIG = {
        "unit_length" : 2.5,
        "graph_origin" : ORIGIN,
        "x_axis_width" : 15,
        "y_axis_height" : 10,
        "x_min" : -3,
        "x_max" : 3,
        "y_min" : -2,
        "y_max" : 2,
        "x_labeled_nums" : [-2, -1, 1, 2],
        "y_labeled_nums" : [-1, 1],
        "x_tick_frequency" : 0.5,
        "y_tick_frequency" : 0.5,
        "circle_color" : BLUE,
        "example_radians" : 0.8,
        "rotations_per_second" : 0.25,
        "include_radial_line_dot" : True,
        "remove_angle_label" : True,
        "line_class" : DashedLine,
        "theta_label" : "= 0.8",
    }
    def construct(self):
        self.setup_axes()
        self.add_title()
        self.introduce_unit_circle()
        self.draw_example_radians()
        self.label_sine()
        self.walk_around_circle()

    def add_title(self):
        title = TexMobject("f(\\theta) = \\sin(\\theta)")
        title.to_corner(UP+LEFT)
        self.add(title)
        self.title = title

    def introduce_unit_circle(self):
        circle = self.get_unit_circle()
        radial_line = Line(ORIGIN, self.unit_length*RIGHT)
        radial_line.set_color(RED)
        if self.include_radial_line_dot:
            dot = Dot()
            dot.move_to(radial_line.get_end())
            radial_line.add(dot)

        self.play(ShowCreation(radial_line))
        self.play(
            ShowCreation(circle),            
            Rotate(radial_line, 2*np.pi),
            run_time = 2,
        )
        self.wait()

        self.circle = circle
        self.radial_line = radial_line

    def draw_example_radians(self):
        circle = self.circle
        radial_line = self.radial_line

        line = Line(
            ORIGIN, self.example_radians*self.unit_length*UP,
            color = YELLOW,
        )
        line.shift(FRAME_X_RADIUS*RIGHT/3).to_edge(UP)
        line.insert_n_curves(10)
        line.make_smooth()

        arc = Arc(
            self.example_radians, radius = self.unit_length,
            color = line.get_color(),
        )
        arc_copy = arc.copy().set_color(WHITE)

        brace = Brace(line, RIGHT)
        brace_text = brace.get_text("$\\theta%s$"%self.theta_label)
        brace_text.set_color(line.get_color())
        theta_copy = brace_text[0].copy()

        self.play(
            GrowFromCenter(line),
            GrowFromCenter(brace),
        )
        self.play(Write(brace_text))
        self.wait()
        self.play(
            line.move_to, radial_line.get_end(), DOWN,
            FadeOut(brace)
        )
        self.play(ReplacementTransform(line, arc))
        self.wait()
        self.play(
            Rotate(radial_line, self.example_radians),
            ShowCreation(arc_copy)
        )
        self.wait()
        arc_copy.generate_target()
        arc_copy.target.scale(0.2)
        theta_copy.generate_target()
        theta_copy.target.next_to(
            arc_copy.target, RIGHT,
            aligned_edge = DOWN,
            buff = SMALL_BUFF
        )
        theta_copy.target.shift(SMALL_BUFF*UP)
        self.play(*list(map(MoveToTarget, [arc_copy, theta_copy])))
        self.wait()

        self.angle_label = VGroup(arc_copy, theta_copy)
        self.example_theta_equation = brace_text

    def label_sine(self):
        radial_line = self.radial_line

        drop_point = radial_line.get_end()[0]*RIGHT
        v_line = self.line_class(radial_line.get_end(), drop_point)
        brace = Brace(v_line, RIGHT)
        brace_text = brace.get_text("$\\sin(\\theta)$")
        brace_text[-2].set_color(YELLOW)

        self.play(ShowCreation(v_line))
        self.play(
            GrowFromCenter(brace),
            Write(brace_text)
        )
        self.wait()
        faders = [brace, brace_text, self.example_theta_equation]
        if self.remove_angle_label:
            faders += self.angle_label
        self.play(*list(map(FadeOut, faders)))

        self.v_line = v_line

    def walk_around_circle(self):
        radial_line = self.radial_line
        v_line = self.v_line

        def v_line_update(v_line):
            drop_point = radial_line.get_end()[0]*RIGHT
            v_line.put_start_and_end_on(
                radial_line.get_end(), drop_point
            )
            return v_line
        filler_arc = self.circle.copy()
        filler_arc.set_color(YELLOW)
        curr_arc_portion = self.example_radians/(2*np.pi)
        filler_portion = 1 - curr_arc_portion
        filler_arc.pointwise_become_partial(filler_arc, curr_arc_portion, 1)

        self.play(
            Rotate(radial_line, filler_portion*2*np.pi),
            ShowCreation(filler_arc),
            UpdateFromFunc(v_line, v_line_update),
            run_time = filler_portion/self.rotations_per_second,
            rate_func=linear,
        )
        for x in range(5):
            self.play(
                Rotate(radial_line, 2*np.pi),
                UpdateFromFunc(v_line, v_line_update),
                run_time = 1./self.rotations_per_second,
                rate_func=linear,
            )

    ##############

    def setup_axes(self):
        GraphScene.setup_axes(self)
        VGroup(*self.x_axis.numbers[:2]).shift(MED_SMALL_BUFF*LEFT)
        VGroup(*self.x_axis.numbers[2:]).shift(SMALL_BUFF*RIGHT)
        self.y_axis.numbers[0].shift(MED_SMALL_BUFF*DOWN)
        self.y_axis.numbers[1].shift(MED_SMALL_BUFF*UP)

    def get_unit_circle(self):
        return Circle(
            radius = self.unit_length,
            color = self.circle_color,
        )

class DerivativeIntuitionFromSineGraph(GraphScene):
    CONFIG = {
        "graph_origin" : 6*LEFT,
        "x_axis_width" : 11,
        "x_min" : 0,
        "x_max" : 4*np.pi,
        "x_labeled_nums" : np.arange(0, 4*np.pi, np.pi),
        "x_tick_frequency" : np.pi/4,
        "x_axis_label" : "$\\theta$",
        "y_axis_height" : 6,
        "y_min" : -2,
        "y_max" : 2,
        "y_tick_frequency" : 0.5,
        "y_axis_label" : "",
    }
    def construct(self):
        self.setup_axes()
        self.draw_sine_graph()
        self.draw_derivative_from_slopes()
        self.alter_derivative_graph()

    def draw_sine_graph(self):
        graph = self.get_graph(np.sin)
        v_line = DashedLine(ORIGIN, UP)
        rps = IntroduceUnitCircleWithSine.CONFIG["rotations_per_second"]
        self.play(
            ShowCreation(graph),
            UpdateFromFunc(v_line, lambda v : self.v_line_update(v, graph)),
            run_time = 2./rps,
            rate_func=linear
        )
        self.wait()
        self.graph = graph

    def draw_derivative_from_slopes(self):
        ss_group = self.get_secant_slope_group(
            0, self.graph,
            dx = 0.01,
            secant_line_color = RED,
        )
        deriv_graph = self.get_graph(np.cos, color = DERIVATIVE_COLOR)
        v_line = DashedLine(
            self.graph_origin, self.coords_to_point(0, 1), 
            color = RED
        )

        self.play(ShowCreation(ss_group, lag_ratio = 0))
        self.play(ShowCreation(v_line))
        self.wait()
        last_theta = 0
        next_theta = np.pi/2
        while last_theta < self.x_max:
            deriv_copy = deriv_graph.copy()
            self.animate_secant_slope_group_change(
                ss_group,
                target_x = next_theta,
                added_anims = [
                    ShowCreation(
                        deriv_copy,
                        rate_func = lambda t : interpolate(
                            last_theta/self.x_max, 
                            next_theta/self.x_max,
                            smooth(t)
                        ),
                        run_time = 3,
                    ),
                    UpdateFromFunc(
                        v_line, 
                        lambda v : self.v_line_update(v, deriv_copy),
                        run_time = 3
                    ),
                ]
            )
            self.wait()
            if next_theta == 2*np.pi:
                words = TextMobject("Looks a lot like $\\cos(\\theta)$")
                words.next_to(self.graph_origin, RIGHT)
                words.to_edge(UP)
                arrow = Arrow(
                    words.get_bottom(), 
                    deriv_graph.point_from_proportion(0.45)
                )
                VGroup(words, arrow).set_color(deriv_graph.get_color())
                self.play(
                    Write(words),
                    ShowCreation(arrow)
                )
            self.remove(deriv_copy)
            last_theta = next_theta
            next_theta += np.pi/2
        self.add(deriv_copy)

        self.deriv_graph = deriv_copy

    def alter_derivative_graph(self):
        func_list = [
            lambda x : 0.5*(np.cos(x)**3 + np.cos(x)),
            lambda x : 0.75*(np.sign(np.cos(x))*np.cos(x)**2 + np.cos(x)),
            lambda x : 2*np.cos(x),
            lambda x : np.cos(x),
        ]
        for func in func_list:
            new_graph = self.get_graph(func, color = DERIVATIVE_COLOR)
            self.play(
                Transform(self.deriv_graph, new_graph),
                run_time = 2
            )
            self.wait()

    ######

    def v_line_update(self, v_line, graph):
        point = graph.point_from_proportion(1)
        drop_point = point[0]*RIGHT
        v_line.put_start_and_end_on(drop_point, point)
        return v_line

    def setup_axes(self):
        GraphScene.setup_axes(self)
        self.x_axis.remove(self.x_axis.numbers)
        self.remove(self.x_axis.numbers)
        for x in range(1, 4):
            if x == 1:
                label = TexMobject("\\pi")
            else:
                label = TexMobject("%d\\pi"%x)
            label.next_to(self.coords_to_point(x*np.pi, 0), DOWN, MED_LARGE_BUFF)
            self.add(label)
        self.x_axis_label_mob.set_color(YELLOW)

class LookToFunctionsMeaning(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            Look to the function's
            actual meaning
        """)
        self.change_student_modes(*["pondering"]*3)
        self.wait(3)

class DerivativeFromZoomingInOnSine(IntroduceUnitCircleWithSine, ZoomedScene):
    CONFIG = {
        "zoom_factor" : 10,
        "zoomed_canvas_frame_shape" : (3, 4.5),
        "include_radial_line_dot" : False,
        "remove_angle_label" : False,
        "theta_label" : "",
        "line_class" : Line,
        "example_radians" : 1.0,
        "zoomed_canvas_corner_buff" : SMALL_BUFF,
        "d_theta" : 0.05,
    }
    def construct(self):
        self.setup_axes()
        self.add_title()
        self.introduce_unit_circle()
        self.draw_example_radians()
        self.label_sine()

        self.zoom_in()
        self.perform_nudge()
        self.show_similar_triangles()
        self.analyze_ratios()

    def zoom_in(self):
        self.activate_zooming()
        self.little_rectangle.next_to(self.radial_line.get_end(), UP, LARGE_BUFF)
        self.play(*list(map(FadeIn, [
            self.little_rectangle, self.big_rectangle
        ])))
        self.play(
            self.little_rectangle.move_to, 
            self.radial_line.get_end(), DOWN+RIGHT,
            self.little_rectangle.shift, 
            SMALL_BUFF*(DOWN+RIGHT)
        )
        self.wait()

    def perform_nudge(self):
        d_theta_arc = Arc(
            start_angle = self.example_radians,
            angle = self.d_theta,
            radius = self.unit_length,
            color = MAROON_B,
            stroke_width = 6
        )
        d_theta_arc.scale(self.zoom_factor)
        d_theta_brace = Brace(
            d_theta_arc,
            rotate_vector(RIGHT, self.example_radians)
        )
        d_theta_label = TexMobject("d\\theta")
        d_theta_label.next_to(
            d_theta_brace.get_center(), d_theta_brace.direction, 
            MED_LARGE_BUFF
        )
        d_theta_label.set_color(d_theta_arc.get_color())

        group = VGroup(d_theta_arc, d_theta_brace, d_theta_label)
        group.scale(1./self.zoom_factor)

        point = self.radial_line.get_end()
        nudged_point = d_theta_arc.point_from_proportion(1)
        interim_point = nudged_point[0]*RIGHT+point[1]*UP
        h_line = DashedLine(
            interim_point, point, 
            dash_length = 0.01
        )
        d_sine_line = Line(interim_point, nudged_point, color = DERIVATIVE_COLOR)
        d_sine_brace = Brace(Line(ORIGIN, UP), LEFT)
        d_sine_brace.set_height(d_sine_line.get_height())
        d_sine_brace.next_to(d_sine_line, LEFT, buff = SMALL_BUFF/self.zoom_factor)
        d_sine = TexMobject("d(\\sin(\\theta))")
        d_sine.set_color(d_sine_line.get_color())
        d_sine.set_width(1.5*self.d_theta*self.unit_length)
        d_sine.next_to(d_sine_brace, LEFT, SMALL_BUFF/self.zoom_factor)

        self.play(ShowCreation(d_theta_arc))
        self.play(
            GrowFromCenter(d_theta_brace),
            FadeIn(d_theta_label)
        )
        self.wait()
        self.play(
            ShowCreation(h_line),
            ShowCreation(d_sine_line)
        )
        self.play(
            GrowFromCenter(d_sine_brace),
            Write(d_sine)
        )
        self.wait()

        self.little_triangle = Polygon(
            nudged_point, point, interim_point
        )
        self.d_theta_group = VGroup(d_theta_brace, d_theta_label)
        self.d_sine_group = VGroup(d_sine_brace, d_sine)

    def show_similar_triangles(self):
        little_triangle = self.little_triangle
        big_triangle = Polygon(
            self.graph_origin,
            self.radial_line.get_end(),
            self.radial_line.get_end()[0]*RIGHT,
        )
        for triangle in little_triangle, big_triangle:
            triangle.set_color(GREEN)
            triangle.set_fill(GREEN, opacity = 0.5)
        big_triangle_copy = big_triangle.copy()
        big_triangle_copy.next_to(ORIGIN, UP+LEFT)

        new_angle_label = self.angle_label.copy()
        new_angle_label.scale(
            little_triangle.get_width()/big_triangle.get_height()
        )
        new_angle_label.rotate(-np.pi/2)
        new_angle_label.shift(little_triangle.points[0])
        new_angle_label[1].rotate_in_place(np.pi/2)

        little_triangle_lines = VGroup(*[
            Line(*list(map(little_triangle.get_corner, pair)))
            for pair in [
                (DOWN+RIGHT, UP+LEFT),
                (UP+LEFT, DOWN+LEFT)
            ]
        ])
        little_triangle_lines.set_color(little_triangle.get_color())

        self.play(DrawBorderThenFill(little_triangle))
        self.play(
            little_triangle.scale, 2, little_triangle.get_corner(DOWN+RIGHT),
            little_triangle.set_color, YELLOW,
            rate_func = there_and_back
        )
        self.wait()
        groups = [self.d_theta_group, self.d_sine_group]
        for group, line in zip(groups, little_triangle_lines):
            self.play(ApplyMethod(
                line.rotate_in_place, np.pi/12,
                rate_func = wiggle,
                remover = True,
            ))
            self.play(
                group.scale, 1.2, group.get_corner(DOWN+RIGHT),
                group.set_color, YELLOW,
                rate_func = there_and_back,
            )
            self.wait()

        self.play(ReplacementTransform(
            little_triangle.copy().set_fill(opacity = 0), 
            big_triangle_copy,
            path_arc = np.pi/2,
            run_time = 2
        ))
        self.wait()
        self.play(
            ReplacementTransform(big_triangle_copy, big_triangle),
            Animation(self.angle_label)
        )
        self.wait()
        self.play(
            self.radial_line.rotate_in_place, np.pi/12,
            Animation(big_triangle),
            rate_func = wiggle,
        )
        self.wait()
        self.play(
            ReplacementTransform(
                big_triangle.copy().set_fill(opacity = 0), 
                little_triangle,
                path_arc = -np.pi/2,
                run_time = 3,
            ),
            ReplacementTransform(
                self.angle_label.copy(),
                new_angle_label,
                path_arc = -np.pi/2,
                run_time = 3,
            ),
        )
        self.play(
            new_angle_label.scale_in_place, 2,
            new_angle_label.set_color, RED,
            rate_func = there_and_back
        )
        self.wait()

    def analyze_ratios(self):
        d_ratio = TexMobject("\\frac{d(\\sin(\\theta))}{d\\theta} = ")
        VGroup(*d_ratio[:9]).set_color(GREEN)
        VGroup(*d_ratio[10:12]).set_color(MAROON_B)
        trig_ratio = TexMobject("\\frac{\\text{Adj.}}{\\text{Hyp.}}")
        VGroup(*trig_ratio[:4]).set_color(GREEN)
        VGroup(*trig_ratio[5:9]).set_color(MAROON_B)
        cos = TexMobject("= \\cos(\\theta)")
        cos.add_background_rectangle()

        group = VGroup(d_ratio, trig_ratio, cos)
        group.arrange()
        group.next_to(
            self.title, DOWN, 
            buff = MED_LARGE_BUFF, 
            aligned_edge = LEFT
        )

        for mob in group:
            self.play(Write(mob))
            self.wait()

class TryWithCos(Scene):
    def construct(self):
        words = TextMobject("What about $\\cos(\\theta)$?")
        words.set_color(YELLOW)
        self.play(Write(words))
        self.wait()

class NextVideo(TeacherStudentsScene):
    def construct(self):
        series = VideoSeries()
        next_video = series[3]
        series.to_edge(UP)

        d_sum = TexMobject("\\frac{d}{dx}(x^3 + x^2)")
        d_product = TexMobject("\\frac{d}{dx} \\sin(x)x^2")
        d_composition = TexMobject("\\frac{d}{dx} \\cos\\left(\\frac{1}{x}\\right)")

        group = VGroup(d_sum, d_product, d_composition)
        group.arrange(RIGHT, buff = 2*LARGE_BUFF)
        group.next_to(VGroup(*self.get_pi_creatures()), UP, buff = LARGE_BUFF)

        self.play(
            FadeIn(
                series,
                lag_ratio = 0.5,
                run_time = 3,
            ),
            *[
                ApplyMethod(pi.look_at, next_video)
                for pi in self.get_pi_creatures()
            ]
        )
        self.play(
            next_video.set_color, YELLOW,
            next_video.shift, MED_LARGE_BUFF*DOWN
        )
        self.wait()
        for mob in group:
            self.play(
                Write(mob, run_time = 1),
                *[
                    ApplyMethod(pi.look_at, mob)
                    for pi in self.get_pi_creatures()
                ]
            )
        self.wait(3)

class Chapter3PatreonThanks(PatreonThanks):
    CONFIG = {
        "specific_patrons" : [
            "Ali Yahya",
            "CrypticSwarm    ",
            "Yu  Jun",
            "Shelby  Doolittle",
            "Dave    Nicponski",
            "Damion  Kistler",
            "Juan    Benet",
            "Othman  Alikhan",
            "Markus  Persson",
            "Dan Buchoff",
            "Derek   Dai",
            "Joseph  John Cox",
            "Luc Ritchie",
            "Guido   Gambardella",
            "Jerry   Ling",
            "Mark    Govea",
            "Vecht   ",
            "Jonathan    Eppele",
            "Shimin Kuang",
            "Rish    Kundalia",
            "Achille Brighton",
            "Kirk    Werklund",
            "Ripta   Pasay",
            "Felipe  Diniz",
        ]
    }

class Promotion(PiCreatureScene):
    CONFIG = {
        "seconds_to_blink" : 5,
    }
    def construct(self):
        url = TextMobject("https://brilliant.org/3b1b/")
        url.to_corner(UP+LEFT)

        rect = Rectangle(height = 9, width = 16)
        rect.set_height(5.5)
        rect.next_to(url, DOWN)
        rect.to_edge(LEFT)

        self.play(
            Write(url),
            self.pi_creature.change, "raise_right_hand"
        )
        self.play(ShowCreation(rect))
        self.wait(2)
        self.change_mode("thinking")
        self.wait()
        self.look_at(url)
        self.wait(10)
        self.change_mode("happy")
        self.wait(10)
        self.change_mode("raise_right_hand")
        self.wait(10)

class Thumbnail(NudgeSideLengthOfCube):
    def construct(self):
        self.introduce_cube()
        VGroup(*self.get_mobjects()).to_edge(DOWN)

        formula = TexMobject(
            "\\frac{d(x^3)}{dx} = 3x^2"
        )
        VGroup(*formula[:5]).set_color(YELLOW)
        VGroup(*formula[-3:]).set_color(GREEN_B)
        formula.set_width(FRAME_X_RADIUS-1)
        formula.to_edge(RIGHT)
        self.add(formula)

        title = TextMobject("Geometric derivatives")
        title.set_width(FRAME_WIDTH-1)
        title.to_edge(UP)
        self.add(title)



















