<gh_stars>1-10
 # -*- coding: utf-8 -*-
from manim_imports_ext import *


def apply_function_to_center(point_func, mobject):
    mobject.apply_function_to_position(point_func)


def apply_function_to_submobjects(point_func, mobject):
    mobject.apply_function_to_submobject_positions(point_func)


def apply_function_to_points(point_func, mobject):
    mobject.apply_function(point_func)


def get_nested_one_plus_one_over_x(n_terms, bottom_term="x"):
    tex = "1+ {1 \\over" * n_terms + bottom_term + "}" * n_terms
    return Tex(tex, isolate=["1", "\\over", bottom_term])


def get_phi_continued_fraction(n_terms):
    return get_nested_one_plus_one_over_x(n_terms, bottom_term="1+\\cdots")


def get_nested_f(n_terms, arg="x"):
    terms = ["f("] * n_terms + [arg] + [")"] * n_terms
    return Tex(*terms)


# Scene types

class NumberlineTransformationScene(ZoomedScene):
    CONFIG = {
        "input_line_zero_point": 0.5 * UP + (FRAME_X_RADIUS - 1) * LEFT,
        "output_line_zero_point": 2 * DOWN + (FRAME_X_RADIUS - 1) * LEFT,
        "number_line_config": {
            "include_numbers": True,
            "x_min": -3.5,
            "x_max": 3.5,
            "unit_size": 2,
        },
        # These would override number_line_config
        "input_line_config": {
            "color": BLUE,
        },
        "output_line_config": {},
        "num_inserted_number_line_curves": 20,
        "default_delta_x": 0.1,
        "default_sample_dot_radius": 0.07,
        "default_sample_dot_colors": [RED, YELLOW],
        "default_mapping_animation_config": {
            "run_time": 3,
            # "path_arc": 30 * DEGREES,
        },
        "local_coordinate_num_decimal_places": 2,
        "zoom_factor": 0.1,
        "zoomed_display_height": 2.5,
        "zoomed_display_corner_buff": MED_SMALL_BUFF,
        "mini_line_scale_factor": 2,
        "default_coordinate_value_dx": 0.05,
        "zoomed_camera_background_rectangle_fill_opacity": 1.0,
    }

    def setup(self):
        ZoomedScene.setup(self)
        self.setup_number_lines()
        self.setup_titles()
        self.setup_zoomed_camera_background_rectangle()

    def setup_number_lines(self):
        number_lines = self.number_lines = VGroup()
        added_configs = (self.input_line_config, self.output_line_config)
        zero_opints = (self.input_line_zero_point, self.output_line_zero_point)
        for added_config, zero_point in zip(added_configs, zero_opints):
            full_config = dict(self.number_line_config)
            full_config.update(added_config)
            number_line = NumberLine(**full_config)
            number_line.insert_n_curves(
                self.num_inserted_number_line_curves
            )
            number_line.shift(zero_point - number_line.number_to_point(0))
            number_lines.add(number_line)
        self.input_line, self.output_line = number_lines

        self.add(number_lines)

    def setup_titles(self):
        input_title, output_title = self.titles = VGroup(*[
            TexText(word)
            for word in ("Inputs", "Outputs")
        ])
        vects = [UP, DOWN]
        for title, line, vect in zip(self.titles, self.number_lines, vects):
            title.next_to(line, vect, aligned_edge=LEFT)
            title.shift_onto_screen()

        self.add(self.titles)

    def setup_zoomed_camera_background_rectangle(self):
        frame = self.zoomed_camera.frame
        frame.next_to(self.camera.frame, UL)
        self.zoomed_camera_background_rectangle = BackgroundRectangle(
            frame, fill_opacity=self.zoomed_camera_background_rectangle_fill_opacity
        )
        self.zoomed_camera_background_rectangle_anim = UpdateFromFunc(
            self.zoomed_camera_background_rectangle,
            lambda m: m.replace(frame, stretch=True)
        )
        self.zoomed_camera_background_rectangle_group = VGroup(
            self.zoomed_camera_background_rectangle,
        )

    def get_sample_input_points(self, x_min=None, x_max=None, delta_x=None):
        x_min = x_min or self.input_line.x_min
        x_max = x_max or self.input_line.x_max
        delta_x = delta_x or self.default_delta_x
        return [
            self.get_input_point(x)
            for x in np.arange(x_min, x_max + delta_x, delta_x)
        ]

    def get_sample_dots(self, x_min=None, x_max=None,
                        delta_x=None, dot_radius=None, colors=None):
        dot_radius = dot_radius or self.default_sample_dot_radius
        colors = colors or self.default_sample_dot_colors

        dots = VGroup(*[
            Dot(point, radius=dot_radius)
            for point in self.get_sample_input_points(x_min, x_max, delta_x)
        ])
        dots.set_color_by_gradient(*colors)
        return dots

    def get_local_sample_dots(self, x, sample_radius=None, **kwargs):
        zoom_factor = self.get_zoom_factor()
        delta_x = kwargs.get("delta_x", self.default_delta_x * zoom_factor)
        dot_radius = kwargs.get("dot_radius", self.default_sample_dot_radius * zoom_factor)

        if sample_radius is None:
            unrounded_radius = self.zoomed_camera.frame.get_width() / 2
            sample_radius = int(unrounded_radius / delta_x) * delta_x
        config = {
            "x_min": x - sample_radius,
            "x_max": x + sample_radius,
            "delta_x": delta_x,
            "dot_radius": dot_radius,
        }
        config.update(kwargs)
        return self.get_sample_dots(**config)

    def add_sample_dot_ghosts(self, sample_dots, fade_factor=0.5):
        self.sample_dot_ghosts = sample_dots.copy()
        self.sample_dot_ghosts.fade(fade_factor)
        self.add(self.sample_dot_ghosts, sample_dots)

    def get_local_coordinate_values(self, x, dx=None, n_neighbors=1):
        dx = dx or self.default_coordinate_value_dx
        return [
            x + n * dx
            for n in range(-n_neighbors, n_neighbors + 1)
        ]

    # Mapping animations
    def get_mapping_animation(self, func, mobject,
                              how_to_apply_func=apply_function_to_center,
                              **kwargs):
        anim_config = dict(self.default_mapping_animation_config)
        anim_config.update(kwargs)

        point_func = self.number_func_to_point_func(func)

        mobject.generate_target(use_deepcopy=True)
        how_to_apply_func(point_func, mobject.target)
        return MoveToTarget(mobject, **anim_config)

    def get_line_mapping_animation(self, func, **kwargs):
        input_line_copy = self.input_line.deepcopy()
        self.moving_input_line = input_line_copy
        input_line_copy.remove(input_line_copy.numbers)
        # input_line_copy.set_stroke(width=2)
        input_line_copy.insert_n_curves(
            self.num_inserted_number_line_curves
        )
        return AnimationGroup(
            self.get_mapping_animation(
                func, input_line_copy,
                apply_function_to_points
            ),
            self.get_mapping_animation(
                func, input_line_copy.tick_marks,
                apply_function_to_submobjects
            ),
        )

    def get_sample_dots_mapping_animation(self, func, dots, **kwargs):
        return self.get_mapping_animation(
            func, dots, how_to_apply_func=apply_function_to_submobjects
        )

    def get_zoomed_camera_frame_mapping_animation(self, func, x=None, **kwargs):
        frame = self.zoomed_camera.frame
        if x is None:
            point = frame.get_center()
        else:
            point = self.get_input_point(x)
        point_mob = VectorizedPoint(point)
        return AnimationGroup(
            self.get_mapping_animation(func, point_mob),
            UpdateFromFunc(frame, lambda m: m.move_to(point_mob)),
        )

    def apply_function(self, func,
                       apply_function_to_number_line=True,
                       sample_dots=None,
                       local_sample_dots=None,
                       target_coordinate_values=None,
                       added_anims=None,
                       **kwargs
                       ):
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        frame = self.zoomed_camera.frame

        anims = []
        if apply_function_to_number_line:
            anims.append(self.get_line_mapping_animation(func))
        if hasattr(self, "mini_line"):  # Test for if mini_line is in self?
            anims.append(self.get_mapping_animation(
                func, self.mini_line,
                how_to_apply_func=apply_function_to_center
            ))
        if sample_dots:
            anims.append(
                self.get_sample_dots_mapping_animation(func, sample_dots)
            )
        if self.zoom_activated:
            zoom_anim = self.get_zoomed_camera_frame_mapping_animation(func)
            anims.append(zoom_anim)
            anims.append(zcbr_anim)

            zoom_anim.update(1)
            target_mini_line = Line(frame.get_left(), frame.get_right())
            target_mini_line.scale(self.mini_line_scale_factor)
            target_mini_line.match_style(self.output_line)
            zoom_anim.update(0)
            zcbr_group.submobjects.insert(1, target_mini_line)
        if target_coordinate_values:
            coordinates = self.get_local_coordinates(
                self.output_line,
                *target_coordinate_values
            )
            anims.append(FadeIn(coordinates))
            zcbr_group.add(coordinates)
            self.local_target_coordinates = coordinates
        if local_sample_dots:
            anims.append(
                self.get_sample_dots_mapping_animation(func, local_sample_dots)
            )
            zcbr_group.add(local_sample_dots)
        if added_anims:
            anims += added_anims
        anims.append(Animation(zcbr_group))

        self.play(*anims, **kwargs)

    # Zooming
    def zoom_in_on_input(self, x,
                         local_sample_dots=None,
                         local_coordinate_values=None,
                         pop_out=True,
                         first_added_anims=None,
                         first_anim_kwargs=None,
                         second_added_anims=None,
                         second_anim_kwargs=None,
                         zoom_factor=None,
                         ):

        first_added_anims = first_added_anims or []
        first_anim_kwargs = first_anim_kwargs or {}
        second_added_anims = second_added_anims or []
        second_anim_kwargs = second_anim_kwargs or {}
        input_point = self.get_input_point(x)

        # Decide how to move camera frame into place
        frame = self.zoomed_camera.frame
        frame.generate_target()
        frame.target.move_to(input_point)
        if zoom_factor:
            frame.target.set_height(
                self.zoomed_display.get_height() * zoom_factor
            )
        movement = MoveToTarget(frame)
        zcbr = self.zoomed_camera_background_rectangle
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        anims = []
        if self.zoom_activated:
            anims.append(movement)
            anims.append(zcbr_anim)
        else:
            movement.update(1)
            zcbr_anim.update(1)
            anims.append(self.get_zoom_in_animation())
            anims.append(FadeIn(zcbr))

        # Make sure frame is in final place
        for anim in anims:
            anim.update(1)

        # Add miniature number_line
        mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
        mini_line.scale(self.mini_line_scale_factor)
        mini_line.insert_n_curves(self.num_inserted_number_line_curves)
        mini_line.match_style(self.input_line)
        mini_line_copy = mini_line.copy()
        zcbr_group.add(mini_line_copy, mini_line)
        anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]

        # Add tiny coordinates
        if local_coordinate_values is None:
            local_coordinate_values = [x]
        local_coordinates = self.get_local_coordinates(
            self.input_line,
            *local_coordinate_values
        )
        anims.append(FadeIn(local_coordinates))
        zcbr_group.add(local_coordinates)
        self.local_coordinates = local_coordinates

        # Add tiny dots
        if local_sample_dots is not None:
            anims.append(LaggedStartMap(GrowFromCenter, local_sample_dots))
            zcbr_group.add(local_sample_dots)

        if first_added_anims:
            anims += first_added_anims

        anims.append(Animation(zcbr_group))
        if not pop_out:
            self.activate_zooming(animate=False)
        self.play(*anims, **first_anim_kwargs)

        if not self.zoom_activated and pop_out:
            self.activate_zooming(animate=False)
            added_anims = second_added_anims or []
            self.play(
                self.get_zoomed_display_pop_out_animation(),
                *added_anims,
                **second_anim_kwargs
            )

    def get_local_coordinates(self, line, *x_values, **kwargs):
        num_decimal_places = kwargs.get(
            "num_decimal_places", self.local_coordinate_num_decimal_places
        )
        result = VGroup()
        result.tick_marks = VGroup()
        result.numbers = VGroup()
        result.add(result.tick_marks, result.numbers)
        for x in x_values:
            tick_mark = Line(UP, DOWN)
            tick_mark.set_height(
                0.15 * self.zoomed_camera.frame.get_height()
            )
            tick_mark.move_to(line.number_to_point(x))
            result.tick_marks.add(tick_mark)

            number = DecimalNumber(x, num_decimal_places=num_decimal_places)
            number.scale(self.get_zoom_factor())
            number.scale(0.5)  # To make it seem small
            number.next_to(tick_mark, DOWN, buff=0.5 * number.get_height())
            result.numbers.add(number)
        return result

    def get_mobjects_in_zoomed_camera(self, mobjects):
        frame = self.zoomed_camera.frame
        x_min = frame.get_left()[0]
        x_max = frame.get_right()[0]
        y_min = frame.get_bottom()[1]
        y_max = frame.get_top()[1]
        result = VGroup()
        for mob in mobjects:
            for point in mob.get_all_points():
                if (x_min < point[0] < x_max) and (y_min < point[1] < y_max):
                    result.add(mob)
                    break
        return result

    # Helpers
    def get_input_point(self, x):
        return self.input_line.number_to_point(x)

    def get_output_point(self, fx):
        return self.output_line.number_to_point(fx)

    def number_func_to_point_func(self, number_func):
        input_line, output_line = self.number_lines

        def point_func(point):
            input_number = input_line.point_to_number(point)
            output_number = number_func(input_number)
            return output_line.number_to_point(output_number)
        return point_func


class ExampleNumberlineTransformationScene(NumberlineTransformationScene):
    CONFIG = {
        "number_line_config": {
            "x_min": 0,
            "x_max": 5,
            "unit_size": 2.0
        },
        "output_line_config": {
            "x_max": 20,
        },
    }

    def construct(self):
        func = lambda x: x**2
        x = 3
        dx = 0.05

        sample_dots = self.get_sample_dots()
        local_sample_dots = self.get_local_sample_dots(x)

        self.play(LaggedStartMap(GrowFromCenter, sample_dots))
        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=[x - dx, x, x + dx],
        )
        self.wait()
        self.apply_function(
            func,
            sample_dots=sample_dots,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=[func(x) - dx, func(x), func(x) + dx],
        )
        self.wait()


# Scenes


class WriteOpeningWords(Scene):
    def construct(self):
        raw_string1 = "Dear calculus student,"
        raw_string2 = "You're about to go through your first course. Like " + \
                      "any new topic, it will take some hard work to understand,"
        words1, words2 = [
            TexText("\\Large", *rs.split(" "))
            for rs in (raw_string1, raw_string2)
        ]
        words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF)
        words = VGroup(*it.chain(words1, words2))
        words.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
        words.to_edge(UP)

        letter_wait = 0.05
        word_wait = 2 * letter_wait
        comma_wait = 5 * letter_wait
        for word in words:
            self.play(LaggedStartMap(
                FadeIn, word,
                run_time=len(word) * letter_wait,
                lag_ratio=1.5 / len(word)
            ))
            self.wait(word_wait)
            if word.get_tex()[-1] == ",":
                self.wait(comma_wait)


class StartingCalc101(PiCreatureScene):
    CONFIG = {
        "camera_config": {"background_opacity": 1},
        "image_frame_width": 3.5,
        "image_frame_height": 2.5,
    }

    def construct(self):
        self.show_you()
        self.show_images()
        self.show_mystery_topic()

    def show_you(self):
        randy = self.pi_creature
        title = self.title = Title("Calculus 101")
        you = TexText("You")
        arrow = Vector(DL, color=WHITE)
        arrow.next_to(randy, UR)
        you.next_to(arrow.get_start(), UP)

        self.play(
            Write(you),
            GrowArrow(arrow),
            randy.change, "erm", title
        )
        self.wait()
        self.play(Write(title, run_time=1))
        self.play(FadeOut(VGroup(arrow, you)))

    def show_images(self):
        randy = self.pi_creature
        images = self.get_all_images()
        modes = [
            "pondering",  # hard_work_image
            "pondering",  # neat_example_image
            "hesitant",  # not_so_neat_example_image
            "hesitant",  # physics_image
            "horrified",  # piles_of_formulas_image
            "horrified",  # getting_stuck_image
            "thinking",  # aha_image
            "thinking",  # graphical_intuition_image
        ]

        for i, image, mode in zip(it.count(), images, modes):
            anims = []
            if hasattr(image, "fade_in_anim"):
                anims.append(image.fade_in_anim)
                anims.append(FadeIn(image.frame))
            else:
                anims.append(FadeIn(image))

            if i >= 3:
                image_to_fade_out = images[i - 3]
                if hasattr(image_to_fade_out, "fade_out_anim"):
                    anims.append(image_to_fade_out.fade_out_anim)
                else:
                    anims.append(FadeOut(image_to_fade_out))

            if hasattr(image, "continual_animations"):
                self.add(*image.continual_animations)

            anims.append(ApplyMethod(randy.change, mode))
            self.play(*anims)
            self.wait()
            if i >= 3:
                if hasattr(image_to_fade_out, "continual_animations"):
                    self.remove(*image_to_fade_out.continual_animations)
                self.remove(image_to_fade_out.frame)
        self.wait(3)

        self.remaining_images = images[-3:]

    def show_mystery_topic(self):
        images = self.remaining_images
        randy = self.pi_creature

        mystery_box = Rectangle(
            width=self.image_frame_width,
            height=self.image_frame_height,
            stroke_color=YELLOW,
            fill_color=GREY_D,
            fill_opacity=0.5,
        )
        mystery_box.scale(1.5)
        mystery_box.next_to(self.title, DOWN, MED_LARGE_BUFF)

        rects = images[-1].rects.copy()
        rects.center()
        rects.set_height(FRAME_HEIGHT - 1)
        # image = rects.get_image()
        open_cv_image = cv2.imread(get_full_raster_image_path("alt_calc_hidden_image"))
        blurry_iamge = cv2.blur(open_cv_image, (50, 50))
        array = np.array(blurry_iamge)[:, :, ::-1]
        im_mob = ImageMobject(array)
        im_mob.replace(mystery_box, stretch=True)
        mystery_box.add(im_mob)

        q_marks = Tex("???").scale(3)
        q_marks.space_out_submobjects(1.5)
        q_marks.set_stroke(BLACK, 1)
        q_marks.move_to(mystery_box)
        mystery_box.add(q_marks)

        for image in images:
            if hasattr(image, "continual_animations"):
                self.remove(*image.continual_animations)
            self.play(
                image.shift, DOWN,
                image.fade, 1,
                randy.change, "erm",
                run_time=1.5
            )
            self.remove(image)
        self.wait()
        self.play(
            FadeInFromDown(mystery_box),
            randy.change, "confused"
        )
        self.wait(5)

    # Helpers

    def get_all_images(self):
        # Images matched to narration's introductory list
        images = VGroup(
            self.get_hard_work_image(),
            self.get_neat_example_image(),
            self.get_not_so_neat_example_image(),
            self.get_physics_image(),
            self.get_piles_of_formulas_image(),
            self.get_getting_stuck_image(),
            self.get_aha_image(),
            self.get_graphical_intuition_image(),
        )
        colors = color_gradient([BLUE, YELLOW], len(images))
        for i, image, color in zip(it.count(), images, colors):
            self.adjust_size(image)
            frame = Rectangle(
                width=self.image_frame_width,
                height=self.image_frame_height,
                color=color,
                stroke_width=2,
            )
            frame.move_to(image)
            image.frame = frame
            image.add(frame)
            image.next_to(self.title, DOWN)
            alt_i = (i % 3) - 1
            vect = (self.image_frame_width + LARGE_BUFF) * RIGHT
            image.shift(alt_i * vect)
        return images

    def adjust_size(self, group):
        group.set_width(min(
            group.get_width(),
            self.image_frame_width - 2 * MED_SMALL_BUFF
        ))
        group.set_height(min(
            group.get_height(),
            self.image_frame_height - 2 * MED_SMALL_BUFF
        ))
        return group

    def get_hard_work_image(self):
        new_randy = self.pi_creature.copy()
        new_randy.change_mode("telepath")
        bubble = new_randy.get_bubble(height=3.5, width=4)
        bubble.add_content(Tex("\\frac{d}{dx}(\\sin(\\sqrt{x}))"))
        bubble.add(bubble.content)  # Remove?

        return VGroup(new_randy, bubble)

    def get_neat_example_image(self):
        filled_circle = Circle(
            stroke_width=0,
            fill_color=BLUE_E,
            fill_opacity=1
        )
        area = Tex("\\pi r^2")
        area.move_to(filled_circle)
        unfilled_circle = Circle(
            stroke_width=3,
            stroke_color=YELLOW,
            fill_opacity=0,
        )
        unfilled_circle.next_to(filled_circle, RIGHT)
        circles = VGroup(filled_circle, unfilled_circle)
        circumference = Tex("2\\pi r")
        circumference.move_to(unfilled_circle)
        equation = Tex(
            "{d (\\pi r^2) \\over dr}  = 2\\pi r",
            tex_to_color_map={
                "\\pi r^2": BLUE_D,
                "2\\pi r": YELLOW,
            }
        )
        equation.next_to(circles, UP)

        return VGroup(
            filled_circle, area,
            unfilled_circle, circumference,
            equation
        )

    def get_not_so_neat_example_image(self):
        return Tex("\\int x \\cos(x) \\, dx")

    def get_physics_image(self):
        t_max = 6.5
        r = 0.2
        spring = ParametricCurve(
            lambda t: op.add(
                r * (np.sin(TAU * t) * RIGHT + np.cos(TAU * t) * UP),
                t * DOWN,
            ),
            t_min=0, t_max=t_max,
            color=WHITE,
            stroke_width=2,
        )
        spring.color_using_background_image("grey_gradient")

        weight = Square()
        weight.set_stroke(width=0)
        weight.set_fill(opacity=1)
        weight.color_using_background_image("grey_gradient")
        weight.set_height(0.4)

        t_tracker = ValueTracker(0)
        group = VGroup(spring, weight)
        group.continual_animations = [
            t_tracker.add_udpater(
                lambda tracker, dt: tracker.set_value(
                    tracker.get_value() + dt
                )
            ),
            spring.add_updater(
                lambda s: s.stretch_to_fit_height(
                    1.5 + 0.5 * np.cos(3 * t_tracker.get_value()),
                    about_edge=UP
                )
            ),
            weight.add_updater(
                lambda w: w.move_to(spring.get_points()[-1])
            )
        ]

        def update_group_style(alpha):
            spring.set_stroke(width=2 * alpha)
            weight.set_fill(opacity=alpha)

        group.fade_in_anim = UpdateFromAlphaFunc(
            group,
            lambda g, a: update_group_style(a)
        )
        group.fade_out_anim = UpdateFromAlphaFunc(
            group,
            lambda g, a: update_group_style(1 - a)
        )
        return group

    def get_piles_of_formulas_image(self):
        return Tex("(f/g)' = \\frac{gf' - fg'}{g^2}")

    def get_getting_stuck_image(self):
        creature = self.pi_creature.copy()
        creature.change_mode("angry")
        equation = Tex("\\frac{d}{dx}(x^x)")
        equation.set_height(creature.get_height() / 2)
        equation.next_to(creature, RIGHT, aligned_edge=UP)
        creature.look_at(equation)
        return VGroup(creature, equation)

    def get_aha_image(self):
        creature = self.pi_creature.copy()
        creature.change_mode("hooray")
        from _2017.eoc.eoc.chapter3 import NudgeSideLengthOfCube
        scene = NudgeSideLengthOfCube(
            end_at_animation_number=7,
            skip_animations=True
        )
        group = VGroup(
            scene.cube, scene.faces,
            scene.bars, scene.corner_cube,
        )
        group.set_height(0.75 * creature.get_height())
        group.next_to(creature, RIGHT)
        creature.look_at(group)
        return VGroup(creature, group)

    def get_graphical_intuition_image(self):
        gs = GraphScene()
        gs.setup_axes()
        graph = gs.get_graph(
            lambda x: 0.2 * (x - 3) * (x - 5) * (x - 6) + 4,
            x_min=2, x_max=8,
        )
        rects = gs.get_riemann_rectangles(
            graph, x_min=2, x_max=8,
            stroke_width=0.5,
            dx=0.25
        )
        gs.add(graph, rects, gs.axes)
        group = VGroup(*gs.mobjects)
        self.adjust_size(group)
        group.next_to(self.title, DOWN, MED_LARGE_BUFF)
        group.rects = rects
        group.continual_animations = [
            turn_animation_into_updater(Write(rects)),
            turn_animation_into_updater(ShowCreation(graph)),
            turn_animation_into_updater(FadeIn(gs.axes)),
        ]
        self.adjust_size(group)
        return group


class GraphicalIntuitions(GraphScene):
    CONFIG = {
        "func": lambda x: 0.1 * (x - 2) * (x - 5) * (x - 7) + 4,
        "x_labeled_nums": list(range(1, 10)),
    }

    def construct(self):
        self.setup_axes()
        axes = self.axes
        graph = self.get_graph(self.func)

        ss_group = self.get_secant_slope_group(
            x=8, graph=graph, dx=0.01,
            secant_line_length=6,
            secant_line_color=RED,
        )
        rects = self.get_riemann_rectangles(
            graph, x_min=2, x_max=8, dx=0.01, stroke_width=0
        )

        deriv_text = TexText(
            "Derivative $\\rightarrow$ slope",
            tex_to_color_map={"slope": ss_group.secant_line.get_color()}
        )
        deriv_text.to_edge(UP)
        integral_text = TexText(
            "Integral $\\rightarrow$ area",
            tex_to_color_map={"area": rects[0].get_color()}
        )
        integral_text.next_to(deriv_text, DOWN)

        self.play(
            Succession(Write(axes), ShowCreation(graph, run_time=2)),
            self.get_graph_words_anim(),
        )
        self.animate_secant_slope_group_change(
            ss_group,
            target_x=2,
            rate_func=smooth,
            run_time=2.5,
            added_anims=[
                Write(deriv_text),
                VFadeIn(ss_group, run_time=2),
            ]
        )
        self.play(FadeIn(integral_text))
        self.play(
            LaggedStartMap(
                GrowFromEdge, rects,
                lambda r: (r, DOWN)
            ),
            Animation(axes),
            Animation(graph),
        )
        self.wait()

    def get_graph_words_anim(self):
        words = VGroup(
            TexText("Graphs,"),
            TexText("graphs,"),
            TexText("non-stop graphs"),
            TexText("all day"),
            TexText("every day"),
            TexText("as if to visualize is to graph"),
        )
        for word in words:
            word.add_background_rectangle()
        words.arrange(DOWN)
        words.to_edge(UP)
        return LaggedStartMap(
            FadeIn, words,
            rate_func=there_and_back,
            run_time=len(words) - 1,
            lag_ratio=0.6,
            remover=True
        )


class Wrapper(Scene):
    CONFIG = {
        "title": "",
        "title_kwargs": {},
        "screen_height": 6,
        "wait_time": 2,
    }

    def construct(self):
        rect = self.rect = ScreenRectangle(height=self.screen_height)
        title = self.title = TexText(self.title, **self.title_kwargs)
        title.to_edge(UP)
        rect.next_to(title, DOWN)

        self.add(title)
        self.play(ShowCreation(rect))
        self.wait(self.wait_time)


class DomainColoringWrapper(Wrapper):
    CONFIG = {
        "title": "Complex $\\rightarrow$ Complex",
    }


class R2ToR2Wrapper(Wrapper):
    CONFIG = {"title": "$\\mathds{R}^2 \\rightarrow \\mathds{R}^2$"}


class ExampleMultivariableFunction(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors": False,
        "show_coordinates": True,
    }

    def construct(self):
        def example_function(point):
            x, y, z = point
            return np.array([
                x + np.sin(y),
                y + np.sin(x),
                0
            ])
        self.wait()
        self.apply_nonlinear_transformation(example_function, run_time=5)
        self.wait()


class ChangingVectorFieldWrapper(Wrapper):
    CONFIG = {"title": "$(x, y, t) \\rightarrow (x', y')$"}


class ChangingVectorField(Scene):
    CONFIG = {
        "wait_time": 30,
    }

    def construct(self):
        plane = self.plane = NumberPlane()
        plane.set_stroke(width=2)
        plane.add_coordinates()
        self.add(plane)

        # Obviously a silly thing to do, but I'm sweeping
        # through trying to make sure old scenes don't
        # completely break in spots which used to have
        # Continual animations
        time_tracker = self.time_tracker = ValueTracker(0)
        time_tracker.add_updater(
            lambda t: t.set_value(self.get_time())
        )

        vectors = self.get_vectors()
        vectors.add_updater(self.update_vectors)
        self.add(vectors)
        self.wait(self.wait_time)

    def get_vectors(self):
        vectors = VGroup()
        x_max = int(np.ceil(FRAME_WIDTH))
        y_max = int(np.ceil(FRAME_HEIGHT))
        step = 0.5
        for x in np.arange(-x_max, x_max + 1, step):
            for y in np.arange(-y_max, y_max + 1, step):
                point = x * RIGHT + y * UP
                vectors.add(Vector(RIGHT).shift(point))
        vectors.set_color_by_gradient(YELLOW, RED)
        return vectors

    def update_vectors(self, vectors):
        time = self.time_tracker.get_value()
        for vector in vectors:
            point = vector.get_start()
            out_point = self.func(point, time)
            norm = get_norm(out_point)
            if norm == 0:
                out_point = RIGHT  # Fake it
                vector.set_fill(opacity=0)
            else:
                out_point *= 0.5
                color = interpolate_color(BLUE, RED, norm / np.sqrt(8))
                vector.set_fill(color, opacity=1)
                vector.set_stroke(BLACK, width=1)
            new_x, new_y = out_point[:2]
            vector.put_start_and_end_on(
                point, point + new_x * RIGHT + new_y * UP
            )

    def func(self, point, time):
        x, y, z = point
        return np.array([
            np.sin(time + 0.5 * x + y),
            np.cos(time + 0.2 * x * y + 0.7),
            0
        ])


class MoreTopics(Scene):
    def construct(self):
        calculus = TexText("Calculus")
        calculus.next_to(LEFT, LEFT)
        calculus.set_color(YELLOW)
        calculus.add_background_rectangle()
        others = VGroup(
            TexText("Multivariable calculus"),
            TexText("Complex analysis"),
            TexText("Differential geometry"),
            TexText("$\\vdots$")
        )
        for word in others:
            word.add_background_rectangle()
        others.arrange(
            DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT,
        )
        others.next_to(RIGHT, RIGHT)
        lines = VGroup(*[
            Line(calculus.get_right(), word.get_left(), buff=MED_SMALL_BUFF)
            for word in others
        ])

        rect = FullScreenFadeRectangle(fill_opacity=0.7)
        self.add(rect)

        self.add(calculus)
        self.play(
            LaggedStartMap(ShowCreation, lines),
            LaggedStartMap(Write, others),
        )
        self.wait()

        self.calculus = calculus
        self.lines = lines
        self.full_screen_rect = rect
        self.other_topics = others


class TransformationalViewWrapper(Wrapper):
    CONFIG = {
        "title": "Transformational view"
    }


class SetTheStage(TeacherStudentsScene):
    def construct(self):
        ordinary = TexText("Ordinary visual")
        transformational = TexText("Transformational visual")
        for word in ordinary, transformational:
            word.move_to(self.hold_up_spot, DOWN)
            word.shift_onto_screen()

        self.screen.scale(1.25, about_edge=UL)
        self.add(self.screen)
        self.teacher_holds_up(
            ordinary,
            added_anims=[self.get_student_changes(*3 * ["sassy"])]
        )
        self.wait()
        self.play(
            ordinary.shift, UP,
            FadeInFromDown(transformational),
            self.teacher.change, "hooray",
            self.get_student_changes(*3 * ["erm"])
        )
        self.wait(3)
        self.change_all_student_modes("pondering", look_at_arg=self.screen)


class StandardDerivativeVisual(GraphScene):
    CONFIG = {
        "y_max": 8,
        "y_axis_height": 5,
    }

    def construct(self):
        self.add_title()
        self.show_function_graph()
        self.show_slope_of_graph()
        self.encourage_not_to_think_of_slope_as_definition()
        self.show_sensitivity()

    def add_title(self):
        title = self.title = TexText("Standard derivative visual")
        title.to_edge(UP)
        h_line = Line(LEFT, RIGHT)
        h_line.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
        h_line.next_to(title, DOWN)

        self.add(title, h_line)

    def show_function_graph(self):
        self.setup_axes()

        def func(x):
            x -= 5
            return 0.1 * (x + 3) * (x - 3) * x + 3
        graph = self.get_graph(func)
        graph_label = self.get_graph_label(graph, x_val=9.5)

        input_tracker = ValueTracker(4)

        def get_x_value():
            return input_tracker.get_value()

        def get_y_value():
            return graph.underlying_function(get_x_value())

        def get_x_point():
            return self.coords_to_point(get_x_value(), 0)

        def get_y_point():
            return self.coords_to_point(0, get_y_value())

        def get_graph_point():
            return self.coords_to_point(get_x_value(), get_y_value())

        def get_v_line():
            return DashedLine(get_x_point(), get_graph_point(), stroke_width=2)

        def get_h_line():
            return DashedLine(get_graph_point(), get_y_point(), stroke_width=2)

        input_triangle = RegularPolygon(n=3, start_angle=TAU / 4)
        output_triangle = RegularPolygon(n=3, start_angle=0)
        for triangle in input_triangle, output_triangle:
            triangle.set_fill(WHITE, 1)
            triangle.set_stroke(width=0)
            triangle.scale(0.1)

        input_triangle_update = input_tracker.add_updater(
            lambda m: m.move_to(get_x_point(), UP)
        )
        output_triangle_update = output_triangle.add_updater(
            lambda m: m.move_to(get_y_point(), RIGHT)
        )

        x_label = Tex("x")
        x_label_update = Mobject.add_updater(
            x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF)
        )

        output_label = Tex("f(x)")
        output_label_update = Mobject.add_updater(
            output_label, lambda m: m.next_to(
                output_triangle, LEFT, SMALL_BUFF)
        )

        v_line = get_v_line()
        v_line_update = Mobject.add_updater(
            v_line, lambda vl: Transform(vl, get_v_line()).update(1)
        )

        h_line = get_h_line()
        h_line_update = Mobject.add_updater(
            h_line, lambda hl: Transform(hl, get_h_line()).update(1)
        )

        graph_dot = Dot(color=YELLOW)
        graph_dot_update = Mobject.add_updater(
            graph_dot, lambda m: m.move_to(get_graph_point())
        )

        self.play(
            ShowCreation(graph),
            Write(graph_label),
        )
        self.play(
            DrawBorderThenFill(input_triangle, run_time=1),
            Write(x_label),
            ShowCreation(v_line),
            GrowFromCenter(graph_dot),
        )
        self.add_foreground_mobject(graph_dot)
        self.play(
            ShowCreation(h_line),
            Write(output_label),
            DrawBorderThenFill(output_triangle, run_time=1)
        )
        self.add(
            input_triangle_update,
            x_label_update,
            graph_dot_update,
            v_line_update,
            h_line_update,
            output_triangle_update,
            output_label_update,
        )
        self.play(
            input_tracker.set_value, 8,
            run_time=6,
            rate_func=there_and_back
        )

        self.input_tracker = input_tracker
        self.graph = graph

    def show_slope_of_graph(self):
        input_tracker = self.input_tracker
        deriv_input_tracker = ValueTracker(input_tracker.get_value())

        # Slope line
        def get_slope_line():
            return self.get_secant_slope_group(
                x=deriv_input_tracker.get_value(),
                graph=self.graph,
                dx=0.01,
                secant_line_length=4
            ).secant_line

        slope_line = get_slope_line()
        slope_line_update = Mobject.add_updater(
            slope_line, lambda sg: Transform(sg, get_slope_line()).update(1)
        )

        def position_deriv_label(deriv_label):
            deriv_label.next_to(slope_line, UP)
            return deriv_label
        deriv_label = Tex(
            "\\frac{df}{dx}(x) =", "\\text{Slope}", "="
        )
        deriv_label.get_part_by_tex("Slope").match_color(slope_line)
        deriv_label_update = Mobject.add_updater(
            deriv_label, position_deriv_label
        )

        slope_decimal = DecimalNumber(slope_line.get_slope())
        slope_decimal.match_color(slope_line)
        slope_decimal.add_updater(
            lambda d: d.set_value(slope_line.get_slope())
        )
        slope_decimal.add_upater(
            lambda d: d.next_to(
                deriv_label, RIGHT, SMALL_BUFF
            ).shift(0.2 * SMALL_BUFF * DOWN)
        )

        self.play(
            ShowCreation(slope_line),
            Write(deriv_label),
            Write(slope_decimal),
            run_time=1
        )
        self.wait()
        self.add(
            slope_line_update,
            # deriv_label_update,
        )
        for x in 9, 2, 4:
            self.play(
                input_tracker.set_value, x,
                deriv_input_tracker.set_value, x,
                run_time=3
            )
            self.wait()

        self.deriv_input_tracker = deriv_input_tracker

    def encourage_not_to_think_of_slope_as_definition(self):
        morty = Mortimer(height=2)
        morty.to_corner(DR)

        self.play(FadeIn(morty))
        self.play(PiCreatureSays(
            morty, "Don't think of \\\\ this as the definition",
            bubble_kwargs={"height": 2, "width": 4}
        ))
        self.play(Blink(morty))
        self.wait()
        self.play(
            RemovePiCreatureBubble(morty),
            UpdateFromAlphaFunc(
                morty, lambda m, a: m.set_fill(opacity=1 - a),
                remover=True
            )
        )

    def show_sensitivity(self):
        input_tracker = self.input_tracker
        deriv_input_tracker = self.deriv_input_tracker

        self.wiggle_input()
        for x in 9, 7, 2:
            self.play(
                input_tracker.set_value, x,
                deriv_input_tracker.set_value, x,
                run_time=3
            )
            self.wiggle_input()

    ###
    def wiggle_input(self, dx=0.5, run_time=3):
        input_tracker = self.input_tracker

        x = input_tracker.get_value()
        x_min = x - dx
        x_max = x + dx
        y, y_min, y_max = list(map(
            self.graph.underlying_function,
            [x, x_min, x_max]
        ))
        x_line = Line(
            self.coords_to_point(x_min, 0),
            self.coords_to_point(x_max, 0),
        )
        y_line = Line(
            self.coords_to_point(0, y_min),
            self.coords_to_point(0, y_max),
        )

        x_rect, y_rect = rects = VGroup(Rectangle(), Rectangle())
        rects.set_stroke(width=0)
        rects.set_fill(YELLOW, 0.5)
        x_rect.match_width(x_line)
        x_rect.stretch_to_fit_height(0.25)
        x_rect.move_to(x_line)
        y_rect.match_height(y_line)
        y_rect.stretch_to_fit_width(0.25)
        y_rect.move_to(y_line)

        self.play(
            ApplyMethod(
                input_tracker.set_value, input_tracker.get_value() + dx,
                rate_func=lambda t: wiggle(t, 6)
            ),
            FadeIn(
                rects,
                rate_func=squish_rate_func(smooth, 0, 0.33),
                remover=True,
            ),
            run_time=run_time,
        )
        self.play(FadeOut(rects))


class EoCWrapper(Scene):
    def construct(self):
        title = Title("Essence of calculus")
        self.play(Write(title))
        self.wait()


class IntroduceTransformationView(NumberlineTransformationScene):
    CONFIG = {
        "func": lambda x: 0.5 * np.sin(2 * x) + x,
        "number_line_config": {
            "x_min": 0,
            "x_max": 6,
            "unit_size": 2.0
        },
    }

    def construct(self):
        self.add_title()
        self.show_animation_preview()
        self.indicate_indicate_point_densities()
        self.show_zoomed_transformation()

    def add_title(self):
        title = self.title = TexText("$f(x)$ as a transformation")
        title.to_edge(UP)
        self.add(title)

    def show_animation_preview(self):
        input_points = self.get_sample_input_points()
        output_points = list(map(
            self.number_func_to_point_func(self.func),
            input_points
        ))
        sample_dots = self.get_sample_dots()
        sample_dot_ghosts = sample_dots.copy().fade(0.5)
        arrows = VGroup(*[
            Arrow(ip, op, buff=MED_SMALL_BUFF)
            for ip, op in zip(input_points, output_points)
        ])
        arrows = arrows[1::3]
        arrows.set_stroke(BLACK, 1)

        for sd in sample_dots:
            sd.save_state()
            sd.scale(2)
            sd.fade(1)
        self.play(LaggedStartMap(
            ApplyMethod, sample_dots,
            lambda sd: (sd.restore,),
            run_time=2
        ))
        self.play(LaggedStartMap(
            GrowArrow, arrows,
            run_time=6,
            lag_ratio=0.3,
        ))
        self.add(sample_dot_ghosts)
        self.apply_function(
            self.func, sample_dots=sample_dots,
            run_time=3
        )
        self.wait()
        self.play(LaggedStartMap(FadeOut, arrows, run_time=1))

        self.sample_dots = sample_dots
        self.sample_dot_ghosts = sample_dot_ghosts

    def indicate_indicate_point_densities(self):
        lower_brace = Brace(Line(LEFT, RIGHT), UP)
        upper_brace = lower_brace.copy()
        input_tracker = ValueTracker(0.5)
        dx = 0.5

        def update_upper_brace(brace):
            x = input_tracker.get_value()
            line = Line(
                self.get_input_point(x),
                self.get_input_point(x + dx),
            )
            brace.match_width(line, stretch=True)
            brace.next_to(line, UP, buff=SMALL_BUFF)
            return brace

        def update_lower_brace(brace):
            x = input_tracker.get_value()
            line = Line(
                self.get_output_point(self.func(x)),
                self.get_output_point(self.func(x + dx)),
            )
            brace.match_width(line, stretch=True)
            brace.next_to(line, UP, buff=SMALL_BUFF)
            return brace

        lower_brace_anim = UpdateFromFunc(lower_brace, update_lower_brace)
        upper_brace_anim = UpdateFromFunc(upper_brace, update_upper_brace)

        new_title = TexText(
            "$\\frac{df}{dx}(x)$ measures stretch/squishing"
        )
        new_title.move_to(self.title, UP)

        stretch_factor = DecimalNumber(0, color=YELLOW)
        stretch_factor_anim = ChangingDecimal(
            stretch_factor, lambda a: lower_brace.get_width() / upper_brace.get_width(),
            position_update_func=lambda m: m.next_to(lower_brace, UP, SMALL_BUFF)
        )

        self.play(
            GrowFromCenter(upper_brace),
            FadeOut(self.title),
            # FadeIn(new_title)
            Write(new_title, run_time=2)
        )
        self.title = new_title
        self.play(
            ReplacementTransform(upper_brace.copy(), lower_brace),
            GrowFromPoint(stretch_factor, upper_brace.get_center())
        )
        self.play(
            input_tracker.set_value, self.input_line.x_max - dx,
            lower_brace_anim,
            upper_brace_anim,
            stretch_factor_anim,
            run_time=8,
            rate_func=bezier([0, 0, 1, 1])
        )
        self.wait()

        new_sample_dots = self.get_sample_dots()
        self.play(
            FadeOut(VGroup(
                upper_brace, lower_brace, stretch_factor,
                self.sample_dots, self.moving_input_line,
            )),
            FadeIn(new_sample_dots),
        )
        self.sample_dots = new_sample_dots

    def show_zoomed_transformation(self):
        x = 2.75
        local_sample_dots = self.get_local_sample_dots(x)

        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=self.get_local_coordinate_values(x),
        )
        self.wait()
        self.apply_function(
            self.func,
            sample_dots=self.sample_dots,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=self.get_local_coordinate_values(self.func(x))
        )
        self.wait()


class ExamplePlease(TeacherStudentsScene):
    def construct(self):
        self.student_says("Example?", student_index=0)
        self.teacher_holds_up(Tex("f(x) = x^2").scale(1.5))
        self.wait(2)


class TalkThroughXSquaredExample(IntroduceTransformationView):
    CONFIG = {
        "func": lambda x: x**2,
        "number_line_config": {
            "x_min": 0,
            "x_max": 5,
            "unit_size": 1.25,
        },
        "output_line_config": {
            "x_max": 25,
        },
        "default_delta_x": 0.2
    }

    def construct(self):
        self.add_title()
        self.show_specific_points_mapping()

    def add_title(self):
        title = self.title = TexText("$f(x) = x^2$")
        title.to_edge(UP, buff=MED_SMALL_BUFF)
        self.add(title)

    def show_specific_points_mapping(self):
        # First, just show integers as examples
        int_dots = self.get_sample_dots(1, 6, 1)
        int_dot_ghosts = int_dots.copy().fade(0.5)
        int_arrows = VGroup(*[
            Arrow(
                # num.get_bottom(),
                self.get_input_point(x),
                self.get_output_point(self.func(x)),
                buff=MED_SMALL_BUFF
            )
            for x, num in zip(list(range(1, 6)), self.input_line.numbers[1:])
        ])
        point_func = self.number_func_to_point_func(self.func)

        numbers = self.input_line.numbers
        numbers.next_to(self.input_line, UP, SMALL_BUFF)
        self.titles[0].next_to(numbers, UP, MED_SMALL_BUFF, LEFT)
        # map(Tex.add_background_rectangle, numbers)
        # self.add_foreground_mobject(numbers)

        for dot, dot_ghost, arrow in zip(int_dots, int_dot_ghosts, int_arrows):
            arrow.match_color(dot)
            self.play(DrawBorderThenFill(dot, run_time=1))
            self.add(dot_ghost)
            self.play(
                GrowArrow(arrow),
                dot.apply_function_to_position, point_func
            )
        self.wait()

        # Show more sample_dots
        sample_dots = self.get_sample_dots()
        sample_dot_ghosts = sample_dots.copy().fade(0.5)

        self.play(
            LaggedStartMap(DrawBorderThenFill, sample_dots),
            LaggedStartMap(FadeOut, int_arrows),
        )
        self.remove(int_dot_ghosts)
        self.add(sample_dot_ghosts)
        self.apply_function(self.func, sample_dots=sample_dots)
        self.remove(int_dots)
        self.wait()

        self.sample_dots = sample_dots
        self.sample_dot_ghosts = sample_dot_ghosts

    def get_stretch_words(self, factor, color=RED, less_than_one=False):
        factor_str = "$%s$" % str(factor)
        result = TexText(
            "Scale \\\\ by", factor_str,
            tex_to_color_map={factor_str: color}
        )
        result.scale(0.7)
        la, ra = Tex("\\leftarrow \\rightarrow")
        if less_than_one:
            la, ra = ra, la
        if factor < 0:
            kwargs = {
                "path_arc": np.pi,
            }
            la = Arrow(UP, DOWN, **kwargs)
            ra = Arrow(DOWN, UP, **kwargs)
            for arrow in la, ra:
                arrow.pointwise_become_partial(arrow, 0, 0.9)
                arrow.tip.scale(2)
            VGroup(la, ra).match_height(result)
        la.next_to(result, LEFT)
        ra.next_to(result, RIGHT)
        result.add(la, ra)
        result.next_to(
            self.zoomed_display.get_top(), DOWN, SMALL_BUFF
        )
        return result

    def get_deriv_equation(self, x, rhs, color=RED):
        deriv_equation = self.deriv_equation = Tex(
            "\\frac{df}{dx}(", str(x), ")", "=", str(rhs),
            tex_to_color_map={str(x): color, str(rhs): color}
        )
        deriv_equation.next_to(self.title, DOWN, MED_LARGE_BUFF)
        return deriv_equation


class ZoomInOnXSquaredNearOne(TalkThroughXSquaredExample):
    def setup(self):
        TalkThroughXSquaredExample.setup(self)
        self.force_skipping()
        self.add_title()
        self.show_specific_points_mapping()
        self.revert_to_original_skipping_status()

    def construct(self):
        zoom_words = TexText("Zoomed view \\\\ near 1")
        zoom_words.next_to(self.zoomed_display, DOWN)
        # zoom_words.shift_onto_screen()

        x = 1
        local_sample_dots = self.get_local_sample_dots(x)
        local_coords = self.get_local_coordinate_values(x, dx=0.1)

        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        zcbr_group = self.zoomed_camera_background_rectangle_group
        frame = self.zoomed_camera.frame
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()

        self.zoom_in_on_input(x, local_sample_dots, local_coords)
        self.play(FadeIn(zoom_words))
        self.wait()
        local_sample_dots.save_state()
        frame.save_state()
        self.mini_line.save_state()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coords
        )
        self.remove(sample_dot_ghost_copies)
        self.wait()

        # Go back
        self.play(
            frame.restore,
            self.mini_line.restore,
            local_sample_dots.restore,
            zcbr_anim,
            Animation(zcbr_group)
        )
        self.wait()

        # Zoom in even more
        extra_zoom_factor = 0.3
        one_group = VGroup(
            self.local_coordinates.tick_marks[1],
            self.local_coordinates.numbers[1],
        )
        all_other_coordinates = VGroup(
            self.local_coordinates.tick_marks[::2],
            self.local_coordinates.numbers[::2],
            self.local_target_coordinates,
        )
        self.play(frame.scale, extra_zoom_factor)
        new_local_sample_dots = self.get_local_sample_dots(x, delta_x=0.005)
        new_coordinate_values = self.get_local_coordinate_values(x, dx=0.02)
        new_local_coordinates = self.get_local_coordinates(
            self.input_line, *new_coordinate_values
        )

        self.play(
            Write(new_local_coordinates),
            Write(new_local_sample_dots),
            one_group.scale, extra_zoom_factor, {"about_point": self.get_input_point(1)},
            FadeOut(all_other_coordinates),
            *[
                ApplyMethod(dot.scale, extra_zoom_factor)
                for dot in local_sample_dots
            ]
        )
        self.remove(one_group, local_sample_dots)
        zcbr_group.remove(
            self.local_coordinates, self.local_target_coordinates,
            local_sample_dots
        )

        # Transform new zoomed view
        stretch_by_two_words = self.get_stretch_words(2)
        self.add_foreground_mobject(stretch_by_two_words)
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=new_local_sample_dots,
            target_coordinate_values=new_coordinate_values,
            added_anims=[FadeIn(stretch_by_two_words)]
        )
        self.remove(sample_dot_ghost_copies)
        self.wait()

        # Write derivative
        deriv_equation = self.get_deriv_equation(1, 2, color=RED)
        self.play(Write(deriv_equation))
        self.wait()


class ZoomInOnXSquaredNearThree(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoomed_display_width": 4,
    }

    def construct(self):
        zoom_words = TexText("Zoomed view \\\\ near 3")
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 3
        local_sample_dots = self.get_local_sample_dots(x)
        local_coordinate_values = self.get_local_coordinate_values(x, dx=0.1)
        target_coordinate_values = self.get_local_coordinate_values(self.func(x), dx=0.1)

        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words(2 * x, color)
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            pop_out=False,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values,
            added_anims=[Write(stretch_words)]
        )
        self.wait(2)


class ZoomInOnXSquaredNearOneFourth(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoom_factor": 0.01,
        "local_coordinate_num_decimal_places": 4,
        "zoomed_display_width": 4,
        "default_delta_x": 0.25,
    }

    def construct(self):
        # Much copy-pasting from previous scenes.  Not great, but
        # the fastest way to get the ease-of-tweaking I'd like.
        zoom_words = TexText("Zoomed view \\\\ near $1/4$")
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 0.25
        local_sample_dots = self.get_local_sample_dots(
            x, sample_radius=2.5 * self.zoomed_camera.frame.get_width(),
        )
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=0.01,
        )
        target_coordinate_values = self.get_local_coordinate_values(
            self.func(x), dx=0.01,
        )

        color = RED
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words("1/2", color, less_than_one=True)
        deriv_equation = self.get_deriv_equation("1/4", "1/2", color)

        one_fourth_point = self.get_input_point(x)
        one_fourth_arrow = Vector(0.5 * UP, color=WHITE)
        one_fourth_arrow.stem.stretch(0.75, 0)
        one_fourth_arrow.tip.scale(0.75, about_edge=DOWN)
        one_fourth_arrow.next_to(one_fourth_point, DOWN, SMALL_BUFF)
        one_fourth_label = Tex("0.25")
        one_fourth_label.match_height(self.input_line.numbers)
        one_fourth_label.next_to(one_fourth_arrow, DOWN, SMALL_BUFF)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
            pop_out=False,
            first_added_anims=[
                FadeIn(one_fourth_label),
                GrowArrow(one_fourth_arrow),
            ]
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values,
            added_anims=[Write(stretch_words)]
        )
        self.wait(2)


class ZoomInOnXSquaredNearZero(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoom_factor": 0.1,
        "zoomed_display_width": 4,
        "scale_by_term": "???",
    }

    def construct(self):
        zoom_words = TexText(
            "Zoomed %sx \\\\ near 0" % "{:,}".format(int(1.0 / self.zoom_factor))
        )
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 0
        local_sample_dots = self.get_local_sample_dots(
            x, sample_radius=2 * self.zoomed_camera.frame.get_width()
        )
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=self.zoom_factor
        )
        # target_coordinate_values = self.get_local_coordinate_values(
        #     self.func(x), dx=self.zoom_factor
        # )

        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words(
            self.scale_by_term, color, less_than_one=True
        )
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            pop_out=False,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            # target_coordinate_values=target_coordinate_values,
            added_anims=[
                Write(stretch_words),
                MaintainPositionRelativeTo(
                    self.local_coordinates,
                    self.zoomed_camera.frame
                )
            ]
        )
        self.wait(2)


class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero):
    def construct(self):
        x = 0
        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)
        self.add(deriv_equation)

        frame = self.zoomed_camera.frame
        zoomed_display_height = self.zoomed_display.get_height()

        last_sample_dots = VGroup()
        last_coords = VGroup()
        last_zoom_words = None
        for factor in 0.1, 0.01, 0.001, 0.0001:
            frame.save_state()
            frame.set_height(factor * zoomed_display_height)
            self.local_coordinate_num_decimal_places = int(-np.log10(factor))
            zoom_words = TexText(
                "Zoomed", "{:,}x \\\\".format(int(1.0 / factor)),
                "near 0",
            )
            zoom_words.next_to(self.zoomed_display, DOWN)

            sample_dots = self.get_local_sample_dots(x)
            coords = self.get_local_coordinate_values(x, dx=factor)
            frame.restore()

            added_anims = [
                ApplyMethod(last_sample_dots.fade, 1),
                ApplyMethod(last_coords.fade, 1),
            ]
            if last_zoom_words is not None:
                added_anims.append(ReplacementTransform(
                    last_zoom_words, zoom_words
                ))
            else:
                added_anims.append(FadeIn(zoom_words))
            self.zoom_in_on_input(
                x,
                local_sample_dots=sample_dots,
                local_coordinate_values=coords,
                pop_out=False,
                zoom_factor=factor,
                first_added_anims=added_anims
            )
            self.wait()
            last_sample_dots = sample_dots
            last_coords = self.local_coordinates
            last_zoom_words = zoom_words


class ZoomInOnXSquared100xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.01
    }


class ZoomInOnXSquared1000xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.001,
        "local_coordinate_num_decimal_places": 3,
    }


class ZoomInOnXSquared10000xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.0001,
        "local_coordinate_num_decimal_places": 4,
        "scale_by_term": "0",
    }


class XSquaredForNegativeInput(TalkThroughXSquaredExample):
    CONFIG = {
        "input_line_config": {
            "x_min": -4,
            "x_max": 4,
        },
        "input_line_zero_point": 0.5 * UP + 0 * LEFT,
        "output_line_config": {},
        "default_mapping_animation_config": {
            "path_arc": 30 * DEGREES
        },
        "zoomed_display_width": 4,
    }

    def construct(self):
        self.add_title()
        self.show_full_transformation()
        self.zoom_in_on_example()

    def show_full_transformation(self):
        sample_dots = self.get_sample_dots(
            x_min=-4.005,
            delta_x=0.05,
            dot_radius=0.05
        )
        sample_dots.set_fill(opacity=0.8)

        self.play(LaggedStartMap(DrawBorderThenFill, sample_dots))
        self.play(LaggedStartMap(
            ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1],
            lambda mob: (
                lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK),
                mob,
            ),
            rate_func=there_and_back,
        ))
        self.add_sample_dot_ghosts(sample_dots)
        self.apply_function(self.func, sample_dots=sample_dots)
        self.wait()

    def zoom_in_on_example(self):
        x = -2

        local_sample_dots = self.get_local_sample_dots(x)
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=0.1
        )
        target_coordinate_values = self.get_local_coordinate_values(
            self.func(x), dx=0.1
        )
        deriv_equation = self.get_deriv_equation(x, 2 * x, color=BLUE)
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        scale_words = self.get_stretch_words(-4, color=BLUE)

        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
        )
        self.wait()
        self.play(Write(deriv_equation))
        self.add_foreground_mobject(scale_words)
        self.play(Write(scale_words))
        self.apply_function(
            self.func,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values
        )
        self.wait()


class FeelsALittleCramped(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Kind of cramped,\\\\ isn't it?",
            target_mode="sassy"
        )
        self.wait()
        self.teacher_says(
            "Sure, but think \\\\ locally"
        )
        self.change_all_student_modes("pondering", look_at_arg=self.screen)
        self.wait(3)


class HowDoesThisSolveProblems(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Is this...useful?",
            target_mode="confused"
        )
        self.change_student_modes("maybe", "confused", "sassy")
        self.play(self.teacher.change, "happy")
        self.wait(3)


class IntroduceContinuedFractionPuzzle(PiCreatureScene):
    CONFIG = {
        "remove_initial_rhs": True,
    }

    def construct(self):
        self.ask_question()
        self.set_equal_to_x()

    def create_pi_creatures(self):
        morty = Mortimer(height=2)
        morty.to_corner(DR)

        friend = PiCreature(color=GREEN, height=2)
        friend.to_edge(DOWN)
        friend.shift(LEFT)

        group = VGroup(morty, friend)
        group.shift(2 * LEFT)

        return morty, friend

    def ask_question(self):
        morty, friend = self.pi_creatures
        frac = get_phi_continued_fraction(9)
        frac.scale(0.8)
        rhs = DecimalNumber(
            (1 - np.sqrt(5)) / 2.0,
            num_decimal_places=5,
            show_ellipsis=True,
        )
        rhs.set_color(YELLOW)
        equals = Tex("=")
        equals.next_to(frac.get_part_by_tex("\\over"), RIGHT)
        rhs.next_to(equals, RIGHT)
        group = VGroup(frac, equals, rhs)
        group.scale(1.5)
        group.to_corner(UR)

        self.play(
            LaggedStartMap(
                Write, frac,
                run_time=15,
                lag_ratio=0.15,
            ),
            FadeInFromDown(equals),
            FadeInFromDown(rhs),
            PiCreatureSays(
                friend, "Would this be valid? \\\\ If not, why not?",
                target_mode="confused",
                look_at_arg=frac,
                bubble_kwargs={
                    "direction": RIGHT,
                    "width": 4,
                    "height": 3,
                }
            ),
            morty.change, "pondering",
        )
        self.wait()

        anims = [
            RemovePiCreatureBubble(
                friend, target_mode="pondering",
                look_at_arg=frac
            ),
        ]
        if self.remove_initial_rhs:
            anims += [
                Animation(frac),
                FadeOut(equals),
                rhs.scale, 0.5,
                rhs.to_corner, DL,
            ]
        self.play(*anims)

        self.neg_one_over_phi = rhs
        self.equals = equals
        self.frac = frac

    def set_equal_to_x(self):
        frac = self.frac
        morty, friend = self.get_pi_creatures()

        inner_frac = frac[4:]
        inner_frac_rect = SurroundingRectangle(
            inner_frac, stroke_width=2, buff=0.5 * SMALL_BUFF
        )
        inner_frac_group = VGroup(inner_frac, inner_frac_rect)

        equals = Tex("=")
        equals.next_to(frac[3], RIGHT)
        x, new_x = [Tex("x") for i in range(2)]
        xs = VGroup(x, new_x)
        xs.set_color(YELLOW)
        xs.scale(1.3)
        x.next_to(equals, RIGHT)
        new_x.next_to(frac[3], DOWN, 2 * SMALL_BUFF)

        fixed_point_words = VGroup(
            TexText("Fixed point of"),
            Tex(
                "f(x) = 1 + \\frac{1}{x}",
                tex_to_color_map={"x": YELLOW}
            )
        )
        fixed_point_words.arrange(DOWN)

        self.play(Write(x), Write(equals))
        self.wait()
        self.play(ShowCreation(inner_frac_rect))
        self.wait()
        self.play(
            inner_frac_group.scale, 0.75,
            inner_frac_group.center,
            inner_frac_group.to_edge, LEFT,
            ReplacementTransform(
                x.copy(), new_x,
                path_arc=-90 * DEGREES
            )
        )
        self.wait()
        self.play(
            frac[3].stretch, 0.1, 0, {"about_edge": RIGHT},
            MaintainPositionRelativeTo(
                VGroup(frac[2], new_x), frac[3]
            ),
            UpdateFromFunc(
                frac[:2], lambda m: m.next_to(frac[3], LEFT)
            )
        )
        self.wait()
        fixed_point_words.next_to(VGroup(frac[0], xs), DOWN, LARGE_BUFF)
        self.play(
            Write(fixed_point_words),
            morty.change, "hooray",
            friend.change, "happy"
        )
        self.wait(3)


class GraphOnePlusOneOverX(GraphScene):
    CONFIG = {
        "x_min": -6,
        "x_max": 6,
        "x_axis_width": 12,
        "y_min": -4,
        "y_max": 5,
        "y_axis_height": 8,
        "y_axis_label": None,
        "graph_origin": 0.5 * DOWN,
        "num_graph_anchor_points": 100,
        "func_graph_color": GREEN,
        "identity_graph_color": BLUE,
    }

    def construct(self):
        self.add_title()
        self.setup_axes()
        self.draw_graphs()
        self.show_solutions()

    def add_title(self):
        title = self.title = Tex(
            "\\text{Solve: }", "1 + \\frac{1}{x}", "=", "x",
        )
        title.set_color_by_tex("x", self.identity_graph_color, substring=False)
        title.set_color_by_tex("frac", self.func_graph_color)
        title.to_corner(UL)
        self.add(title)

    def setup_axes(self):
        GraphScene.setup_axes(self)
        step = 2
        self.x_axis.add_numbers(*list(range(-6, 0, step)) + list(range(step, 7, step)))
        self.y_axis.label_direction = RIGHT
        self.y_axis.add_numbers(*list(range(-2, 0, step)) + list(range(step, 4, step)))

    def draw_graphs(self, animate=True):
        lower_func_graph, upper_func_graph = func_graph = VGroup(*[
            self.get_graph(
                lambda x: 1.0 + 1.0 / x,
                x_min=x_min,
                x_max=x_max,
                color=self.func_graph_color,
            )
            for x_min, x_max in [(-10, -0.1), (0.1, 10)]
        ])
        func_graph.label = self.get_graph_label(
            upper_func_graph, "y = 1 + \\frac{1}{x}",
            x_val=6, direction=UP,
        )

        identity_graph = self.get_graph(
            lambda x: x, color=self.identity_graph_color
        )
        identity_graph.label = self.get_graph_label(
            identity_graph, "y = x",
            x_val=3, direction=UL, buff=SMALL_BUFF
        )

        if animate:
            for graph in func_graph, identity_graph:
                self.play(
                    ShowCreation(graph),
                    Write(graph.label),
                    run_time=2
                )
            self.wait()
        else:
            self.add(
                func_graph, func_graph.label,
                identity_graph, identity_graph.label,
            )

        self.func_graph = func_graph
        self.identity_graph = identity_graph

    def show_solutions(self):
        phi = 0.5 * (1 + np.sqrt(5))
        phi_bro = 0.5 * (1 - np.sqrt(5))

        lines = VGroup()
        for num in phi, phi_bro:
            line = DashedLine(
                self.coords_to_point(num, 0),
                self.coords_to_point(num, num),
                color=WHITE
            )
            line_copy = line.copy()
            line_copy.set_color(YELLOW)
            line.fade(0.5)
            line_anim = ShowCreationThenDestruction(
                line_copy,
                lag_ratio=0.5,
                run_time=2
            )
            cycle_animation(line_anim)
            lines.add(line)

        phi_line, phi_bro_line = lines

        decimal_kwargs = {
            "num_decimal_places": 3,
            "show_ellipsis": True,
            "color": YELLOW,
        }
        arrow_kwargs = {
            "buff": SMALL_BUFF,
            "color": WHITE,
            "tip_length": 0.15,
            "rectangular_stem_width": 0.025,
        }

        phi_decimal = DecimalNumber(phi, **decimal_kwargs)
        phi_decimal.next_to(phi_line, DOWN, LARGE_BUFF)
        phi_arrow = Arrow(
            phi_decimal[:4].get_top(), phi_line.get_bottom(),
            **arrow_kwargs
        )
        phi_label = Tex("=", "\\varphi")
        phi_label.next_to(phi_decimal, RIGHT)
        phi_label.set_color_by_tex("\\varphi", YELLOW)

        phi_bro_decimal = DecimalNumber(phi_bro, **decimal_kwargs)
        phi_bro_decimal.next_to(phi_bro_line, UP, LARGE_BUFF)
        phi_bro_decimal.shift(0.5 * LEFT)
        phi_bro_arrow = Arrow(
            phi_bro_decimal[:6].get_bottom(), phi_bro_line.get_top(),
            **arrow_kwargs
        )

        brother_words = TexText(
            "$\\varphi$'s little brother",
            tex_to_color_map={"$\\varphi$": YELLOW},
            arg_separator=""
        )
        brother_words.next_to(
            phi_bro_decimal[-2], UP, buff=MED_SMALL_BUFF,
            aligned_edge=RIGHT
        )

        self.add(phi_line.continual_anim)
        self.play(ShowCreation(phi_line))
        self.play(
            Write(phi_decimal),
            GrowArrow(phi_arrow),
        )
        self.play(Write(phi_label))
        self.wait(3)
        self.add(phi_bro_line.continual_anim)
        self.play(ShowCreation(phi_bro_line))
        self.play(
            Write(phi_bro_decimal),
            GrowArrow(phi_bro_arrow),
        )
        self.wait(4)
        self.play(Write(brother_words))
        self.wait(8)


class ThinkAboutWithRepeatedApplication(IntroduceContinuedFractionPuzzle):
    CONFIG = {
        "remove_initial_rhs": False,
    }

    def construct(self):
        self.force_skipping()
        self.ask_question()
        self.revert_to_original_skipping_status()

        self.obviously_not()
        self.ask_about_fraction()
        self.plug_func_into_self()

    def obviously_not(self):
        morty, friend = self.get_pi_creatures()
        friend.change_mode("confused")
        randy = Randolph()
        randy.match_height(morty)
        randy.to_corner(DL)

        frac = self.frac
        rhs = self.neg_one_over_phi
        plusses = frac[1::4]
        plus_rects = VGroup(*[
            SurroundingRectangle(plus, buff=0) for plus in plusses
        ])
        plus_rects.set_color(PINK)

        self.play(FadeIn(randy))
        self.play(
            PiCreatureSays(
                randy, "Obviously not!",
                bubble_kwargs={"width": 3, "height": 2},
                target_mode="angry",
                run_time=1,
            ),
            morty.change, "guilty",
            friend.change, "hesitant"
        )
        self.wait()
        self.play(
            Animation(frac),
            RemovePiCreatureBubble(randy, target_mode="sassy"),
            morty.change, "confused",
            friend.change, "confused",
        )
        self.play(LaggedStartMap(
            ShowCreationThenDestruction, plus_rects,
            run_time=2,
            lag_ratio=0.35,
        ))
        self.play(WiggleOutThenIn(rhs))
        self.wait(2)
        self.play(
            frac.scale, 0.7,
            frac.to_corner, UL,
            FadeOut(self.equals),
            rhs.scale, 0.5,
            rhs.center,
            rhs.to_edge, LEFT,
            FadeOut(randy),
            morty.change, "pondering",
            friend.change, "pondering",
        )

    def ask_about_fraction(self):
        frac = self.frac
        arrow = Vector(LEFT, color=RED)
        arrow.next_to(frac, RIGHT)
        question = TexText("What does this \\\\ actually mean?")
        question.set_color(RED)
        question.next_to(arrow, RIGHT)

        self.play(
            LaggedStartMap(FadeIn, question, run_time=1),
            GrowArrow(arrow),
            LaggedStartMap(
                ApplyMethod, frac,
                lambda m: (m.set_color, RED),
                rate_func=there_and_back,
                lag_ratio=0.2,
                run_time=2
            )
        )
        self.wait()
        self.play(FadeOut(question), FadeOut(arrow))

    def plug_func_into_self(self, value=1, value_str="1"):
        morty, friend = self.pi_creatures

        def func(x):
            return 1 + 1.0 / x

        lines = VGroup()
        value_labels = VGroup()
        for n_terms in range(5):
            lhs = get_nested_f(n_terms, arg="c")
            equals = Tex("=")
            rhs = get_nested_one_plus_one_over_x(n_terms, bottom_term=value_str)
            equals.next_to(rhs[0], LEFT)
            lhs.next_to(equals, LEFT)
            lines.add(VGroup(lhs, equals, rhs))

            value_label = Tex("= %.3f\\dots" % value)
            value = func(value)
            value_labels.add(value_label)

        lines.arrange(
            DOWN, buff=MED_LARGE_BUFF,
        )
        VGroup(lines, value_labels).scale(0.8)
        lines.to_corner(UR)
        buff = MED_LARGE_BUFF + MED_SMALL_BUFF + value_labels.get_width()
        lines.to_edge(RIGHT, buff=buff)
        for line, value_label in zip(lines, value_labels):
            value_label.move_to(line[1]).to_edge(RIGHT)

        top_line = lines[0]
        colors = [WHITE] + color_gradient([YELLOW, RED, PINK], len(lines) - 1)
        for n in range(1, len(lines)):
            color = colors[n]
            lines[n][0].set_color(color)
            lines[n][0][1:-1].match_style(lines[n - 1][0])
            lines[n][2].set_color(color)
            lines[n][2][4:].match_style(lines[n - 1][2])

        arrow = Vector(0.5 * DOWN, color=WHITE)
        arrow.next_to(value_labels[-1], DOWN)
        q_marks = Tex("???")
        q_marks.next_to(arrow, DOWN)

        self.play(
            FadeInFromDown(top_line),
            FadeInFromDown(value_labels[0])
        )
        for n in range(1, len(lines)):
            new_line = lines[n]
            last_line = lines[n - 1]
            value_label = value_labels[n]
            mover, target = [
                VGroup(
                    line[0][0],
                    line[0][-1],
                    line[1],
                    line[2][:4],
                )
                for line in (lines[1], new_line)
            ]
            anims = [ReplacementTransform(
                mover.copy().fade(1), target, path_arc=30 * DEGREES
            )]
            if n == 4:
                morty.generate_target()
                morty.target.change("horrified")
                morty.target.shift(2.5 * DOWN)
                anims.append(MoveToTarget(morty, remover=True))
            self.play(*anims)
            self.wait()
            self.play(
                ReplacementTransform(
                    last_line[0].copy(), new_line[0][1:-1]
                ),
                ReplacementTransform(
                    last_line[2].copy(), new_line[2][4:]
                ),
            )
            self.play(FadeIn(value_label))
            self.wait()
        self.play(
            GrowArrow(arrow),
            Write(q_marks),
            friend.change, "confused"
        )
        self.wait(3)

        self.top_line = VGroup(lines[0], value_labels[0])


class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication):
    CONFIG = {
        "value": (1 - np.sqrt(5)) / 2,
        "value_str": "-1/\\varphi",
    }

    def construct(self):
        self.force_skipping()
        self.ask_question()
        self.obviously_not()
        self.revert_to_original_skipping_status()

        self.plug_func_into_self(
            value=self.value,
            value_str=self.value_str
        )


class RepeatedApplicationWithNegativeSeed(RepeatedApplicationWithPhiBro, MovingCameraScene):
    CONFIG = {
        "value": -0.65,
        "value_str": "-0.65"
    }

    def setup(self):
        MovingCameraScene.setup(self)
        RepeatedApplicationWithPhiBro.setup(self)

    def construct(self):
        RepeatedApplicationWithPhiBro.construct(self)

        rect = SurroundingRectangle(self.top_line)
        question = TexText("What about a negative seed?")
        question.match_color(rect)
        question.next_to(rect, UP)
        self.play(ShowCreation(rect))
        self.play(
            Write(question),
            self.camera.frame.set_height, FRAME_HEIGHT + 1.5
        )
        self.wait()
        self.play(
            FadeOut(question),
            FadeOut(rect),
            self.camera.frame.set_height, FRAME_HEIGHT
        )


class ShowRepeatedApplication(Scene):
    CONFIG = {
        "title_color": YELLOW,
    }

    def construct(self):
        self.add_func_title()
        self.show_repeated_iteration()

    def add_func_title(self):
        title = self.title = VGroup(
            Tex("f(", "x", ")"),
            Tex("="),
            get_nested_one_plus_one_over_x(1)
        )
        title.arrange(RIGHT)
        title.to_corner(UL)
        title.set_color(self.title_color)

        self.add(title)

    def show_repeated_iteration(self):
        line = VGroup()
        decimal_kwargs = {
            "num_decimal_places": 3,
            "show_ellipsis": True,
        }
        phi = (1 + np.sqrt(5)) / 2

        def func(x):
            return 1.0 + 1.0 / x

        initial_term = DecimalNumber(2.71828, **decimal_kwargs)
        line.add(initial_term)
        last_term = initial_term

        def get_arrow():
            arrow = Tex("\\rightarrow")
            arrow.stretch(1.5, 0)
            arrow.next_to(line[-1], RIGHT)
            tex = Tex("f(x)")
            tex.set_color(YELLOW)
            tex.match_width(arrow)
            tex.next_to(arrow, UP, SMALL_BUFF)
            return VGroup(arrow, tex)

        for x in range(2):
            arrow = get_arrow()
            line.add(arrow)

            new_term = DecimalNumber(
                func(last_term.number),
                **decimal_kwargs
            )
            new_term.next_to(arrow[0], RIGHT)
            last_term = new_term
            line.add(new_term)

        line.add(get_arrow())
        line.add(Tex("\\dots\\dots").next_to(line[-1][0], RIGHT))
        num_phi_mob = DecimalNumber(phi, **decimal_kwargs)
        line.add(num_phi_mob.next_to(line[-1], RIGHT))
        line.move_to(DOWN)

        rects = VGroup(*[
            SurroundingRectangle(mob)
            for mob in (line[0], line[1:-1], line[-1])
        ])
        rects.set_stroke(BLUE, 2)

        braces = VGroup(*[
            Brace(rect, DOWN, buff=SMALL_BUFF)
            for rect in rects
        ])
        braces.set_color_by_gradient(GREEN, YELLOW)
        brace_texts = VGroup(*[
            brace.get_text(text).scale(0.75, about_edge=UP)
            for brace, text in zip(braces, [
                "Arbitrary \\\\ starting \\\\ value",
                "Repeatedly apply $f(x)$",
                "$\\varphi$ \\\\ ``Golden ratio''"
            ])
        ])
        var_phi_mob = brace_texts[2][0]
        var_phi_mob.scale(2, about_edge=UP).set_color(YELLOW)
        brace_texts[2][1:].next_to(var_phi_mob, DOWN, MED_SMALL_BUFF)

        # Animations
        self.add(line[0])
        self.play(
            GrowFromCenter(braces[0]),
            Write(brace_texts[0])
        )
        self.wait()
        self.play(
            GrowFromEdge(line[1], LEFT),
            FadeIn(braces[1]),
            FadeIn(brace_texts[1]),
        )
        self.play(ReplacementTransform(line[0].copy(), line[2]))
        self.wait()

        self.play(GrowFromEdge(line[3], LEFT))
        self.play(ReplacementTransform(line[2].copy(), line[4]))
        self.wait()

        self.play(GrowFromEdge(line[5], LEFT))
        self.play(LaggedStartMap(GrowFromCenter, line[6]))
        self.wait()

        self.play(FadeIn(line[7]))
        self.play(
            GrowFromCenter(braces[2]),
            FadeIn(brace_texts[2]),
        )
        self.wait()


class NumericalPlayFromOne(ExternallyAnimatedScene):
    pass


class NumericalPlayFromTau(ExternallyAnimatedScene):
    pass


class NumericalPlayFromNegPhi(ExternallyAnimatedScene):
    pass


class NumericalPlayOnNumberLineFromOne(Scene):
    CONFIG = {
        "starting_value": 1,
        "n_jumps": 10,
        "func": lambda x: 1 + 1.0 / x,
        "number_line_config": {
            "x_min": 0,
            "x_max": 2,
            "unit_size": 6,
            "tick_frequency": 0.25,
            "numbers_with_elongated_ticks": [0, 1, 2]
        }
    }

    def construct(self):
        self.add_number_line()
        self.add_phi_label()
        self.add_title()
        self.bounce_around()

    def add_number_line(self):
        number_line = NumberLine(**self.number_line_config)
        number_line.move_to(2 * DOWN)
        number_line.add_numbers()

        self.add(number_line)
        self.number_line = number_line

    def add_phi_label(self):
        number_line = self.number_line
        phi_point = number_line.number_to_point(
            (1 + np.sqrt(5)) / 2
        )
        phi_dot = Dot(phi_point, color=YELLOW)
        arrow = Vector(DL)
        arrow.next_to(phi_point, UR, SMALL_BUFF)
        phi_label = Tex("\\varphi = 1.618\\dots")
        phi_label.set_color(YELLOW)
        phi_label.next_to(arrow.get_start(), UP, SMALL_BUFF)

        self.add(phi_dot, phi_label, arrow)

    def add_title(self):
        title = Tex("x \\rightarrow 1 + \\frac{1}{x}")
        title.to_corner(UL)
        self.add(title)

    def bounce_around(self):
        number_line = self.number_line
        value = self.starting_value
        point = number_line.number_to_point(value)
        dot = Dot(point)
        dot.set_fill(RED, opacity=0.8)
        arrow = Vector(DR)
        arrow.next_to(point, UL, buff=SMALL_BUFF)
        arrow.match_color(dot)
        start_here = TexText("Start here")
        start_here.next_to(arrow.get_start(), UP, SMALL_BUFF)
        start_here.match_color(dot)

        self.play(
            FadeIn(start_here),
            GrowArrow(arrow),
            GrowFromPoint(dot, arrow.get_start())
        )
        self.play(
            FadeOut(start_here),
            FadeOut(arrow)
        )
        self.wait()
        for x in range(self.n_jumps):
            new_value = self.func(value)
            new_point = number_line.number_to_point(new_value)
            if new_value - value > 0:
                path_arc = -120 * DEGREES
            else:
                path_arc = -120 * DEGREES
            arc = Line(
                point, new_point,
                path_arc=path_arc,
                buff=SMALL_BUFF
            )
            self.play(
                ShowCreationThenDestruction(arc, run_time=1.5),
                ApplyMethod(
                    dot.move_to, new_point,
                    path_arc=path_arc
                ),
            )
            self.wait(0.5)

            value = new_value
            point = new_point


class NumericalPlayOnNumberLineFromTau(NumericalPlayOnNumberLineFromOne):
    CONFIG = {
        "starting_value": TAU,
        "number_line_config": {
            "x_min": 0,
            "x_max": 7,
            "unit_size": 2,
        }
    }


class NumericalPlayOnNumberLineFromMinusPhi(NumericalPlayOnNumberLineFromOne):
    CONFIG = {
        "starting_value": -0.61803,
        "number_line_config": {
            "x_min": -3,
            "x_max": 3,
            "unit_size": 2,
            "tick_frequency": 0.25,
        },
        "n_jumps": 25,
    }

    def add_phi_label(self):
        NumericalPlayOnNumberLineFromOne.add_phi_label(self)
        number_line = self.number_line
        new_point = number_line.number_to_point(
            (1 - np.sqrt(5)) / 2
        )
        arrow = Vector(DR)
        arrow.next_to(new_point, UL, SMALL_BUFF)
        arrow.set_color(RED)
        new_label = Tex("-\\frac{1}{\\varphi} = -0.618\\dots")
        new_label.set_color(RED)
        new_label.next_to(arrow.get_start(), UP, SMALL_BUFF)
        new_label.shift(RIGHT)
        self.add(new_label, arrow)


class RepeatedApplicationGraphically(GraphOnePlusOneOverX, PiCreatureScene):
    CONFIG = {
        "starting_value": 1,
        "n_jumps": 5,
        "n_times_to_show_identity_property": 2,
    }

    def setup(self):
        GraphOnePlusOneOverX.setup(self)
        PiCreatureScene.setup(self)

    def construct(self):
        self.setup_axes()
        self.draw_graphs(animate=False)
        self.draw_spider_web()

    def create_pi_creature(self):
        randy = Randolph(height=2)
        randy.flip()
        randy.to_corner(DR)
        return randy

    def get_new_randy_mode(self):
        randy = self.pi_creature
        if not hasattr(self, "n_mode_changes"):
            self.n_mode_changes = 0
        else:
            self.n_mode_changes += 1
        if self.n_mode_changes % 3 != 0:
            return randy.get_mode()
        return random.choice([
            "confused",
            "erm",
            "maybe"
        ])

    def draw_spider_web(self):
        randy = self.pi_creature
        func = self.func_graph[0].underlying_function
        x_val = self.starting_value
        curr_output = 0
        dot = Dot(color=RED, fill_opacity=0.7)
        dot.move_to(self.coords_to_point(x_val, curr_output))

        self.play(FadeIn(dot, 2 * UR))
        self.wait()

        for n in range(self.n_jumps):
            new_output = func(x_val)
            func_graph_point = self.coords_to_point(x_val, new_output)
            id_graph_point = self.coords_to_point(new_output, new_output)
            v_line = DashedLine(dot.get_center(), func_graph_point)
            h_line = DashedLine(func_graph_point, id_graph_point)

            curr_output = new_output
            x_val = new_output

            for line in v_line, h_line:
                line_end = line.get_end()
                self.play(
                    ShowCreation(line),
                    dot.move_to, line_end,
                    randy.change, self.get_new_randy_mode()
                )
                self.wait()

            if n < self.n_times_to_show_identity_property:
                x_point = self.coords_to_point(new_output, 0)
                y_point = self.coords_to_point(0, new_output)
                lines = VGroup(*[
                    Line(dot.get_center(), point)
                    for point in (x_point, y_point)
                ])
                lines.set_color(YELLOW)
                self.play(ShowCreationThenDestruction(
                    lines, run_time=2
                ))
                self.wait(0.25)


class RepeatedApplicationGraphicallyFromNegPhi(RepeatedApplicationGraphically):
    CONFIG = {
        "starting_value": -0.61,
        "n_jumps": 13,
        "n_times_to_show_identity_property": 0,
    }


class LetsSwitchToTheTransformationalView(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Lose the \\\\ graphs!",
            target_mode="hooray"
        )
        self.change_student_modes("hooray", "erm", "surprised")
        self.wait(5)


class AnalyzeFunctionWithTransformations(NumberlineTransformationScene):
    CONFIG = {
        "input_line_zero_point": 0.5 * UP,
        "output_line_zero_point": 2 * DOWN,
        "func": lambda x: 1 + 1.0 / x,
        "num_initial_applications": 10,
        "num_repeated_local_applications": 7,
        "zoomed_display_width": 3.5,
        "zoomed_display_height": 2,
        "default_mapping_animation_config": {},
    }

    def construct(self):
        self.add_function_title()
        self.repeatedly_apply_function()
        self.show_phi_and_phi_bro()
        self.zoom_in_on_phi()
        self.zoom_in_on_phi_bro()

    def setup_number_lines(self):
        NumberlineTransformationScene.setup_number_lines(self)
        for line in self.input_line, self.output_line:
            VGroup(line, line.tick_marks).set_stroke(width=2)

    def add_function_title(self):
        title = Tex("f(x)", "=", "1 +", "\\frac{1}{x}")
        title.to_edge(UP)
        self.add(title)
        self.title = title

    def repeatedly_apply_function(self):
        input_zero_point = self.input_line.number_to_point(0)
        output_zero_point = self.output_line.number_to_point(0)
        sample_dots = self.get_sample_dots(
            delta_x=0.05,
            dot_radius=0.05,
            x_min=-10,
            x_max=10,
        )
        sample_dots.set_stroke(BLACK, 0.5)
        sample_points = list(map(Mobject.get_center, sample_dots))

        self.play(LaggedStartMap(
            FadeInFrom, sample_dots,
            lambda m: (m, UP)
        ))
        self.show_arrows(sample_points)
        self.wait()
        for x in range(self.num_initial_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                sample_dots=sample_dots
            )
            self.wait()

            shift_vect = input_zero_point - output_zero_point
            shift_vect[0] = 0
            lower_output_line = self.output_line.copy()
            upper_output_line = self.output_line.copy()
            lower_output_line.shift(-shift_vect)
            lower_output_line.fade(1)

            self.remove(self.output_line)
            self.play(
                ReplacementTransform(lower_output_line, self.output_line),
                upper_output_line.shift, shift_vect,
                upper_output_line.fade, 1,
                sample_dots.shift, shift_vect,
            )
            self.remove(upper_output_line)
            self.wait()
        self.play(FadeOut(sample_dots))

    def show_arrows(self, sample_points):
        input_zero_point = self.input_line.number_to_point(0)
        point_func = self.number_func_to_point_func(self.func)
        alt_point_func = self.number_func_to_point_func(lambda x: 1.0 / x)
        arrows, alt_arrows = [
            VGroup(*[
                Arrow(
                    point, func(point), buff=SMALL_BUFF,
                    tip_length=0.15
                )
                for point in sample_points
                if get_norm(point - input_zero_point) > 0.3
            ])
            for func in (point_func, alt_point_func)
        ]
        for group in arrows, alt_arrows:
            group.set_stroke(WHITE, 0.5)
            group.set_color_by_gradient(RED, YELLOW)
            for arrow in group:
                arrow.tip.set_stroke(BLACK, 0.5)

        one_plus = self.title.get_part_by_tex("1 +")
        one_plus_rect = BackgroundRectangle(one_plus)
        one_plus_rect.set_fill(BLACK, opacity=0.8)

        self.play(LaggedStartMap(GrowArrow, arrows))
        self.wait()
        self.play(LaggedStartMap(
            ApplyMethod, arrows,
            lambda a: (a.scale, 0.7),
            rate_func=there_and_back,
        ))
        self.wait()
        self.play(
            Transform(arrows, alt_arrows),
            FadeIn(one_plus_rect, remover=True),
            rate_func=there_and_back_with_pause,
            run_time=4,
        )
        self.wait()

        self.all_arrows = arrows

    def show_phi_and_phi_bro(self):
        phi = (1 + np.sqrt(5)) / 2
        phi_bro = (1 - np.sqrt(5)) / 2

        input_phi_point = self.input_line.number_to_point(phi)
        output_phi_point = self.output_line.number_to_point(phi)
        input_phi_bro_point = self.input_line.number_to_point(phi_bro)
        output_phi_bro_point = self.output_line.number_to_point(phi_bro)

        tick = Line(UP, DOWN)
        tick.set_stroke(YELLOW, 3)
        tick.match_height(self.input_line.tick_marks)
        phi_tick = tick.copy().move_to(input_phi_point, DOWN)
        phi_bro_tick = tick.copy().move_to(input_phi_bro_point, DOWN)
        VGroup(phi_tick, phi_bro_tick).shift(SMALL_BUFF * DOWN)

        phi_label = Tex("1.618\\dots")
        phi_label.next_to(phi_tick, UP)
        phi_bro_label = Tex("-0.618\\dots")
        phi_bro_label.next_to(phi_bro_tick, UP)
        VGroup(phi_label, phi_bro_label).set_color(YELLOW)

        arrow_kwargs = {
            "buff": SMALL_BUFF,
            "rectangular_stem_width": 0.035,
            "tip_length": 0.2,
        }
        phi_arrow = Arrow(phi_tick, output_phi_point, **arrow_kwargs)
        phi_bro_arrow = Arrow(phi_bro_tick, output_phi_bro_point, **arrow_kwargs)

        def fade_arrow(arrow):
            # arrow.set_stroke(GREY_D, 0.5)
            arrow.set_stroke(width=0.1)
            arrow.tip.set_fill(opacity=0)
            arrow.tip.set_stroke(width=0)
            return arrow

        self.play(
            LaggedStartMap(
                ApplyFunction, self.all_arrows,
                lambda a: (fade_arrow, a)
            ),
            FadeIn(phi_arrow),
            FadeIn(phi_bro_arrow),
        )
        self.play(
            Write(phi_label),
            GrowFromCenter(phi_tick)
        )
        self.play(
            Write(phi_bro_label),
            GrowFromCenter(phi_bro_tick)
        )

        self.set_variables_as_attrs(
            input_phi_point, output_phi_point,
            input_phi_bro_point, output_phi_bro_point,
            phi_label, phi_tick,
            phi_bro_label, phi_bro_tick,
            phi_arrow, phi_bro_arrow
        )

    def zoom_in_on_phi(self):
        phi = (1 + np.sqrt(5)) / 2
        # phi_point = self.get_input_point(phi)
        local_sample_dots = self.get_local_sample_dots(
            phi, dot_radius=0.005, sample_radius=1
        )
        local_coordinate_values = [1.55, 1.6, 1.65, 1.7]

        # zcbr = self.zoomed_camera_background_rectangle
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_group.add(self.phi_tick)

        title = self.title
        deriv_text = Tex(
            "|", "\\frac{df}{dx}(\\varphi)", "|", "< 1",
            tex_to_color_map={"\\varphi": YELLOW}
        )
        deriv_text.get_parts_by_tex("|").match_height(
            deriv_text, stretch=True
        )
        deriv_text.move_to(title, UP)
        approx_value = Tex("\\approx |%.2f|" % (-1 / phi**2))
        approx_value.move_to(deriv_text)
        deriv_text_lhs = deriv_text[:-1]
        deriv_text_rhs = deriv_text[-1]

        self.zoom_in_on_input(
            phi,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.wait()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coordinate_values,
        )
        self.wait()
        self.play(
            FadeInFromDown(deriv_text_lhs),
            FadeInFromDown(deriv_text_rhs),
            title.to_corner, UL
        )
        self.wait()
        self.play(
            deriv_text_lhs.next_to, approx_value, LEFT,
            deriv_text_rhs.next_to, approx_value, RIGHT,
            FadeIn(approx_value)
        )
        self.wait()
        for n in range(self.num_repeated_local_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                local_sample_dots=local_sample_dots,
                path_arc=60 * DEGREES,
                run_time=2
            )

        self.deriv_text = VGroup(
            deriv_text_lhs, deriv_text_rhs, approx_value
        )

    def zoom_in_on_phi_bro(self):
        zcbr = self.zoomed_camera_background_rectangle
        # zcbr_group = self.zoomed_camera_background_rectangle_group
        zoomed_frame = self.zoomed_camera.frame

        phi_bro = (1 - np.sqrt(5)) / 2
        # phi_bro_point = self.get_input_point(phi_bro)
        local_sample_dots = self.get_local_sample_dots(phi_bro)
        local_coordinate_values = [-0.65, -0.6, -0.55]

        deriv_text = Tex(
            "\\left| \\frac{df}{dx}\\left(\\frac{-1}{\\varphi}\\right) \\right|",
            "\\approx |%.2f|" % (-1 / (phi_bro**2)),
            "> 1"
        )
        deriv_text.move_to(self.deriv_text, UL)
        deriv_text[0][10:14].set_color(YELLOW)

        self.play(
            zoomed_frame.set_height, 4,
            zoomed_frame.center,
            self.deriv_text.fade, 1,
            run_time=2
        )
        self.wait()
        zcbr.set_fill(opacity=0)
        self.zoom_in_on_input(
            phi_bro,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
            zoom_factor=self.zoom_factor,
            first_anim_kwargs={"run_time": 2},
        )
        self.wait()
        self.play(FadeInFromDown(deriv_text))
        self.wait()
        zcbr.set_fill(opacity=1)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coordinate_values,
        )
        self.wait()
        for n in range(self.num_repeated_local_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                local_sample_dots=local_sample_dots,
                path_arc=20 * DEGREES,
                run_time=2,
            )


class StabilityAndInstability(AnalyzeFunctionWithTransformations):
    CONFIG = {
        "num_initial_applications": 0,
    }

    def construct(self):
        self.force_skipping()
        self.add_function_title()
        self.repeatedly_apply_function()
        self.show_phi_and_phi_bro()
        self.revert_to_original_skipping_status()

        self.label_stability()
        self.write_derivative_fact()

    def label_stability(self):
        self.title.to_corner(UL)

        stable_label = TexText("Stable fixed point")
        unstable_label = TexText("Unstable fixed point")
        labels = VGroup(stable_label, unstable_label)
        labels.scale(0.8)
        stable_label.next_to(self.phi_label, UP, aligned_edge=ORIGIN)
        unstable_label.next_to(self.phi_bro_label, UP, aligned_edge=ORIGIN)

        phi_point = self.input_phi_point
        phi_bro_point = self.input_phi_bro_point

        arrow_groups = VGroup()
        for point in phi_point, phi_bro_point:
            arrows = VGroup(*[a for a in self.all_arrows if get_norm(a.get_start() - point) < 0.75]).copy()
            arrows.set_fill(PINK, 1)
            arrows.set_stroke(PINK, 3)
            arrows.second_anim = LaggedStartMap(
                ApplyMethod, arrows,
                lambda m: (m.set_color, YELLOW),
                rate_func=there_and_back_with_pause,
                lag_ratio=0.7,
                run_time=2,
            )
            arrows.anim = AnimationGroup(*list(map(GrowArrow, arrows)))
            arrow_groups.add(arrows)
        phi_arrows, phi_bro_arrows = arrow_groups

        self.add_foreground_mobjects(self.phi_arrow, self.phi_bro_arrow)
        self.play(
            Write(stable_label),
            phi_arrows.anim,
        )
        self.play(phi_arrows.second_anim)
        self.play(
            Write(unstable_label),
            phi_bro_arrows.anim,
        )
        self.play(phi_bro_arrows.second_anim)
        self.wait()

        self.stable_label = stable_label
        self.unstable_label = unstable_label
        self.phi_arrows = phi_arrows
        self.phi_bro_arrows = phi_bro_arrows

    def write_derivative_fact(self):
        stable_label = self.stable_label
        unstable_label = self.unstable_label
        labels = VGroup(stable_label, unstable_label)
        phi_arrows = self.phi_arrows
        phi_bro_arrows = self.phi_bro_arrows
        arrow_groups = VGroup(phi_arrows, phi_bro_arrows)

        deriv_labels = VGroup()
        for char, label in zip("<>", labels):
            deriv_label = Tex(
                "\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|",
                char, "1"
            )
            deriv_label.get_parts_by_tex("\\big|").match_height(
                deriv_label, stretch=True
            )
            deriv_label.set_color_by_tex("x", YELLOW, substring=False)
            deriv_label.next_to(label, UP)
            deriv_labels.add(deriv_label)

        dot_groups = VGroup()
        for arrow_group in arrow_groups:
            dot_group = VGroup()
            for arrow in arrow_group:
                start_point, end_point = [
                    line.number_to_point(line.point_to_number(p))
                    for line, p in [
                        (self.input_line, arrow.get_start()),
                        (self.output_line, arrow.get_end()),
                    ]
                ]
                dot = Dot(start_point, radius=0.05)
                dot.set_color(YELLOW)
                dot.generate_target()
                dot.target.move_to(end_point)
                dot_group.add(dot)
            dot_groups.add(dot_group)

        for deriv_label, dot_group in zip(deriv_labels, dot_groups):
            self.play(FadeInFromDown(deriv_label))
            self.play(LaggedStartMap(GrowFromCenter, dot_group))
            self.play(*list(map(MoveToTarget, dot_group)), run_time=2)
            self.wait()


class StaticAlgebraicObject(Scene):
    def construct(self):
        frac = get_phi_continued_fraction(40)
        frac.set_width(FRAME_WIDTH - 1)
        # frac.shift(2 * DOWN)
        frac.to_edge(DOWN)
        frac.set_stroke(WHITE, width=0.5)

        title = Tex(
            "\\infty \\ne \\lim",
            tex_to_color_map={"\\ne": RED}
        )
        title.scale(1.5)
        title.to_edge(UP)

        polynomial = Tex("x^2 - x - 1 = 0")
        polynomial.move_to(title)

        self.add(title)
        self.play(LaggedStartMap(
            GrowFromCenter, frac,
            lag_ratio=0.1,
            run_time=3
        ))
        self.wait()
        factor = 1.1
        self.play(frac.scale, factor, run_time=0.5)
        self.play(
            frac.scale, 1 / factor,
            frac.set_color, GREY_B,
            run_time=0.5, rate_func=lambda t: t**5,
        )
        self.wait()
        self.play(
            FadeOut(title),
            FadeIn(polynomial)
        )
        self.wait(2)


class NotBetterThanGraphs(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Um, yeah, I'll stick \\\\ with graphs thanks",
            target_mode="sassy",
        )
        self.play(
            self.teacher.change, "guilty",
            self.get_student_changes("sad", "sassy", "hesitant")
        )
        self.wait(2)
        self.play(
            RemovePiCreatureBubble(self.students[1]),
            self.teacher.change, "raise_right_hand"
        )
        self.change_all_student_modes(
            "confused", look_at_arg=self.screen
        )
        self.wait(3)
        self.teacher_says(
            "You must flex those \\\\ conceptual muscles",
            added_anims=[self.get_student_changes(
                *3 * ["thinking"],
                look_at_arg=self.teacher.eyes
            )]
        )
        self.wait(3)


class WhatComesAfterWrapper(Wrapper):
    CONFIG = {"title": "Beyond the first year"}

    def construct(self):
        Wrapper.construct(self)
        new_title = TexText("Next video")
        new_title.set_color(BLUE)
        new_title.move_to(self.title)

        self.play(
            FadeInFromDown(new_title),
            self.title.shift, UP,
            self.title.fade, 1,
        )
        self.wait(3)


class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
    CONFIG = {
        "pi_creatures_start_on_screen": False,
    }

    def construct(self):
        MoreTopics.construct(self)
        self.show_horror()
        self.zero_in_on_complex_analysis()

    def create_pi_creatures(self):
        creatures = VGroup(*[
            PiCreature(color=color)
            for color in [BLUE_E, BLUE_C, BLUE_D]
        ])
        creatures.arrange(RIGHT, buff=LARGE_BUFF)
        creatures.scale(0.5)
        creatures.to_corner(DR)
        return creatures

    def show_horror(self):
        creatures = self.get_pi_creatures()
        modes = ["horrified", "tired", "horrified"]
        for creature, mode in zip(creatures, modes):
            creature.generate_target()
            creature.target.change(mode, self.other_topics)
        creatures.fade(1)

        self.play(LaggedStartMap(MoveToTarget, creatures))
        self.wait(2)

    def zero_in_on_complex_analysis(self):
        creatures = self.get_pi_creatures()
        complex_analysis = self.other_topics[1]
        self.other_topics.remove(complex_analysis)

        self.play(
            complex_analysis.scale, 1.25,
            complex_analysis.center,
            complex_analysis.to_edge, UP,
            LaggedStartMap(FadeOut, self.other_topics),
            LaggedStartMap(FadeOut, self.lines),
            FadeOut(self.calculus),
            *[
                ApplyMethod(creature.change, "pondering")
                for creature in creatures
            ]
        )
        self.wait(4)


class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene):
    CONFIG = {
        "show_basis_vectors": False,
        "show_coordinates": True,
        "zoom_factor": 0.05,
    }

    def setup(self):
        LinearTransformationScene.setup(self)
        ZoomedScene.setup(self)

    def construct(self):
        def example_function(point):
            x, y, z = point
            return np.array([
                x + np.sin(y),
                y + np.sin(x),
                0
            ])

        zoomed_camera = self.zoomed_camera
        zoomed_display = self.zoomed_display
        frame = zoomed_camera.frame
        frame.move_to(3 * LEFT + 1 * UP)
        frame.set_color(YELLOW)
        zoomed_display.display_frame.set_color(YELLOW)
        zd_rect = BackgroundRectangle(
            zoomed_display,
            fill_opacity=1,
            buff=MED_SMALL_BUFF,
        )
        self.add_foreground_mobject(zd_rect)
        zd_rect.anim = UpdateFromFunc(
            zd_rect,
            lambda rect: rect.replace(zoomed_display).scale(1.1)
        )
        zd_rect.next_to(FRAME_HEIGHT * UP, UP)

        tiny_grid = NumberPlane(
            x_radius=2,
            y_radius=2,
            color=BLUE_E,
            secondary_color=GREY_D,
        )
        tiny_grid.replace(frame)

        jacobian_words = TexText("Jacobian")
        jacobian_words.add_background_rectangle()
        jacobian_words.scale(1.5)
        jacobian_words.move_to(zoomed_display, UP)
        zoomed_display.next_to(jacobian_words, DOWN)

        self.play(self.get_zoom_in_animation())
        self.activate_zooming()
        self.play(
            self.get_zoomed_display_pop_out_animation(),
            zd_rect.anim
        )
        self.play(
            ShowCreation(tiny_grid),
            Write(jacobian_words),
            run_time=2
        )
        self.add_transformable_mobject(tiny_grid)
        self.add_foreground_mobject(jacobian_words)
        self.wait()
        self.apply_nonlinear_transformation(
            example_function,
            added_anims=[MaintainPositionRelativeTo(
                zoomed_camera.frame, tiny_grid,
            )],
            run_time=5
        )
        self.wait()


class PrinciplesOverlay(PiCreatureScene):
    CONFIG = {
        "default_pi_creature_kwargs": {
            "color": GREY_BROWN,
            "flip_at_start": True,
        },
        "default_pi_creature_start_corner": DR,
    }

    def construct(self):
        morty = self.pi_creature
        q_marks = VGroup(*[Tex("?") for x in range(40)])
        q_marks.arrange_in_grid(4, 10)
        q_marks.space_out_submobjects(1.4)
        for mark in q_marks:
            mark.shift(
                random.random() * RIGHT,
                random.random() * UP,
            )
            mark.scale(1.5)
            mark.set_stroke(BLACK, 1)
        q_marks.next_to(morty, UP)
        q_marks.shift_onto_screen()
        q_marks.sort(
            lambda p: get_norm(p - morty.get_top())
        )

        self.play(morty.change, "pondering")
        self.wait(2)
        self.play(morty.change, "raise_right_hand")
        self.wait()
        self.play(morty.change, "thinking")
        self.wait(4)
        self.play(FadeInFromDown(q_marks[0]))
        self.wait(2)
        self.play(LaggedStartMap(
            FadeInFromDown, q_marks[1:],
            run_time=3
        ))
        self.wait(3)


class ManyInfiniteExpressions(Scene):
    def construct(self):
        frac = get_phi_continued_fraction(10)
        frac.set_height(2)
        frac.to_corner(UL)

        n = 9
        radical_str_parts = [
            "%d + \\sqrt{" % d
            for d in range(1, n + 1)
        ]
        radical_str_parts += ["\\cdots"]
        radical_str_parts += ["}"] * n
        radical = Tex("".join(radical_str_parts))
        radical.to_corner(UR)
        radical.to_edge(DOWN)
        radical.set_color_by_gradient(YELLOW, RED)

        n = 12
        power_tower = Tex(
            *["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n
        )
        power_tower.to_corner(UR)
        power_tower.set_color_by_gradient(BLUE, GREEN)

        self.play(*[
            LaggedStartMap(
                GrowFromCenter, group,
                lag_ratio=0.1,
                run_time=8,
            )
            for group in (frac, radical, power_tower)
        ])
        self.wait(2)


class HoldUpPromo(PrinciplesOverlay):
    def construct(self):
        morty = self.pi_creature

        url = TexText("https://brilliant.org/3b1b/")
        url.to_corner(UL)

        rect = ScreenRectangle(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)

        self.play(FadeOut(rect), FadeOut(url))
        self.play(morty.change, "raise_right_hand")
        self.wait()
        self.play(morty.change, "hooray")
        self.wait(3)


class EndScreen(PatreonEndScreen):
    CONFIG = {
        "specific_patrons": [
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Desmos",
            "<NAME>",
            "CrypticSwarm",
            "<NAME>",
            "Ho\\`ang T\\`ung L\\^am",
            # "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Fela",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "supershabam",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "1st ViewMaths",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Awoo",
            "Dr . <NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Ankalagon",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Sh\\`im\\'in Ku\\=ang",
            "<NAME>",
            "<NAME>",
            "<NAME>",
        ]
    }


# class Thumbnail(GraphicalIntuitions):
class Thumbnail(AnalyzeFunctionWithTransformations):
    CONFIG = {
        "x_axis_width": 12,
        "graph_origin": 1.5 * DOWN + 4 * LEFT,
        "num_initial_applications": 1,
        "input_line_zero_point": 2 * UP,
    }

    def construct(self):
        self.add_function_title()
        self.title.fade(1)
        self.titles.fade(1)
        self.repeatedly_apply_function()
        self.all_arrows.set_stroke(width=1)

        full_rect = FullScreenFadeRectangle()
        cross = Cross(full_rect)
        cross.set_stroke(width=40)

        # self.add(cross)

# -*- coding: utf-8 -*-
from manim_imports_ext import *


def apply_function_to_center(point_func, mobject):
    mobject.apply_function_to_position(point_func)


def apply_function_to_submobjects(point_func, mobject):
    mobject.apply_function_to_submobject_positions(point_func)


def apply_function_to_points(point_func, mobject):
    mobject.apply_function(point_func)


def get_nested_one_plus_one_over_x(n_terms, bottom_term="x"):
    tex = "1+ {1 \\over" * n_terms + bottom_term + "}" * n_terms
    return Tex(tex, isolate=["1", "\\over", bottom_term])


def get_phi_continued_fraction(n_terms):
    return get_nested_one_plus_one_over_x(n_terms, bottom_term="1+\\cdots")


def get_nested_f(n_terms, arg="x"):
    terms = ["f("] * n_terms + [arg] + [")"] * n_terms
    return Tex(*terms)


# Scene types

class NumberlineTransformationScene(ZoomedScene):
    CONFIG = {
        "input_line_zero_point": 0.5 * UP + (FRAME_X_RADIUS - 1) * LEFT,
        "output_line_zero_point": 2 * DOWN + (FRAME_X_RADIUS - 1) * LEFT,
        "number_line_config": {
            "include_numbers": True,
            "x_min": -3.5,
            "x_max": 3.5,
            "unit_size": 2,
        },
        # These would override number_line_config
        "input_line_config": {
            "color": BLUE,
        },
        "output_line_config": {},
        "num_inserted_number_line_curves": 20,
        "default_delta_x": 0.1,
        "default_sample_dot_radius": 0.07,
        "default_sample_dot_colors": [RED, YELLOW],
        "default_mapping_animation_config": {
            "run_time": 3,
            # "path_arc": 30 * DEGREES,
        },
        "local_coordinate_num_decimal_places": 2,
        "zoom_factor": 0.1,
        "zoomed_display_height": 2.5,
        "zoomed_display_corner_buff": MED_SMALL_BUFF,
        "mini_line_scale_factor": 2,
        "default_coordinate_value_dx": 0.05,
        "zoomed_camera_background_rectangle_fill_opacity": 1.0,
    }

    def setup(self):
        ZoomedScene.setup(self)
        self.setup_number_lines()
        self.setup_titles()
        self.setup_zoomed_camera_background_rectangle()

    def setup_number_lines(self):
        number_lines = self.number_lines = VGroup()
        added_configs = (self.input_line_config, self.output_line_config)
        zero_opints = (self.input_line_zero_point, self.output_line_zero_point)
        for added_config, zero_point in zip(added_configs, zero_opints):
            full_config = dict(self.number_line_config)
            full_config.update(added_config)
            number_line = NumberLine(**full_config)
            number_line.insert_n_curves(
                self.num_inserted_number_line_curves
            )
            number_line.shift(zero_point - number_line.number_to_point(0))
            number_lines.add(number_line)
        self.input_line, self.output_line = number_lines

        self.add(number_lines)

    def setup_titles(self):
        input_title, output_title = self.titles = VGroup(*[
            TexText(word)
            for word in ("Inputs", "Outputs")
        ])
        vects = [UP, DOWN]
        for title, line, vect in zip(self.titles, self.number_lines, vects):
            title.next_to(line, vect, aligned_edge=LEFT)
            title.shift_onto_screen()

        self.add(self.titles)

    def setup_zoomed_camera_background_rectangle(self):
        frame = self.zoomed_camera.frame
        frame.next_to(self.camera.frame, UL)
        self.zoomed_camera_background_rectangle = BackgroundRectangle(
            frame, fill_opacity=self.zoomed_camera_background_rectangle_fill_opacity
        )
        self.zoomed_camera_background_rectangle_anim = UpdateFromFunc(
            self.zoomed_camera_background_rectangle,
            lambda m: m.replace(frame, stretch=True)
        )
        self.zoomed_camera_background_rectangle_group = VGroup(
            self.zoomed_camera_background_rectangle,
        )

    def get_sample_input_points(self, x_min=None, x_max=None, delta_x=None):
        x_min = x_min or self.input_line.x_min
        x_max = x_max or self.input_line.x_max
        delta_x = delta_x or self.default_delta_x
        return [
            self.get_input_point(x)
            for x in np.arange(x_min, x_max + delta_x, delta_x)
        ]

    def get_sample_dots(self, x_min=None, x_max=None,
                        delta_x=None, dot_radius=None, colors=None):
        dot_radius = dot_radius or self.default_sample_dot_radius
        colors = colors or self.default_sample_dot_colors

        dots = VGroup(*[
            Dot(point, radius=dot_radius)
            for point in self.get_sample_input_points(x_min, x_max, delta_x)
        ])
        dots.set_color_by_gradient(*colors)
        return dots

    def get_local_sample_dots(self, x, sample_radius=None, **kwargs):
        zoom_factor = self.get_zoom_factor()
        delta_x = kwargs.get("delta_x", self.default_delta_x * zoom_factor)
        dot_radius = kwargs.get("dot_radius", self.default_sample_dot_radius * zoom_factor)

        if sample_radius is None:
            unrounded_radius = self.zoomed_camera.frame.get_width() / 2
            sample_radius = int(unrounded_radius / delta_x) * delta_x
        config = {
            "x_min": x - sample_radius,
            "x_max": x + sample_radius,
            "delta_x": delta_x,
            "dot_radius": dot_radius,
        }
        config.update(kwargs)
        return self.get_sample_dots(**config)

    def add_sample_dot_ghosts(self, sample_dots, fade_factor=0.5):
        self.sample_dot_ghosts = sample_dots.copy()
        self.sample_dot_ghosts.fade(fade_factor)
        self.add(self.sample_dot_ghosts, sample_dots)

    def get_local_coordinate_values(self, x, dx=None, n_neighbors=1):
        dx = dx or self.default_coordinate_value_dx
        return [
            x + n * dx
            for n in range(-n_neighbors, n_neighbors + 1)
        ]

    # Mapping animations
    def get_mapping_animation(self, func, mobject,
                              how_to_apply_func=apply_function_to_center,
                              **kwargs):
        anim_config = dict(self.default_mapping_animation_config)
        anim_config.update(kwargs)

        point_func = self.number_func_to_point_func(func)

        mobject.generate_target(use_deepcopy=True)
        how_to_apply_func(point_func, mobject.target)
        return MoveToTarget(mobject, **anim_config)

    def get_line_mapping_animation(self, func, **kwargs):
        input_line_copy = self.input_line.deepcopy()
        self.moving_input_line = input_line_copy
        input_line_copy.remove(input_line_copy.numbers)
        # input_line_copy.set_stroke(width=2)
        input_line_copy.insert_n_curves(
            self.num_inserted_number_line_curves
        )
        return AnimationGroup(
            self.get_mapping_animation(
                func, input_line_copy,
                apply_function_to_points
            ),
            self.get_mapping_animation(
                func, input_line_copy.tick_marks,
                apply_function_to_submobjects
            ),
        )

    def get_sample_dots_mapping_animation(self, func, dots, **kwargs):
        return self.get_mapping_animation(
            func, dots, how_to_apply_func=apply_function_to_submobjects
        )

    def get_zoomed_camera_frame_mapping_animation(self, func, x=None, **kwargs):
        frame = self.zoomed_camera.frame
        if x is None:
            point = frame.get_center()
        else:
            point = self.get_input_point(x)
        point_mob = VectorizedPoint(point)
        return AnimationGroup(
            self.get_mapping_animation(func, point_mob),
            UpdateFromFunc(frame, lambda m: m.move_to(point_mob)),
        )

    def apply_function(self, func,
                       apply_function_to_number_line=True,
                       sample_dots=None,
                       local_sample_dots=None,
                       target_coordinate_values=None,
                       added_anims=None,
                       **kwargs
                       ):
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        frame = self.zoomed_camera.frame

        anims = []
        if apply_function_to_number_line:
            anims.append(self.get_line_mapping_animation(func))
        if hasattr(self, "mini_line"):  # Test for if mini_line is in self?
            anims.append(self.get_mapping_animation(
                func, self.mini_line,
                how_to_apply_func=apply_function_to_center
            ))
        if sample_dots:
            anims.append(
                self.get_sample_dots_mapping_animation(func, sample_dots)
            )
        if self.zoom_activated:
            zoom_anim = self.get_zoomed_camera_frame_mapping_animation(func)
            anims.append(zoom_anim)
            anims.append(zcbr_anim)

            zoom_anim.update(1)
            target_mini_line = Line(frame.get_left(), frame.get_right())
            target_mini_line.scale(self.mini_line_scale_factor)
            target_mini_line.match_style(self.output_line)
            zoom_anim.update(0)
            zcbr_group.submobjects.insert(1, target_mini_line)
        if target_coordinate_values:
            coordinates = self.get_local_coordinates(
                self.output_line,
                *target_coordinate_values
            )
            anims.append(FadeIn(coordinates))
            zcbr_group.add(coordinates)
            self.local_target_coordinates = coordinates
        if local_sample_dots:
            anims.append(
                self.get_sample_dots_mapping_animation(func, local_sample_dots)
            )
            zcbr_group.add(local_sample_dots)
        if added_anims:
            anims += added_anims
        anims.append(Animation(zcbr_group))

        self.play(*anims, **kwargs)

    # Zooming
    def zoom_in_on_input(self, x,
                         local_sample_dots=None,
                         local_coordinate_values=None,
                         pop_out=True,
                         first_added_anims=None,
                         first_anim_kwargs=None,
                         second_added_anims=None,
                         second_anim_kwargs=None,
                         zoom_factor=None,
                         ):

        first_added_anims = first_added_anims or []
        first_anim_kwargs = first_anim_kwargs or {}
        second_added_anims = second_added_anims or []
        second_anim_kwargs = second_anim_kwargs or {}
        input_point = self.get_input_point(x)

        # Decide how to move camera frame into place
        frame = self.zoomed_camera.frame
        frame.generate_target()
        frame.target.move_to(input_point)
        if zoom_factor:
            frame.target.set_height(
                self.zoomed_display.get_height() * zoom_factor
            )
        movement = MoveToTarget(frame)
        zcbr = self.zoomed_camera_background_rectangle
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        anims = []
        if self.zoom_activated:
            anims.append(movement)
            anims.append(zcbr_anim)
        else:
            movement.update(1)
            zcbr_anim.update(1)
            anims.append(self.get_zoom_in_animation())
            anims.append(FadeIn(zcbr))

        # Make sure frame is in final place
        for anim in anims:
            anim.update(1)

        # Add miniature number_line
        mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
        mini_line.scale(self.mini_line_scale_factor)
        mini_line.insert_n_curves(self.num_inserted_number_line_curves)
        mini_line.match_style(self.input_line)
        mini_line_copy = mini_line.copy()
        zcbr_group.add(mini_line_copy, mini_line)
        anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]

        # Add tiny coordinates
        if local_coordinate_values is None:
            local_coordinate_values = [x]
        local_coordinates = self.get_local_coordinates(
            self.input_line,
            *local_coordinate_values
        )
        anims.append(FadeIn(local_coordinates))
        zcbr_group.add(local_coordinates)
        self.local_coordinates = local_coordinates

        # Add tiny dots
        if local_sample_dots is not None:
            anims.append(LaggedStartMap(GrowFromCenter, local_sample_dots))
            zcbr_group.add(local_sample_dots)

        if first_added_anims:
            anims += first_added_anims

        anims.append(Animation(zcbr_group))
        if not pop_out:
            self.activate_zooming(animate=False)
        self.play(*anims, **first_anim_kwargs)

        if not self.zoom_activated and pop_out:
            self.activate_zooming(animate=False)
            added_anims = second_added_anims or []
            self.play(
                self.get_zoomed_display_pop_out_animation(),
                *added_anims,
                **second_anim_kwargs
            )

    def get_local_coordinates(self, line, *x_values, **kwargs):
        num_decimal_places = kwargs.get(
            "num_decimal_places", self.local_coordinate_num_decimal_places
        )
        result = VGroup()
        result.tick_marks = VGroup()
        result.numbers = VGroup()
        result.add(result.tick_marks, result.numbers)
        for x in x_values:
            tick_mark = Line(UP, DOWN)
            tick_mark.set_height(
                0.15 * self.zoomed_camera.frame.get_height()
            )
            tick_mark.move_to(line.number_to_point(x))
            result.tick_marks.add(tick_mark)

            number = DecimalNumber(x, num_decimal_places=num_decimal_places)
            number.scale(self.get_zoom_factor())
            number.scale(0.5)  # To make it seem small
            number.next_to(tick_mark, DOWN, buff=0.5 * number.get_height())
            result.numbers.add(number)
        return result

    def get_mobjects_in_zoomed_camera(self, mobjects):
        frame = self.zoomed_camera.frame
        x_min = frame.get_left()[0]
        x_max = frame.get_right()[0]
        y_min = frame.get_bottom()[1]
        y_max = frame.get_top()[1]
        result = VGroup()
        for mob in mobjects:
            for point in mob.get_all_points():
                if (x_min < point[0] < x_max) and (y_min < point[1] < y_max):
                    result.add(mob)
                    break
        return result

    # Helpers
    def get_input_point(self, x):
        return self.input_line.number_to_point(x)

    def get_output_point(self, fx):
        return self.output_line.number_to_point(fx)

    def number_func_to_point_func(self, number_func):
        input_line, output_line = self.number_lines

        def point_func(point):
            input_number = input_line.point_to_number(point)
            output_number = number_func(input_number)
            return output_line.number_to_point(output_number)
        return point_func


class ExampleNumberlineTransformationScene(NumberlineTransformationScene):
    CONFIG = {
        "number_line_config": {
            "x_min": 0,
            "x_max": 5,
            "unit_size": 2.0
        },
        "output_line_config": {
            "x_max": 20,
        },
    }

    def construct(self):
        func = lambda x: x**2
        x = 3
        dx = 0.05

        sample_dots = self.get_sample_dots()
        local_sample_dots = self.get_local_sample_dots(x)

        self.play(LaggedStartMap(GrowFromCenter, sample_dots))
        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=[x - dx, x, x + dx],
        )
        self.wait()
        self.apply_function(
            func,
            sample_dots=sample_dots,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=[func(x) - dx, func(x), func(x) + dx],
        )
        self.wait()


# Scenes


class WriteOpeningWords(Scene):
    def construct(self):
        raw_string1 = "Dear calculus student,"
        raw_string2 = "You're about to go through your first course. Like " + \
                      "any new topic, it will take some hard work to understand,"
        words1, words2 = [
            TexText("\\Large", *rs.split(" "))
            for rs in (raw_string1, raw_string2)
        ]
        words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF)
        words = VGroup(*it.chain(words1, words2))
        words.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
        words.to_edge(UP)

        letter_wait = 0.05
        word_wait = 2 * letter_wait
        comma_wait = 5 * letter_wait
        for word in words:
            self.play(LaggedStartMap(
                FadeIn, word,
                run_time=len(word) * letter_wait,
                lag_ratio=1.5 / len(word)
            ))
            self.wait(word_wait)
            if word.get_tex()[-1] == ",":
                self.wait(comma_wait)


class StartingCalc101(PiCreatureScene):
    CONFIG = {
        "camera_config": {"background_opacity": 1},
        "image_frame_width": 3.5,
        "image_frame_height": 2.5,
    }

    def construct(self):
        self.show_you()
        self.show_images()
        self.show_mystery_topic()

    def show_you(self):
        randy = self.pi_creature
        title = self.title = Title("Calculus 101")
        you = TexText("You")
        arrow = Vector(DL, color=WHITE)
        arrow.next_to(randy, UR)
        you.next_to(arrow.get_start(), UP)

        self.play(
            Write(you),
            GrowArrow(arrow),
            randy.change, "erm", title
        )
        self.wait()
        self.play(Write(title, run_time=1))
        self.play(FadeOut(VGroup(arrow, you)))

    def show_images(self):
        randy = self.pi_creature
        images = self.get_all_images()
        modes = [
            "pondering",  # hard_work_image
            "pondering",  # neat_example_image
            "hesitant",  # not_so_neat_example_image
            "hesitant",  # physics_image
            "horrified",  # piles_of_formulas_image
            "horrified",  # getting_stuck_image
            "thinking",  # aha_image
            "thinking",  # graphical_intuition_image
        ]

        for i, image, mode in zip(it.count(), images, modes):
            anims = []
            if hasattr(image, "fade_in_anim"):
                anims.append(image.fade_in_anim)
                anims.append(FadeIn(image.frame))
            else:
                anims.append(FadeIn(image))

            if i >= 3:
                image_to_fade_out = images[i - 3]
                if hasattr(image_to_fade_out, "fade_out_anim"):
                    anims.append(image_to_fade_out.fade_out_anim)
                else:
                    anims.append(FadeOut(image_to_fade_out))

            if hasattr(image, "continual_animations"):
                self.add(*image.continual_animations)

            anims.append(ApplyMethod(randy.change, mode))
            self.play(*anims)
            self.wait()
            if i >= 3:
                if hasattr(image_to_fade_out, "continual_animations"):
                    self.remove(*image_to_fade_out.continual_animations)
                self.remove(image_to_fade_out.frame)
        self.wait(3)

        self.remaining_images = images[-3:]

    def show_mystery_topic(self):
        images = self.remaining_images
        randy = self.pi_creature

        mystery_box = Rectangle(
            width=self.image_frame_width,
            height=self.image_frame_height,
            stroke_color=YELLOW,
            fill_color=GREY_D,
            fill_opacity=0.5,
        )
        mystery_box.scale(1.5)
        mystery_box.next_to(self.title, DOWN, MED_LARGE_BUFF)

        rects = images[-1].rects.copy()
        rects.center()
        rects.set_height(FRAME_HEIGHT - 1)
        # image = rects.get_image()
        open_cv_image = cv2.imread(get_full_raster_image_path("alt_calc_hidden_image"))
        blurry_iamge = cv2.blur(open_cv_image, (50, 50))
        array = np.array(blurry_iamge)[:, :, ::-1]
        im_mob = ImageMobject(array)
        im_mob.replace(mystery_box, stretch=True)
        mystery_box.add(im_mob)

        q_marks = Tex("???").scale(3)
        q_marks.space_out_submobjects(1.5)
        q_marks.set_stroke(BLACK, 1)
        q_marks.move_to(mystery_box)
        mystery_box.add(q_marks)

        for image in images:
            if hasattr(image, "continual_animations"):
                self.remove(*image.continual_animations)
            self.play(
                image.shift, DOWN,
                image.fade, 1,
                randy.change, "erm",
                run_time=1.5
            )
            self.remove(image)
        self.wait()
        self.play(
            FadeInFromDown(mystery_box),
            randy.change, "confused"
        )
        self.wait(5)

    # Helpers

    def get_all_images(self):
        # Images matched to narration's introductory list
        images = VGroup(
            self.get_hard_work_image(),
            self.get_neat_example_image(),
            self.get_not_so_neat_example_image(),
            self.get_physics_image(),
            self.get_piles_of_formulas_image(),
            self.get_getting_stuck_image(),
            self.get_aha_image(),
            self.get_graphical_intuition_image(),
        )
        colors = color_gradient([BLUE, YELLOW], len(images))
        for i, image, color in zip(it.count(), images, colors):
            self.adjust_size(image)
            frame = Rectangle(
                width=self.image_frame_width,
                height=self.image_frame_height,
                color=color,
                stroke_width=2,
            )
            frame.move_to(image)
            image.frame = frame
            image.add(frame)
            image.next_to(self.title, DOWN)
            alt_i = (i % 3) - 1
            vect = (self.image_frame_width + LARGE_BUFF) * RIGHT
            image.shift(alt_i * vect)
        return images

    def adjust_size(self, group):
        group.set_width(min(
            group.get_width(),
            self.image_frame_width - 2 * MED_SMALL_BUFF
        ))
        group.set_height(min(
            group.get_height(),
            self.image_frame_height - 2 * MED_SMALL_BUFF
        ))
        return group

    def get_hard_work_image(self):
        new_randy = self.pi_creature.copy()
        new_randy.change_mode("telepath")
        bubble = new_randy.get_bubble(height=3.5, width=4)
        bubble.add_content(Tex("\\frac{d}{dx}(\\sin(\\sqrt{x}))"))
        bubble.add(bubble.content)  # Remove?

        return VGroup(new_randy, bubble)

    def get_neat_example_image(self):
        filled_circle = Circle(
            stroke_width=0,
            fill_color=BLUE_E,
            fill_opacity=1
        )
        area = Tex("\\pi r^2")
        area.move_to(filled_circle)
        unfilled_circle = Circle(
            stroke_width=3,
            stroke_color=YELLOW,
            fill_opacity=0,
        )
        unfilled_circle.next_to(filled_circle, RIGHT)
        circles = VGroup(filled_circle, unfilled_circle)
        circumference = Tex("2\\pi r")
        circumference.move_to(unfilled_circle)
        equation = Tex(
            "{d (\\pi r^2) \\over dr}  = 2\\pi r",
            tex_to_color_map={
                "\\pi r^2": BLUE_D,
                "2\\pi r": YELLOW,
            }
        )
        equation.next_to(circles, UP)

        return VGroup(
            filled_circle, area,
            unfilled_circle, circumference,
            equation
        )

    def get_not_so_neat_example_image(self):
        return Tex("\\int x \\cos(x) \\, dx")

    def get_physics_image(self):
        t_max = 6.5
        r = 0.2
        spring = ParametricCurve(
            lambda t: op.add(
                r * (np.sin(TAU * t) * RIGHT + np.cos(TAU * t) * UP),
                t * DOWN,
            ),
            t_min=0, t_max=t_max,
            color=WHITE,
            stroke_width=2,
        )
        spring.color_using_background_image("grey_gradient")

        weight = Square()
        weight.set_stroke(width=0)
        weight.set_fill(opacity=1)
        weight.color_using_background_image("grey_gradient")
        weight.set_height(0.4)

        t_tracker = ValueTracker(0)
        group = VGroup(spring, weight)
        group.continual_animations = [
            t_tracker.add_udpater(
                lambda tracker, dt: tracker.set_value(
                    tracker.get_value() + dt
                )
            ),
            spring.add_updater(
                lambda s: s.stretch_to_fit_height(
                    1.5 + 0.5 * np.cos(3 * t_tracker.get_value()),
                    about_edge=UP
                )
            ),
            weight.add_updater(
                lambda w: w.move_to(spring.get_points()[-1])
            )
        ]

        def update_group_style(alpha):
            spring.set_stroke(width=2 * alpha)
            weight.set_fill(opacity=alpha)

        group.fade_in_anim = UpdateFromAlphaFunc(
            group,
            lambda g, a: update_group_style(a)
        )
        group.fade_out_anim = UpdateFromAlphaFunc(
            group,
            lambda g, a: update_group_style(1 - a)
        )
        return group

    def get_piles_of_formulas_image(self):
        return Tex("(f/g)' = \\frac{gf' - fg'}{g^2}")

    def get_getting_stuck_image(self):
        creature = self.pi_creature.copy()
        creature.change_mode("angry")
        equation = Tex("\\frac{d}{dx}(x^x)")
        equation.set_height(creature.get_height() / 2)
        equation.next_to(creature, RIGHT, aligned_edge=UP)
        creature.look_at(equation)
        return VGroup(creature, equation)

    def get_aha_image(self):
        creature = self.pi_creature.copy()
        creature.change_mode("hooray")
        from _2017.eoc.eoc.chapter3 import NudgeSideLengthOfCube
        scene = NudgeSideLengthOfCube(
            end_at_animation_number=7,
            skip_animations=True
        )
        group = VGroup(
            scene.cube, scene.faces,
            scene.bars, scene.corner_cube,
        )
        group.set_height(0.75 * creature.get_height())
        group.next_to(creature, RIGHT)
        creature.look_at(group)
        return VGroup(creature, group)

    def get_graphical_intuition_image(self):
        gs = GraphScene()
        gs.setup_axes()
        graph = gs.get_graph(
            lambda x: 0.2 * (x - 3) * (x - 5) * (x - 6) + 4,
            x_min=2, x_max=8,
        )
        rects = gs.get_riemann_rectangles(
            graph, x_min=2, x_max=8,
            stroke_width=0.5,
            dx=0.25
        )
        gs.add(graph, rects, gs.axes)
        group = VGroup(*gs.mobjects)
        self.adjust_size(group)
        group.next_to(self.title, DOWN, MED_LARGE_BUFF)
        group.rects = rects
        group.continual_animations = [
            turn_animation_into_updater(Write(rects)),
            turn_animation_into_updater(ShowCreation(graph)),
            turn_animation_into_updater(FadeIn(gs.axes)),
        ]
        self.adjust_size(group)
        return group


class GraphicalIntuitions(GraphScene):
    CONFIG = {
        "func": lambda x: 0.1 * (x - 2) * (x - 5) * (x - 7) + 4,
        "x_labeled_nums": list(range(1, 10)),
    }

    def construct(self):
        self.setup_axes()
        axes = self.axes
        graph = self.get_graph(self.func)

        ss_group = self.get_secant_slope_group(
            x=8, graph=graph, dx=0.01,
            secant_line_length=6,
            secant_line_color=RED,
        )
        rects = self.get_riemann_rectangles(
            graph, x_min=2, x_max=8, dx=0.01, stroke_width=0
        )

        deriv_text = TexText(
            "Derivative $\\rightarrow$ slope",
            tex_to_color_map={"slope": ss_group.secant_line.get_color()}
        )
        deriv_text.to_edge(UP)
        integral_text = TexText(
            "Integral $\\rightarrow$ area",
            tex_to_color_map={"area": rects[0].get_color()}
        )
        integral_text.next_to(deriv_text, DOWN)

        self.play(
            Succession(Write(axes), ShowCreation(graph, run_time=2)),
            self.get_graph_words_anim(),
        )
        self.animate_secant_slope_group_change(
            ss_group,
            target_x=2,
            rate_func=smooth,
            run_time=2.5,
            added_anims=[
                Write(deriv_text),
                VFadeIn(ss_group, run_time=2),
            ]
        )
        self.play(FadeIn(integral_text))
        self.play(
            LaggedStartMap(
                GrowFromEdge, rects,
                lambda r: (r, DOWN)
            ),
            Animation(axes),
            Animation(graph),
        )
        self.wait()

    def get_graph_words_anim(self):
        words = VGroup(
            TexText("Graphs,"),
            TexText("graphs,"),
            TexText("non-stop graphs"),
            TexText("all day"),
            TexText("every day"),
            TexText("as if to visualize is to graph"),
        )
        for word in words:
            word.add_background_rectangle()
        words.arrange(DOWN)
        words.to_edge(UP)
        return LaggedStartMap(
            FadeIn, words,
            rate_func=there_and_back,
            run_time=len(words) - 1,
            lag_ratio=0.6,
            remover=True
        )


class Wrapper(Scene):
    CONFIG = {
        "title": "",
        "title_kwargs": {},
        "screen_height": 6,
        "wait_time": 2,
    }

    def construct(self):
        rect = self.rect = ScreenRectangle(height=self.screen_height)
        title = self.title = TexText(self.title, **self.title_kwargs)
        title.to_edge(UP)
        rect.next_to(title, DOWN)

        self.add(title)
        self.play(ShowCreation(rect))
        self.wait(self.wait_time)


class DomainColoringWrapper(Wrapper):
    CONFIG = {
        "title": "Complex $\\rightarrow$ Complex",
    }


class R2ToR2Wrapper(Wrapper):
    CONFIG = {"title": "$\\mathds{R}^2 \\rightarrow \\mathds{R}^2$"}


class ExampleMultivariableFunction(LinearTransformationScene):
    CONFIG = {
        "show_basis_vectors": False,
        "show_coordinates": True,
    }

    def construct(self):
        def example_function(point):
            x, y, z = point
            return np.array([
                x + np.sin(y),
                y + np.sin(x),
                0
            ])
        self.wait()
        self.apply_nonlinear_transformation(example_function, run_time=5)
        self.wait()


class ChangingVectorFieldWrapper(Wrapper):
    CONFIG = {"title": "$(x, y, t) \\rightarrow (x', y')$"}


class ChangingVectorField(Scene):
    CONFIG = {
        "wait_time": 30,
    }

    def construct(self):
        plane = self.plane = NumberPlane()
        plane.set_stroke(width=2)
        plane.add_coordinates()
        self.add(plane)

        # Obviously a silly thing to do, but I'm sweeping
        # through trying to make sure old scenes don't
        # completely break in spots which used to have
        # Continual animations
        time_tracker = self.time_tracker = ValueTracker(0)
        time_tracker.add_updater(
            lambda t: t.set_value(self.get_time())
        )

        vectors = self.get_vectors()
        vectors.add_updater(self.update_vectors)
        self.add(vectors)
        self.wait(self.wait_time)

    def get_vectors(self):
        vectors = VGroup()
        x_max = int(np.ceil(FRAME_WIDTH))
        y_max = int(np.ceil(FRAME_HEIGHT))
        step = 0.5
        for x in np.arange(-x_max, x_max + 1, step):
            for y in np.arange(-y_max, y_max + 1, step):
                point = x * RIGHT + y * UP
                vectors.add(Vector(RIGHT).shift(point))
        vectors.set_color_by_gradient(YELLOW, RED)
        return vectors

    def update_vectors(self, vectors):
        time = self.time_tracker.get_value()
        for vector in vectors:
            point = vector.get_start()
            out_point = self.func(point, time)
            norm = get_norm(out_point)
            if norm == 0:
                out_point = RIGHT  # Fake it
                vector.set_fill(opacity=0)
            else:
                out_point *= 0.5
                color = interpolate_color(BLUE, RED, norm / np.sqrt(8))
                vector.set_fill(color, opacity=1)
                vector.set_stroke(BLACK, width=1)
            new_x, new_y = out_point[:2]
            vector.put_start_and_end_on(
                point, point + new_x * RIGHT + new_y * UP
            )

    def func(self, point, time):
        x, y, z = point
        return np.array([
            np.sin(time + 0.5 * x + y),
            np.cos(time + 0.2 * x * y + 0.7),
            0
        ])


class MoreTopics(Scene):
    def construct(self):
        calculus = TexText("Calculus")
        calculus.next_to(LEFT, LEFT)
        calculus.set_color(YELLOW)
        calculus.add_background_rectangle()
        others = VGroup(
            TexText("Multivariable calculus"),
            TexText("Complex analysis"),
            TexText("Differential geometry"),
            TexText("$\\vdots$")
        )
        for word in others:
            word.add_background_rectangle()
        others.arrange(
            DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT,
        )
        others.next_to(RIGHT, RIGHT)
        lines = VGroup(*[
            Line(calculus.get_right(), word.get_left(), buff=MED_SMALL_BUFF)
            for word in others
        ])

        rect = FullScreenFadeRectangle(fill_opacity=0.7)
        self.add(rect)

        self.add(calculus)
        self.play(
            LaggedStartMap(ShowCreation, lines),
            LaggedStartMap(Write, others),
        )
        self.wait()

        self.calculus = calculus
        self.lines = lines
        self.full_screen_rect = rect
        self.other_topics = others


class TransformationalViewWrapper(Wrapper):
    CONFIG = {
        "title": "Transformational view"
    }


class SetTheStage(TeacherStudentsScene):
    def construct(self):
        ordinary = TexText("Ordinary visual")
        transformational = TexText("Transformational visual")
        for word in ordinary, transformational:
            word.move_to(self.hold_up_spot, DOWN)
            word.shift_onto_screen()

        self.screen.scale(1.25, about_edge=UL)
        self.add(self.screen)
        self.teacher_holds_up(
            ordinary,
            added_anims=[self.get_student_changes(*3 * ["sassy"])]
        )
        self.wait()
        self.play(
            ordinary.shift, UP,
            FadeInFromDown(transformational),
            self.teacher.change, "hooray",
            self.get_student_changes(*3 * ["erm"])
        )
        self.wait(3)
        self.change_all_student_modes("pondering", look_at_arg=self.screen)


class StandardDerivativeVisual(GraphScene):
    CONFIG = {
        "y_max": 8,
        "y_axis_height": 5,
    }

    def construct(self):
        self.add_title()
        self.show_function_graph()
        self.show_slope_of_graph()
        self.encourage_not_to_think_of_slope_as_definition()
        self.show_sensitivity()

    def add_title(self):
        title = self.title = TexText("Standard derivative visual")
        title.to_edge(UP)
        h_line = Line(LEFT, RIGHT)
        h_line.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
        h_line.next_to(title, DOWN)

        self.add(title, h_line)

    def show_function_graph(self):
        self.setup_axes()

        def func(x):
            x -= 5
            return 0.1 * (x + 3) * (x - 3) * x + 3
        graph = self.get_graph(func)
        graph_label = self.get_graph_label(graph, x_val=9.5)

        input_tracker = ValueTracker(4)

        def get_x_value():
            return input_tracker.get_value()

        def get_y_value():
            return graph.underlying_function(get_x_value())

        def get_x_point():
            return self.coords_to_point(get_x_value(), 0)

        def get_y_point():
            return self.coords_to_point(0, get_y_value())

        def get_graph_point():
            return self.coords_to_point(get_x_value(), get_y_value())

        def get_v_line():
            return DashedLine(get_x_point(), get_graph_point(), stroke_width=2)

        def get_h_line():
            return DashedLine(get_graph_point(), get_y_point(), stroke_width=2)

        input_triangle = RegularPolygon(n=3, start_angle=TAU / 4)
        output_triangle = RegularPolygon(n=3, start_angle=0)
        for triangle in input_triangle, output_triangle:
            triangle.set_fill(WHITE, 1)
            triangle.set_stroke(width=0)
            triangle.scale(0.1)

        input_triangle_update = input_tracker.add_updater(
            lambda m: m.move_to(get_x_point(), UP)
        )
        output_triangle_update = output_triangle.add_updater(
            lambda m: m.move_to(get_y_point(), RIGHT)
        )

        x_label = Tex("x")
        x_label_update = Mobject.add_updater(
            x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF)
        )

        output_label = Tex("f(x)")
        output_label_update = Mobject.add_updater(
            output_label, lambda m: m.next_to(
                output_triangle, LEFT, SMALL_BUFF)
        )

        v_line = get_v_line()
        v_line_update = Mobject.add_updater(
            v_line, lambda vl: Transform(vl, get_v_line()).update(1)
        )

        h_line = get_h_line()
        h_line_update = Mobject.add_updater(
            h_line, lambda hl: Transform(hl, get_h_line()).update(1)
        )

        graph_dot = Dot(color=YELLOW)
        graph_dot_update = Mobject.add_updater(
            graph_dot, lambda m: m.move_to(get_graph_point())
        )

        self.play(
            ShowCreation(graph),
            Write(graph_label),
        )
        self.play(
            DrawBorderThenFill(input_triangle, run_time=1),
            Write(x_label),
            ShowCreation(v_line),
            GrowFromCenter(graph_dot),
        )
        self.add_foreground_mobject(graph_dot)
        self.play(
            ShowCreation(h_line),
            Write(output_label),
            DrawBorderThenFill(output_triangle, run_time=1)
        )
        self.add(
            input_triangle_update,
            x_label_update,
            graph_dot_update,
            v_line_update,
            h_line_update,
            output_triangle_update,
            output_label_update,
        )
        self.play(
            input_tracker.set_value, 8,
            run_time=6,
            rate_func=there_and_back
        )

        self.input_tracker = input_tracker
        self.graph = graph

    def show_slope_of_graph(self):
        input_tracker = self.input_tracker
        deriv_input_tracker = ValueTracker(input_tracker.get_value())

        # Slope line
        def get_slope_line():
            return self.get_secant_slope_group(
                x=deriv_input_tracker.get_value(),
                graph=self.graph,
                dx=0.01,
                secant_line_length=4
            ).secant_line

        slope_line = get_slope_line()
        slope_line_update = Mobject.add_updater(
            slope_line, lambda sg: Transform(sg, get_slope_line()).update(1)
        )

        def position_deriv_label(deriv_label):
            deriv_label.next_to(slope_line, UP)
            return deriv_label
        deriv_label = Tex(
            "\\frac{df}{dx}(x) =", "\\text{Slope}", "="
        )
        deriv_label.get_part_by_tex("Slope").match_color(slope_line)
        deriv_label_update = Mobject.add_updater(
            deriv_label, position_deriv_label
        )

        slope_decimal = DecimalNumber(slope_line.get_slope())
        slope_decimal.match_color(slope_line)
        slope_decimal.add_updater(
            lambda d: d.set_value(slope_line.get_slope())
        )
        slope_decimal.add_upater(
            lambda d: d.next_to(
                deriv_label, RIGHT, SMALL_BUFF
            ).shift(0.2 * SMALL_BUFF * DOWN)
        )

        self.play(
            ShowCreation(slope_line),
            Write(deriv_label),
            Write(slope_decimal),
            run_time=1
        )
        self.wait()
        self.add(
            slope_line_update,
            # deriv_label_update,
        )
        for x in 9, 2, 4:
            self.play(
                input_tracker.set_value, x,
                deriv_input_tracker.set_value, x,
                run_time=3
            )
            self.wait()

        self.deriv_input_tracker = deriv_input_tracker

    def encourage_not_to_think_of_slope_as_definition(self):
        morty = Mortimer(height=2)
        morty.to_corner(DR)

        self.play(FadeIn(morty))
        self.play(PiCreatureSays(
            morty, "Don't think of \\\\ this as the definition",
            bubble_kwargs={"height": 2, "width": 4}
        ))
        self.play(Blink(morty))
        self.wait()
        self.play(
            RemovePiCreatureBubble(morty),
            UpdateFromAlphaFunc(
                morty, lambda m, a: m.set_fill(opacity=1 - a),
                remover=True
            )
        )

    def show_sensitivity(self):
        input_tracker = self.input_tracker
        deriv_input_tracker = self.deriv_input_tracker

        self.wiggle_input()
        for x in 9, 7, 2:
            self.play(
                input_tracker.set_value, x,
                deriv_input_tracker.set_value, x,
                run_time=3
            )
            self.wiggle_input()

    ###
    def wiggle_input(self, dx=0.5, run_time=3):
        input_tracker = self.input_tracker

        x = input_tracker.get_value()
        x_min = x - dx
        x_max = x + dx
        y, y_min, y_max = list(map(
            self.graph.underlying_function,
            [x, x_min, x_max]
        ))
        x_line = Line(
            self.coords_to_point(x_min, 0),
            self.coords_to_point(x_max, 0),
        )
        y_line = Line(
            self.coords_to_point(0, y_min),
            self.coords_to_point(0, y_max),
        )

        x_rect, y_rect = rects = VGroup(Rectangle(), Rectangle())
        rects.set_stroke(width=0)
        rects.set_fill(YELLOW, 0.5)
        x_rect.match_width(x_line)
        x_rect.stretch_to_fit_height(0.25)
        x_rect.move_to(x_line)
        y_rect.match_height(y_line)
        y_rect.stretch_to_fit_width(0.25)
        y_rect.move_to(y_line)

        self.play(
            ApplyMethod(
                input_tracker.set_value, input_tracker.get_value() + dx,
                rate_func=lambda t: wiggle(t, 6)
            ),
            FadeIn(
                rects,
                rate_func=squish_rate_func(smooth, 0, 0.33),
                remover=True,
            ),
            run_time=run_time,
        )
        self.play(FadeOut(rects))


class EoCWrapper(Scene):
    def construct(self):
        title = Title("Essence of calculus")
        self.play(Write(title))
        self.wait()


class IntroduceTransformationView(NumberlineTransformationScene):
    CONFIG = {
        "func": lambda x: 0.5 * np.sin(2 * x) + x,
        "number_line_config": {
            "x_min": 0,
            "x_max": 6,
            "unit_size": 2.0
        },
    }

    def construct(self):
        self.add_title()
        self.show_animation_preview()
        self.indicate_indicate_point_densities()
        self.show_zoomed_transformation()

    def add_title(self):
        title = self.title = TexText("$f(x)$ as a transformation")
        title.to_edge(UP)
        self.add(title)

    def show_animation_preview(self):
        input_points = self.get_sample_input_points()
        output_points = list(map(
            self.number_func_to_point_func(self.func),
            input_points
        ))
        sample_dots = self.get_sample_dots()
        sample_dot_ghosts = sample_dots.copy().fade(0.5)
        arrows = VGroup(*[
            Arrow(ip, op, buff=MED_SMALL_BUFF)
            for ip, op in zip(input_points, output_points)
        ])
        arrows = arrows[1::3]
        arrows.set_stroke(BLACK, 1)

        for sd in sample_dots:
            sd.save_state()
            sd.scale(2)
            sd.fade(1)
        self.play(LaggedStartMap(
            ApplyMethod, sample_dots,
            lambda sd: (sd.restore,),
            run_time=2
        ))
        self.play(LaggedStartMap(
            GrowArrow, arrows,
            run_time=6,
            lag_ratio=0.3,
        ))
        self.add(sample_dot_ghosts)
        self.apply_function(
            self.func, sample_dots=sample_dots,
            run_time=3
        )
        self.wait()
        self.play(LaggedStartMap(FadeOut, arrows, run_time=1))

        self.sample_dots = sample_dots
        self.sample_dot_ghosts = sample_dot_ghosts

    def indicate_indicate_point_densities(self):
        lower_brace = Brace(Line(LEFT, RIGHT), UP)
        upper_brace = lower_brace.copy()
        input_tracker = ValueTracker(0.5)
        dx = 0.5

        def update_upper_brace(brace):
            x = input_tracker.get_value()
            line = Line(
                self.get_input_point(x),
                self.get_input_point(x + dx),
            )
            brace.match_width(line, stretch=True)
            brace.next_to(line, UP, buff=SMALL_BUFF)
            return brace

        def update_lower_brace(brace):
            x = input_tracker.get_value()
            line = Line(
                self.get_output_point(self.func(x)),
                self.get_output_point(self.func(x + dx)),
            )
            brace.match_width(line, stretch=True)
            brace.next_to(line, UP, buff=SMALL_BUFF)
            return brace

        lower_brace_anim = UpdateFromFunc(lower_brace, update_lower_brace)
        upper_brace_anim = UpdateFromFunc(upper_brace, update_upper_brace)

        new_title = TexText(
            "$\\frac{df}{dx}(x)$ measures stretch/squishing"
        )
        new_title.move_to(self.title, UP)

        stretch_factor = DecimalNumber(0, color=YELLOW)
        stretch_factor_anim = ChangingDecimal(
            stretch_factor, lambda a: lower_brace.get_width() / upper_brace.get_width(),
            position_update_func=lambda m: m.next_to(lower_brace, UP, SMALL_BUFF)
        )

        self.play(
            GrowFromCenter(upper_brace),
            FadeOut(self.title),
            # FadeIn(new_title)
            Write(new_title, run_time=2)
        )
        self.title = new_title
        self.play(
            ReplacementTransform(upper_brace.copy(), lower_brace),
            GrowFromPoint(stretch_factor, upper_brace.get_center())
        )
        self.play(
            input_tracker.set_value, self.input_line.x_max - dx,
            lower_brace_anim,
            upper_brace_anim,
            stretch_factor_anim,
            run_time=8,
            rate_func=bezier([0, 0, 1, 1])
        )
        self.wait()

        new_sample_dots = self.get_sample_dots()
        self.play(
            FadeOut(VGroup(
                upper_brace, lower_brace, stretch_factor,
                self.sample_dots, self.moving_input_line,
            )),
            FadeIn(new_sample_dots),
        )
        self.sample_dots = new_sample_dots

    def show_zoomed_transformation(self):
        x = 2.75
        local_sample_dots = self.get_local_sample_dots(x)

        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=self.get_local_coordinate_values(x),
        )
        self.wait()
        self.apply_function(
            self.func,
            sample_dots=self.sample_dots,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=self.get_local_coordinate_values(self.func(x))
        )
        self.wait()


class ExamplePlease(TeacherStudentsScene):
    def construct(self):
        self.student_says("Example?", student_index=0)
        self.teacher_holds_up(Tex("f(x) = x^2").scale(1.5))
        self.wait(2)


class TalkThroughXSquaredExample(IntroduceTransformationView):
    CONFIG = {
        "func": lambda x: x**2,
        "number_line_config": {
            "x_min": 0,
            "x_max": 5,
            "unit_size": 1.25,
        },
        "output_line_config": {
            "x_max": 25,
        },
        "default_delta_x": 0.2
    }

    def construct(self):
        self.add_title()
        self.show_specific_points_mapping()

    def add_title(self):
        title = self.title = TexText("$f(x) = x^2$")
        title.to_edge(UP, buff=MED_SMALL_BUFF)
        self.add(title)

    def show_specific_points_mapping(self):
        # First, just show integers as examples
        int_dots = self.get_sample_dots(1, 6, 1)
        int_dot_ghosts = int_dots.copy().fade(0.5)
        int_arrows = VGroup(*[
            Arrow(
                # num.get_bottom(),
                self.get_input_point(x),
                self.get_output_point(self.func(x)),
                buff=MED_SMALL_BUFF
            )
            for x, num in zip(list(range(1, 6)), self.input_line.numbers[1:])
        ])
        point_func = self.number_func_to_point_func(self.func)

        numbers = self.input_line.numbers
        numbers.next_to(self.input_line, UP, SMALL_BUFF)
        self.titles[0].next_to(numbers, UP, MED_SMALL_BUFF, LEFT)
        # map(Tex.add_background_rectangle, numbers)
        # self.add_foreground_mobject(numbers)

        for dot, dot_ghost, arrow in zip(int_dots, int_dot_ghosts, int_arrows):
            arrow.match_color(dot)
            self.play(DrawBorderThenFill(dot, run_time=1))
            self.add(dot_ghost)
            self.play(
                GrowArrow(arrow),
                dot.apply_function_to_position, point_func
            )
        self.wait()

        # Show more sample_dots
        sample_dots = self.get_sample_dots()
        sample_dot_ghosts = sample_dots.copy().fade(0.5)

        self.play(
            LaggedStartMap(DrawBorderThenFill, sample_dots),
            LaggedStartMap(FadeOut, int_arrows),
        )
        self.remove(int_dot_ghosts)
        self.add(sample_dot_ghosts)
        self.apply_function(self.func, sample_dots=sample_dots)
        self.remove(int_dots)
        self.wait()

        self.sample_dots = sample_dots
        self.sample_dot_ghosts = sample_dot_ghosts

    def get_stretch_words(self, factor, color=RED, less_than_one=False):
        factor_str = "$%s$" % str(factor)
        result = TexText(
            "Scale \\\\ by", factor_str,
            tex_to_color_map={factor_str: color}
        )
        result.scale(0.7)
        la, ra = Tex("\\leftarrow \\rightarrow")
        if less_than_one:
            la, ra = ra, la
        if factor < 0:
            kwargs = {
                "path_arc": np.pi,
            }
            la = Arrow(UP, DOWN, **kwargs)
            ra = Arrow(DOWN, UP, **kwargs)
            for arrow in la, ra:
                arrow.pointwise_become_partial(arrow, 0, 0.9)
                arrow.tip.scale(2)
            VGroup(la, ra).match_height(result)
        la.next_to(result, LEFT)
        ra.next_to(result, RIGHT)
        result.add(la, ra)
        result.next_to(
            self.zoomed_display.get_top(), DOWN, SMALL_BUFF
        )
        return result

    def get_deriv_equation(self, x, rhs, color=RED):
        deriv_equation = self.deriv_equation = Tex(
            "\\frac{df}{dx}(", str(x), ")", "=", str(rhs),
            tex_to_color_map={str(x): color, str(rhs): color}
        )
        deriv_equation.next_to(self.title, DOWN, MED_LARGE_BUFF)
        return deriv_equation


class ZoomInOnXSquaredNearOne(TalkThroughXSquaredExample):
    def setup(self):
        TalkThroughXSquaredExample.setup(self)
        self.force_skipping()
        self.add_title()
        self.show_specific_points_mapping()
        self.revert_to_original_skipping_status()

    def construct(self):
        zoom_words = TexText("Zoomed view \\\\ near 1")
        zoom_words.next_to(self.zoomed_display, DOWN)
        # zoom_words.shift_onto_screen()

        x = 1
        local_sample_dots = self.get_local_sample_dots(x)
        local_coords = self.get_local_coordinate_values(x, dx=0.1)

        zcbr_anim = self.zoomed_camera_background_rectangle_anim
        zcbr_group = self.zoomed_camera_background_rectangle_group
        frame = self.zoomed_camera.frame
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()

        self.zoom_in_on_input(x, local_sample_dots, local_coords)
        self.play(FadeIn(zoom_words))
        self.wait()
        local_sample_dots.save_state()
        frame.save_state()
        self.mini_line.save_state()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coords
        )
        self.remove(sample_dot_ghost_copies)
        self.wait()

        # Go back
        self.play(
            frame.restore,
            self.mini_line.restore,
            local_sample_dots.restore,
            zcbr_anim,
            Animation(zcbr_group)
        )
        self.wait()

        # Zoom in even more
        extra_zoom_factor = 0.3
        one_group = VGroup(
            self.local_coordinates.tick_marks[1],
            self.local_coordinates.numbers[1],
        )
        all_other_coordinates = VGroup(
            self.local_coordinates.tick_marks[::2],
            self.local_coordinates.numbers[::2],
            self.local_target_coordinates,
        )
        self.play(frame.scale, extra_zoom_factor)
        new_local_sample_dots = self.get_local_sample_dots(x, delta_x=0.005)
        new_coordinate_values = self.get_local_coordinate_values(x, dx=0.02)
        new_local_coordinates = self.get_local_coordinates(
            self.input_line, *new_coordinate_values
        )

        self.play(
            Write(new_local_coordinates),
            Write(new_local_sample_dots),
            one_group.scale, extra_zoom_factor, {"about_point": self.get_input_point(1)},
            FadeOut(all_other_coordinates),
            *[
                ApplyMethod(dot.scale, extra_zoom_factor)
                for dot in local_sample_dots
            ]
        )
        self.remove(one_group, local_sample_dots)
        zcbr_group.remove(
            self.local_coordinates, self.local_target_coordinates,
            local_sample_dots
        )

        # Transform new zoomed view
        stretch_by_two_words = self.get_stretch_words(2)
        self.add_foreground_mobject(stretch_by_two_words)
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=new_local_sample_dots,
            target_coordinate_values=new_coordinate_values,
            added_anims=[FadeIn(stretch_by_two_words)]
        )
        self.remove(sample_dot_ghost_copies)
        self.wait()

        # Write derivative
        deriv_equation = self.get_deriv_equation(1, 2, color=RED)
        self.play(Write(deriv_equation))
        self.wait()


class ZoomInOnXSquaredNearThree(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoomed_display_width": 4,
    }

    def construct(self):
        zoom_words = TexText("Zoomed view \\\\ near 3")
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 3
        local_sample_dots = self.get_local_sample_dots(x)
        local_coordinate_values = self.get_local_coordinate_values(x, dx=0.1)
        target_coordinate_values = self.get_local_coordinate_values(self.func(x), dx=0.1)

        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words(2 * x, color)
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            pop_out=False,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values,
            added_anims=[Write(stretch_words)]
        )
        self.wait(2)


class ZoomInOnXSquaredNearOneFourth(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoom_factor": 0.01,
        "local_coordinate_num_decimal_places": 4,
        "zoomed_display_width": 4,
        "default_delta_x": 0.25,
    }

    def construct(self):
        # Much copy-pasting from previous scenes.  Not great, but
        # the fastest way to get the ease-of-tweaking I'd like.
        zoom_words = TexText("Zoomed view \\\\ near $1/4$")
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 0.25
        local_sample_dots = self.get_local_sample_dots(
            x, sample_radius=2.5 * self.zoomed_camera.frame.get_width(),
        )
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=0.01,
        )
        target_coordinate_values = self.get_local_coordinate_values(
            self.func(x), dx=0.01,
        )

        color = RED
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words("1/2", color, less_than_one=True)
        deriv_equation = self.get_deriv_equation("1/4", "1/2", color)

        one_fourth_point = self.get_input_point(x)
        one_fourth_arrow = Vector(0.5 * UP, color=WHITE)
        one_fourth_arrow.stem.stretch(0.75, 0)
        one_fourth_arrow.tip.scale(0.75, about_edge=DOWN)
        one_fourth_arrow.next_to(one_fourth_point, DOWN, SMALL_BUFF)
        one_fourth_label = Tex("0.25")
        one_fourth_label.match_height(self.input_line.numbers)
        one_fourth_label.next_to(one_fourth_arrow, DOWN, SMALL_BUFF)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
            pop_out=False,
            first_added_anims=[
                FadeIn(one_fourth_label),
                GrowArrow(one_fourth_arrow),
            ]
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values,
            added_anims=[Write(stretch_words)]
        )
        self.wait(2)


class ZoomInOnXSquaredNearZero(ZoomInOnXSquaredNearOne):
    CONFIG = {
        "zoom_factor": 0.1,
        "zoomed_display_width": 4,
        "scale_by_term": "???",
    }

    def construct(self):
        zoom_words = TexText(
            "Zoomed %sx \\\\ near 0" % "{:,}".format(int(1.0 / self.zoom_factor))
        )
        zoom_words.next_to(self.zoomed_display, DOWN)

        x = 0
        local_sample_dots = self.get_local_sample_dots(
            x, sample_radius=2 * self.zoomed_camera.frame.get_width()
        )
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=self.zoom_factor
        )
        # target_coordinate_values = self.get_local_coordinate_values(
        #     self.func(x), dx=self.zoom_factor
        # )

        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        stretch_words = self.get_stretch_words(
            self.scale_by_term, color, less_than_one=True
        )
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)

        self.add(deriv_equation)
        self.zoom_in_on_input(
            x,
            pop_out=False,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.play(Write(zoom_words, run_time=1))
        self.wait()
        self.add_foreground_mobject(stretch_words)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            # target_coordinate_values=target_coordinate_values,
            added_anims=[
                Write(stretch_words),
                MaintainPositionRelativeTo(
                    self.local_coordinates,
                    self.zoomed_camera.frame
                )
            ]
        )
        self.wait(2)


class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero):
    def construct(self):
        x = 0
        color = self.sample_dots[len(self.sample_dots) / 2].get_color()
        deriv_equation = self.get_deriv_equation(x, 2 * x, color)
        self.add(deriv_equation)

        frame = self.zoomed_camera.frame
        zoomed_display_height = self.zoomed_display.get_height()

        last_sample_dots = VGroup()
        last_coords = VGroup()
        last_zoom_words = None
        for factor in 0.1, 0.01, 0.001, 0.0001:
            frame.save_state()
            frame.set_height(factor * zoomed_display_height)
            self.local_coordinate_num_decimal_places = int(-np.log10(factor))
            zoom_words = TexText(
                "Zoomed", "{:,}x \\\\".format(int(1.0 / factor)),
                "near 0",
            )
            zoom_words.next_to(self.zoomed_display, DOWN)

            sample_dots = self.get_local_sample_dots(x)
            coords = self.get_local_coordinate_values(x, dx=factor)
            frame.restore()

            added_anims = [
                ApplyMethod(last_sample_dots.fade, 1),
                ApplyMethod(last_coords.fade, 1),
            ]
            if last_zoom_words is not None:
                added_anims.append(ReplacementTransform(
                    last_zoom_words, zoom_words
                ))
            else:
                added_anims.append(FadeIn(zoom_words))
            self.zoom_in_on_input(
                x,
                local_sample_dots=sample_dots,
                local_coordinate_values=coords,
                pop_out=False,
                zoom_factor=factor,
                first_added_anims=added_anims
            )
            self.wait()
            last_sample_dots = sample_dots
            last_coords = self.local_coordinates
            last_zoom_words = zoom_words


class ZoomInOnXSquared100xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.01
    }


class ZoomInOnXSquared1000xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.001,
        "local_coordinate_num_decimal_places": 3,
    }


class ZoomInOnXSquared10000xZero(ZoomInOnXSquaredNearZero):
    CONFIG = {
        "zoom_factor": 0.0001,
        "local_coordinate_num_decimal_places": 4,
        "scale_by_term": "0",
    }


class XSquaredForNegativeInput(TalkThroughXSquaredExample):
    CONFIG = {
        "input_line_config": {
            "x_min": -4,
            "x_max": 4,
        },
        "input_line_zero_point": 0.5 * UP + 0 * LEFT,
        "output_line_config": {},
        "default_mapping_animation_config": {
            "path_arc": 30 * DEGREES
        },
        "zoomed_display_width": 4,
    }

    def construct(self):
        self.add_title()
        self.show_full_transformation()
        self.zoom_in_on_example()

    def show_full_transformation(self):
        sample_dots = self.get_sample_dots(
            x_min=-4.005,
            delta_x=0.05,
            dot_radius=0.05
        )
        sample_dots.set_fill(opacity=0.8)

        self.play(LaggedStartMap(DrawBorderThenFill, sample_dots))
        self.play(LaggedStartMap(
            ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1],
            lambda mob: (
                lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK),
                mob,
            ),
            rate_func=there_and_back,
        ))
        self.add_sample_dot_ghosts(sample_dots)
        self.apply_function(self.func, sample_dots=sample_dots)
        self.wait()

    def zoom_in_on_example(self):
        x = -2

        local_sample_dots = self.get_local_sample_dots(x)
        local_coordinate_values = self.get_local_coordinate_values(
            x, dx=0.1
        )
        target_coordinate_values = self.get_local_coordinate_values(
            self.func(x), dx=0.1
        )
        deriv_equation = self.get_deriv_equation(x, 2 * x, color=BLUE)
        sample_dot_ghost_copies = self.sample_dot_ghosts.copy()
        scale_words = self.get_stretch_words(-4, color=BLUE)

        self.zoom_in_on_input(
            x,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
        )
        self.wait()
        self.play(Write(deriv_equation))
        self.add_foreground_mobject(scale_words)
        self.play(Write(scale_words))
        self.apply_function(
            self.func,
            sample_dots=sample_dot_ghost_copies,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=target_coordinate_values
        )
        self.wait()


class FeelsALittleCramped(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Kind of cramped,\\\\ isn't it?",
            target_mode="sassy"
        )
        self.wait()
        self.teacher_says(
            "Sure, but think \\\\ locally"
        )
        self.change_all_student_modes("pondering", look_at_arg=self.screen)
        self.wait(3)


class HowDoesThisSolveProblems(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Is this...useful?",
            target_mode="confused"
        )
        self.change_student_modes("maybe", "confused", "sassy")
        self.play(self.teacher.change, "happy")
        self.wait(3)


class IntroduceContinuedFractionPuzzle(PiCreatureScene):
    CONFIG = {
        "remove_initial_rhs": True,
    }

    def construct(self):
        self.ask_question()
        self.set_equal_to_x()

    def create_pi_creatures(self):
        morty = Mortimer(height=2)
        morty.to_corner(DR)

        friend = PiCreature(color=GREEN, height=2)
        friend.to_edge(DOWN)
        friend.shift(LEFT)

        group = VGroup(morty, friend)
        group.shift(2 * LEFT)

        return morty, friend

    def ask_question(self):
        morty, friend = self.pi_creatures
        frac = get_phi_continued_fraction(9)
        frac.scale(0.8)
        rhs = DecimalNumber(
            (1 - np.sqrt(5)) / 2.0,
            num_decimal_places=5,
            show_ellipsis=True,
        )
        rhs.set_color(YELLOW)
        equals = Tex("=")
        equals.next_to(frac.get_part_by_tex("\\over"), RIGHT)
        rhs.next_to(equals, RIGHT)
        group = VGroup(frac, equals, rhs)
        group.scale(1.5)
        group.to_corner(UR)

        self.play(
            LaggedStartMap(
                Write, frac,
                run_time=15,
                lag_ratio=0.15,
            ),
            FadeInFromDown(equals),
            FadeInFromDown(rhs),
            PiCreatureSays(
                friend, "Would this be valid? \\\\ If not, why not?",
                target_mode="confused",
                look_at_arg=frac,
                bubble_kwargs={
                    "direction": RIGHT,
                    "width": 4,
                    "height": 3,
                }
            ),
            morty.change, "pondering",
        )
        self.wait()

        anims = [
            RemovePiCreatureBubble(
                friend, target_mode="pondering",
                look_at_arg=frac
            ),
        ]
        if self.remove_initial_rhs:
            anims += [
                Animation(frac),
                FadeOut(equals),
                rhs.scale, 0.5,
                rhs.to_corner, DL,
            ]
        self.play(*anims)

        self.neg_one_over_phi = rhs
        self.equals = equals
        self.frac = frac

    def set_equal_to_x(self):
        frac = self.frac
        morty, friend = self.get_pi_creatures()

        inner_frac = frac[4:]
        inner_frac_rect = SurroundingRectangle(
            inner_frac, stroke_width=2, buff=0.5 * SMALL_BUFF
        )
        inner_frac_group = VGroup(inner_frac, inner_frac_rect)

        equals = Tex("=")
        equals.next_to(frac[3], RIGHT)
        x, new_x = [Tex("x") for i in range(2)]
        xs = VGroup(x, new_x)
        xs.set_color(YELLOW)
        xs.scale(1.3)
        x.next_to(equals, RIGHT)
        new_x.next_to(frac[3], DOWN, 2 * SMALL_BUFF)

        fixed_point_words = VGroup(
            TexText("Fixed point of"),
            Tex(
                "f(x) = 1 + \\frac{1}{x}",
                tex_to_color_map={"x": YELLOW}
            )
        )
        fixed_point_words.arrange(DOWN)

        self.play(Write(x), Write(equals))
        self.wait()
        self.play(ShowCreation(inner_frac_rect))
        self.wait()
        self.play(
            inner_frac_group.scale, 0.75,
            inner_frac_group.center,
            inner_frac_group.to_edge, LEFT,
            ReplacementTransform(
                x.copy(), new_x,
                path_arc=-90 * DEGREES
            )
        )
        self.wait()
        self.play(
            frac[3].stretch, 0.1, 0, {"about_edge": RIGHT},
            MaintainPositionRelativeTo(
                VGroup(frac[2], new_x), frac[3]
            ),
            UpdateFromFunc(
                frac[:2], lambda m: m.next_to(frac[3], LEFT)
            )
        )
        self.wait()
        fixed_point_words.next_to(VGroup(frac[0], xs), DOWN, LARGE_BUFF)
        self.play(
            Write(fixed_point_words),
            morty.change, "hooray",
            friend.change, "happy"
        )
        self.wait(3)


class GraphOnePlusOneOverX(GraphScene):
    CONFIG = {
        "x_min": -6,
        "x_max": 6,
        "x_axis_width": 12,
        "y_min": -4,
        "y_max": 5,
        "y_axis_height": 8,
        "y_axis_label": None,
        "graph_origin": 0.5 * DOWN,
        "num_graph_anchor_points": 100,
        "func_graph_color": GREEN,
        "identity_graph_color": BLUE,
    }

    def construct(self):
        self.add_title()
        self.setup_axes()
        self.draw_graphs()
        self.show_solutions()

    def add_title(self):
        title = self.title = Tex(
            "\\text{Solve: }", "1 + \\frac{1}{x}", "=", "x",
        )
        title.set_color_by_tex("x", self.identity_graph_color, substring=False)
        title.set_color_by_tex("frac", self.func_graph_color)
        title.to_corner(UL)
        self.add(title)

    def setup_axes(self):
        GraphScene.setup_axes(self)
        step = 2
        self.x_axis.add_numbers(*list(range(-6, 0, step)) + list(range(step, 7, step)))
        self.y_axis.label_direction = RIGHT
        self.y_axis.add_numbers(*list(range(-2, 0, step)) + list(range(step, 4, step)))

    def draw_graphs(self, animate=True):
        lower_func_graph, upper_func_graph = func_graph = VGroup(*[
            self.get_graph(
                lambda x: 1.0 + 1.0 / x,
                x_min=x_min,
                x_max=x_max,
                color=self.func_graph_color,
            )
            for x_min, x_max in [(-10, -0.1), (0.1, 10)]
        ])
        func_graph.label = self.get_graph_label(
            upper_func_graph, "y = 1 + \\frac{1}{x}",
            x_val=6, direction=UP,
        )

        identity_graph = self.get_graph(
            lambda x: x, color=self.identity_graph_color
        )
        identity_graph.label = self.get_graph_label(
            identity_graph, "y = x",
            x_val=3, direction=UL, buff=SMALL_BUFF
        )

        if animate:
            for graph in func_graph, identity_graph:
                self.play(
                    ShowCreation(graph),
                    Write(graph.label),
                    run_time=2
                )
            self.wait()
        else:
            self.add(
                func_graph, func_graph.label,
                identity_graph, identity_graph.label,
            )

        self.func_graph = func_graph
        self.identity_graph = identity_graph

    def show_solutions(self):
        phi = 0.5 * (1 + np.sqrt(5))
        phi_bro = 0.5 * (1 - np.sqrt(5))

        lines = VGroup()
        for num in phi, phi_bro:
            line = DashedLine(
                self.coords_to_point(num, 0),
                self.coords_to_point(num, num),
                color=WHITE
            )
            line_copy = line.copy()
            line_copy.set_color(YELLOW)
            line.fade(0.5)
            line_anim = ShowCreationThenDestruction(
                line_copy,
                lag_ratio=0.5,
                run_time=2
            )
            cycle_animation(line_anim)
            lines.add(line)

        phi_line, phi_bro_line = lines

        decimal_kwargs = {
            "num_decimal_places": 3,
            "show_ellipsis": True,
            "color": YELLOW,
        }
        arrow_kwargs = {
            "buff": SMALL_BUFF,
            "color": WHITE,
            "tip_length": 0.15,
            "rectangular_stem_width": 0.025,
        }

        phi_decimal = DecimalNumber(phi, **decimal_kwargs)
        phi_decimal.next_to(phi_line, DOWN, LARGE_BUFF)
        phi_arrow = Arrow(
            phi_decimal[:4].get_top(), phi_line.get_bottom(),
            **arrow_kwargs
        )
        phi_label = Tex("=", "\\varphi")
        phi_label.next_to(phi_decimal, RIGHT)
        phi_label.set_color_by_tex("\\varphi", YELLOW)

        phi_bro_decimal = DecimalNumber(phi_bro, **decimal_kwargs)
        phi_bro_decimal.next_to(phi_bro_line, UP, LARGE_BUFF)
        phi_bro_decimal.shift(0.5 * LEFT)
        phi_bro_arrow = Arrow(
            phi_bro_decimal[:6].get_bottom(), phi_bro_line.get_top(),
            **arrow_kwargs
        )

        brother_words = TexText(
            "$\\varphi$'s little brother",
            tex_to_color_map={"$\\varphi$": YELLOW},
            arg_separator=""
        )
        brother_words.next_to(
            phi_bro_decimal[-2], UP, buff=MED_SMALL_BUFF,
            aligned_edge=RIGHT
        )

        self.add(phi_line.continual_anim)
        self.play(ShowCreation(phi_line))
        self.play(
            Write(phi_decimal),
            GrowArrow(phi_arrow),
        )
        self.play(Write(phi_label))
        self.wait(3)
        self.add(phi_bro_line.continual_anim)
        self.play(ShowCreation(phi_bro_line))
        self.play(
            Write(phi_bro_decimal),
            GrowArrow(phi_bro_arrow),
        )
        self.wait(4)
        self.play(Write(brother_words))
        self.wait(8)


class ThinkAboutWithRepeatedApplication(IntroduceContinuedFractionPuzzle):
    CONFIG = {
        "remove_initial_rhs": False,
    }

    def construct(self):
        self.force_skipping()
        self.ask_question()
        self.revert_to_original_skipping_status()

        self.obviously_not()
        self.ask_about_fraction()
        self.plug_func_into_self()

    def obviously_not(self):
        morty, friend = self.get_pi_creatures()
        friend.change_mode("confused")
        randy = Randolph()
        randy.match_height(morty)
        randy.to_corner(DL)

        frac = self.frac
        rhs = self.neg_one_over_phi
        plusses = frac[1::4]
        plus_rects = VGroup(*[
            SurroundingRectangle(plus, buff=0) for plus in plusses
        ])
        plus_rects.set_color(PINK)

        self.play(FadeIn(randy))
        self.play(
            PiCreatureSays(
                randy, "Obviously not!",
                bubble_kwargs={"width": 3, "height": 2},
                target_mode="angry",
                run_time=1,
            ),
            morty.change, "guilty",
            friend.change, "hesitant"
        )
        self.wait()
        self.play(
            Animation(frac),
            RemovePiCreatureBubble(randy, target_mode="sassy"),
            morty.change, "confused",
            friend.change, "confused",
        )
        self.play(LaggedStartMap(
            ShowCreationThenDestruction, plus_rects,
            run_time=2,
            lag_ratio=0.35,
        ))
        self.play(WiggleOutThenIn(rhs))
        self.wait(2)
        self.play(
            frac.scale, 0.7,
            frac.to_corner, UL,
            FadeOut(self.equals),
            rhs.scale, 0.5,
            rhs.center,
            rhs.to_edge, LEFT,
            FadeOut(randy),
            morty.change, "pondering",
            friend.change, "pondering",
        )

    def ask_about_fraction(self):
        frac = self.frac
        arrow = Vector(LEFT, color=RED)
        arrow.next_to(frac, RIGHT)
        question = TexText("What does this \\\\ actually mean?")
        question.set_color(RED)
        question.next_to(arrow, RIGHT)

        self.play(
            LaggedStartMap(FadeIn, question, run_time=1),
            GrowArrow(arrow),
            LaggedStartMap(
                ApplyMethod, frac,
                lambda m: (m.set_color, RED),
                rate_func=there_and_back,
                lag_ratio=0.2,
                run_time=2
            )
        )
        self.wait()
        self.play(FadeOut(question), FadeOut(arrow))

    def plug_func_into_self(self, value=1, value_str="1"):
        morty, friend = self.pi_creatures

        def func(x):
            return 1 + 1.0 / x

        lines = VGroup()
        value_labels = VGroup()
        for n_terms in range(5):
            lhs = get_nested_f(n_terms, arg="c")
            equals = Tex("=")
            rhs = get_nested_one_plus_one_over_x(n_terms, bottom_term=value_str)
            equals.next_to(rhs[0], LEFT)
            lhs.next_to(equals, LEFT)
            lines.add(VGroup(lhs, equals, rhs))

            value_label = Tex("= %.3f\\dots" % value)
            value = func(value)
            value_labels.add(value_label)

        lines.arrange(
            DOWN, buff=MED_LARGE_BUFF,
        )
        VGroup(lines, value_labels).scale(0.8)
        lines.to_corner(UR)
        buff = MED_LARGE_BUFF + MED_SMALL_BUFF + value_labels.get_width()
        lines.to_edge(RIGHT, buff=buff)
        for line, value_label in zip(lines, value_labels):
            value_label.move_to(line[1]).to_edge(RIGHT)

        top_line = lines[0]
        colors = [WHITE] + color_gradient([YELLOW, RED, PINK], len(lines) - 1)
        for n in range(1, len(lines)):
            color = colors[n]
            lines[n][0].set_color(color)
            lines[n][0][1:-1].match_style(lines[n - 1][0])
            lines[n][2].set_color(color)
            lines[n][2][4:].match_style(lines[n - 1][2])

        arrow = Vector(0.5 * DOWN, color=WHITE)
        arrow.next_to(value_labels[-1], DOWN)
        q_marks = Tex("???")
        q_marks.next_to(arrow, DOWN)

        self.play(
            FadeInFromDown(top_line),
            FadeInFromDown(value_labels[0])
        )
        for n in range(1, len(lines)):
            new_line = lines[n]
            last_line = lines[n - 1]
            value_label = value_labels[n]
            mover, target = [
                VGroup(
                    line[0][0],
                    line[0][-1],
                    line[1],
                    line[2][:4],
                )
                for line in (lines[1], new_line)
            ]
            anims = [ReplacementTransform(
                mover.copy().fade(1), target, path_arc=30 * DEGREES
            )]
            if n == 4:
                morty.generate_target()
                morty.target.change("horrified")
                morty.target.shift(2.5 * DOWN)
                anims.append(MoveToTarget(morty, remover=True))
            self.play(*anims)
            self.wait()
            self.play(
                ReplacementTransform(
                    last_line[0].copy(), new_line[0][1:-1]
                ),
                ReplacementTransform(
                    last_line[2].copy(), new_line[2][4:]
                ),
            )
            self.play(FadeIn(value_label))
            self.wait()
        self.play(
            GrowArrow(arrow),
            Write(q_marks),
            friend.change, "confused"
        )
        self.wait(3)

        self.top_line = VGroup(lines[0], value_labels[0])


class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication):
    CONFIG = {
        "value": (1 - np.sqrt(5)) / 2,
        "value_str": "-1/\\varphi",
    }

    def construct(self):
        self.force_skipping()
        self.ask_question()
        self.obviously_not()
        self.revert_to_original_skipping_status()

        self.plug_func_into_self(
            value=self.value,
            value_str=self.value_str
        )


class RepeatedApplicationWithNegativeSeed(RepeatedApplicationWithPhiBro, MovingCameraScene):
    CONFIG = {
        "value": -0.65,
        "value_str": "-0.65"
    }

    def setup(self):
        MovingCameraScene.setup(self)
        RepeatedApplicationWithPhiBro.setup(self)

    def construct(self):
        RepeatedApplicationWithPhiBro.construct(self)

        rect = SurroundingRectangle(self.top_line)
        question = TexText("What about a negative seed?")
        question.match_color(rect)
        question.next_to(rect, UP)
        self.play(ShowCreation(rect))
        self.play(
            Write(question),
            self.camera.frame.set_height, FRAME_HEIGHT + 1.5
        )
        self.wait()
        self.play(
            FadeOut(question),
            FadeOut(rect),
            self.camera.frame.set_height, FRAME_HEIGHT
        )


class ShowRepeatedApplication(Scene):
    CONFIG = {
        "title_color": YELLOW,
    }

    def construct(self):
        self.add_func_title()
        self.show_repeated_iteration()

    def add_func_title(self):
        title = self.title = VGroup(
            Tex("f(", "x", ")"),
            Tex("="),
            get_nested_one_plus_one_over_x(1)
        )
        title.arrange(RIGHT)
        title.to_corner(UL)
        title.set_color(self.title_color)

        self.add(title)

    def show_repeated_iteration(self):
        line = VGroup()
        decimal_kwargs = {
            "num_decimal_places": 3,
            "show_ellipsis": True,
        }
        phi = (1 + np.sqrt(5)) / 2

        def func(x):
            return 1.0 + 1.0 / x

        initial_term = DecimalNumber(2.71828, **decimal_kwargs)
        line.add(initial_term)
        last_term = initial_term

        def get_arrow():
            arrow = Tex("\\rightarrow")
            arrow.stretch(1.5, 0)
            arrow.next_to(line[-1], RIGHT)
            tex = Tex("f(x)")
            tex.set_color(YELLOW)
            tex.match_width(arrow)
            tex.next_to(arrow, UP, SMALL_BUFF)
            return VGroup(arrow, tex)

        for x in range(2):
            arrow = get_arrow()
            line.add(arrow)

            new_term = DecimalNumber(
                func(last_term.number),
                **decimal_kwargs
            )
            new_term.next_to(arrow[0], RIGHT)
            last_term = new_term
            line.add(new_term)

        line.add(get_arrow())
        line.add(Tex("\\dots\\dots").next_to(line[-1][0], RIGHT))
        num_phi_mob = DecimalNumber(phi, **decimal_kwargs)
        line.add(num_phi_mob.next_to(line[-1], RIGHT))
        line.move_to(DOWN)

        rects = VGroup(*[
            SurroundingRectangle(mob)
            for mob in (line[0], line[1:-1], line[-1])
        ])
        rects.set_stroke(BLUE, 2)

        braces = VGroup(*[
            Brace(rect, DOWN, buff=SMALL_BUFF)
            for rect in rects
        ])
        braces.set_color_by_gradient(GREEN, YELLOW)
        brace_texts = VGroup(*[
            brace.get_text(text).scale(0.75, about_edge=UP)
            for brace, text in zip(braces, [
                "Arbitrary \\\\ starting \\\\ value",
                "Repeatedly apply $f(x)$",
                "$\\varphi$ \\\\ ``Golden ratio''"
            ])
        ])
        var_phi_mob = brace_texts[2][0]
        var_phi_mob.scale(2, about_edge=UP).set_color(YELLOW)
        brace_texts[2][1:].next_to(var_phi_mob, DOWN, MED_SMALL_BUFF)

        # Animations
        self.add(line[0])
        self.play(
            GrowFromCenter(braces[0]),
            Write(brace_texts[0])
        )
        self.wait()
        self.play(
            GrowFromEdge(line[1], LEFT),
            FadeIn(braces[1]),
            FadeIn(brace_texts[1]),
        )
        self.play(ReplacementTransform(line[0].copy(), line[2]))
        self.wait()

        self.play(GrowFromEdge(line[3], LEFT))
        self.play(ReplacementTransform(line[2].copy(), line[4]))
        self.wait()

        self.play(GrowFromEdge(line[5], LEFT))
        self.play(LaggedStartMap(GrowFromCenter, line[6]))
        self.wait()

        self.play(FadeIn(line[7]))
        self.play(
            GrowFromCenter(braces[2]),
            FadeIn(brace_texts[2]),
        )
        self.wait()


class NumericalPlayFromOne(ExternallyAnimatedScene):
    pass


class NumericalPlayFromTau(ExternallyAnimatedScene):
    pass


class NumericalPlayFromNegPhi(ExternallyAnimatedScene):
    pass


class NumericalPlayOnNumberLineFromOne(Scene):
    CONFIG = {
        "starting_value": 1,
        "n_jumps": 10,
        "func": lambda x: 1 + 1.0 / x,
        "number_line_config": {
            "x_min": 0,
            "x_max": 2,
            "unit_size": 6,
            "tick_frequency": 0.25,
            "numbers_with_elongated_ticks": [0, 1, 2]
        }
    }

    def construct(self):
        self.add_number_line()
        self.add_phi_label()
        self.add_title()
        self.bounce_around()

    def add_number_line(self):
        number_line = NumberLine(**self.number_line_config)
        number_line.move_to(2 * DOWN)
        number_line.add_numbers()

        self.add(number_line)
        self.number_line = number_line

    def add_phi_label(self):
        number_line = self.number_line
        phi_point = number_line.number_to_point(
            (1 + np.sqrt(5)) / 2
        )
        phi_dot = Dot(phi_point, color=YELLOW)
        arrow = Vector(DL)
        arrow.next_to(phi_point, UR, SMALL_BUFF)
        phi_label = Tex("\\varphi = 1.618\\dots")
        phi_label.set_color(YELLOW)
        phi_label.next_to(arrow.get_start(), UP, SMALL_BUFF)

        self.add(phi_dot, phi_label, arrow)

    def add_title(self):
        title = Tex("x \\rightarrow 1 + \\frac{1}{x}")
        title.to_corner(UL)
        self.add(title)

    def bounce_around(self):
        number_line = self.number_line
        value = self.starting_value
        point = number_line.number_to_point(value)
        dot = Dot(point)
        dot.set_fill(RED, opacity=0.8)
        arrow = Vector(DR)
        arrow.next_to(point, UL, buff=SMALL_BUFF)
        arrow.match_color(dot)
        start_here = TexText("Start here")
        start_here.next_to(arrow.get_start(), UP, SMALL_BUFF)
        start_here.match_color(dot)

        self.play(
            FadeIn(start_here),
            GrowArrow(arrow),
            GrowFromPoint(dot, arrow.get_start())
        )
        self.play(
            FadeOut(start_here),
            FadeOut(arrow)
        )
        self.wait()
        for x in range(self.n_jumps):
            new_value = self.func(value)
            new_point = number_line.number_to_point(new_value)
            if new_value - value > 0:
                path_arc = -120 * DEGREES
            else:
                path_arc = -120 * DEGREES
            arc = Line(
                point, new_point,
                path_arc=path_arc,
                buff=SMALL_BUFF
            )
            self.play(
                ShowCreationThenDestruction(arc, run_time=1.5),
                ApplyMethod(
                    dot.move_to, new_point,
                    path_arc=path_arc
                ),
            )
            self.wait(0.5)

            value = new_value
            point = new_point


class NumericalPlayOnNumberLineFromTau(NumericalPlayOnNumberLineFromOne):
    CONFIG = {
        "starting_value": TAU,
        "number_line_config": {
            "x_min": 0,
            "x_max": 7,
            "unit_size": 2,
        }
    }


class NumericalPlayOnNumberLineFromMinusPhi(NumericalPlayOnNumberLineFromOne):
    CONFIG = {
        "starting_value": -0.61803,
        "number_line_config": {
            "x_min": -3,
            "x_max": 3,
            "unit_size": 2,
            "tick_frequency": 0.25,
        },
        "n_jumps": 25,
    }

    def add_phi_label(self):
        NumericalPlayOnNumberLineFromOne.add_phi_label(self)
        number_line = self.number_line
        new_point = number_line.number_to_point(
            (1 - np.sqrt(5)) / 2
        )
        arrow = Vector(DR)
        arrow.next_to(new_point, UL, SMALL_BUFF)
        arrow.set_color(RED)
        new_label = Tex("-\\frac{1}{\\varphi} = -0.618\\dots")
        new_label.set_color(RED)
        new_label.next_to(arrow.get_start(), UP, SMALL_BUFF)
        new_label.shift(RIGHT)
        self.add(new_label, arrow)


class RepeatedApplicationGraphically(GraphOnePlusOneOverX, PiCreatureScene):
    CONFIG = {
        "starting_value": 1,
        "n_jumps": 5,
        "n_times_to_show_identity_property": 2,
    }

    def setup(self):
        GraphOnePlusOneOverX.setup(self)
        PiCreatureScene.setup(self)

    def construct(self):
        self.setup_axes()
        self.draw_graphs(animate=False)
        self.draw_spider_web()

    def create_pi_creature(self):
        randy = Randolph(height=2)
        randy.flip()
        randy.to_corner(DR)
        return randy

    def get_new_randy_mode(self):
        randy = self.pi_creature
        if not hasattr(self, "n_mode_changes"):
            self.n_mode_changes = 0
        else:
            self.n_mode_changes += 1
        if self.n_mode_changes % 3 != 0:
            return randy.get_mode()
        return random.choice([
            "confused",
            "erm",
            "maybe"
        ])

    def draw_spider_web(self):
        randy = self.pi_creature
        func = self.func_graph[0].underlying_function
        x_val = self.starting_value
        curr_output = 0
        dot = Dot(color=RED, fill_opacity=0.7)
        dot.move_to(self.coords_to_point(x_val, curr_output))

        self.play(FadeIn(dot, 2 * UR))
        self.wait()

        for n in range(self.n_jumps):
            new_output = func(x_val)
            func_graph_point = self.coords_to_point(x_val, new_output)
            id_graph_point = self.coords_to_point(new_output, new_output)
            v_line = DashedLine(dot.get_center(), func_graph_point)
            h_line = DashedLine(func_graph_point, id_graph_point)

            curr_output = new_output
            x_val = new_output

            for line in v_line, h_line:
                line_end = line.get_end()
                self.play(
                    ShowCreation(line),
                    dot.move_to, line_end,
                    randy.change, self.get_new_randy_mode()
                )
                self.wait()

            if n < self.n_times_to_show_identity_property:
                x_point = self.coords_to_point(new_output, 0)
                y_point = self.coords_to_point(0, new_output)
                lines = VGroup(*[
                    Line(dot.get_center(), point)
                    for point in (x_point, y_point)
                ])
                lines.set_color(YELLOW)
                self.play(ShowCreationThenDestruction(
                    lines, run_time=2
                ))
                self.wait(0.25)


class RepeatedApplicationGraphicallyFromNegPhi(RepeatedApplicationGraphically):
    CONFIG = {
        "starting_value": -0.61,
        "n_jumps": 13,
        "n_times_to_show_identity_property": 0,
    }


class LetsSwitchToTheTransformationalView(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Lose the \\\\ graphs!",
            target_mode="hooray"
        )
        self.change_student_modes("hooray", "erm", "surprised")
        self.wait(5)


class AnalyzeFunctionWithTransformations(NumberlineTransformationScene):
    CONFIG = {
        "input_line_zero_point": 0.5 * UP,
        "output_line_zero_point": 2 * DOWN,
        "func": lambda x: 1 + 1.0 / x,
        "num_initial_applications": 10,
        "num_repeated_local_applications": 7,
        "zoomed_display_width": 3.5,
        "zoomed_display_height": 2,
        "default_mapping_animation_config": {},
    }

    def construct(self):
        self.add_function_title()
        self.repeatedly_apply_function()
        self.show_phi_and_phi_bro()
        self.zoom_in_on_phi()
        self.zoom_in_on_phi_bro()

    def setup_number_lines(self):
        NumberlineTransformationScene.setup_number_lines(self)
        for line in self.input_line, self.output_line:
            VGroup(line, line.tick_marks).set_stroke(width=2)

    def add_function_title(self):
        title = Tex("f(x)", "=", "1 +", "\\frac{1}{x}")
        title.to_edge(UP)
        self.add(title)
        self.title = title

    def repeatedly_apply_function(self):
        input_zero_point = self.input_line.number_to_point(0)
        output_zero_point = self.output_line.number_to_point(0)
        sample_dots = self.get_sample_dots(
            delta_x=0.05,
            dot_radius=0.05,
            x_min=-10,
            x_max=10,
        )
        sample_dots.set_stroke(BLACK, 0.5)
        sample_points = list(map(Mobject.get_center, sample_dots))

        self.play(LaggedStartMap(
            FadeInFrom, sample_dots,
            lambda m: (m, UP)
        ))
        self.show_arrows(sample_points)
        self.wait()
        for x in range(self.num_initial_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                sample_dots=sample_dots
            )
            self.wait()

            shift_vect = input_zero_point - output_zero_point
            shift_vect[0] = 0
            lower_output_line = self.output_line.copy()
            upper_output_line = self.output_line.copy()
            lower_output_line.shift(-shift_vect)
            lower_output_line.fade(1)

            self.remove(self.output_line)
            self.play(
                ReplacementTransform(lower_output_line, self.output_line),
                upper_output_line.shift, shift_vect,
                upper_output_line.fade, 1,
                sample_dots.shift, shift_vect,
            )
            self.remove(upper_output_line)
            self.wait()
        self.play(FadeOut(sample_dots))

    def show_arrows(self, sample_points):
        input_zero_point = self.input_line.number_to_point(0)
        point_func = self.number_func_to_point_func(self.func)
        alt_point_func = self.number_func_to_point_func(lambda x: 1.0 / x)
        arrows, alt_arrows = [
            VGroup(*[
                Arrow(
                    point, func(point), buff=SMALL_BUFF,
                    tip_length=0.15
                )
                for point in sample_points
                if get_norm(point - input_zero_point) > 0.3
            ])
            for func in (point_func, alt_point_func)
        ]
        for group in arrows, alt_arrows:
            group.set_stroke(WHITE, 0.5)
            group.set_color_by_gradient(RED, YELLOW)
            for arrow in group:
                arrow.tip.set_stroke(BLACK, 0.5)

        one_plus = self.title.get_part_by_tex("1 +")
        one_plus_rect = BackgroundRectangle(one_plus)
        one_plus_rect.set_fill(BLACK, opacity=0.8)

        self.play(LaggedStartMap(GrowArrow, arrows))
        self.wait()
        self.play(LaggedStartMap(
            ApplyMethod, arrows,
            lambda a: (a.scale, 0.7),
            rate_func=there_and_back,
        ))
        self.wait()
        self.play(
            Transform(arrows, alt_arrows),
            FadeIn(one_plus_rect, remover=True),
            rate_func=there_and_back_with_pause,
            run_time=4,
        )
        self.wait()

        self.all_arrows = arrows

    def show_phi_and_phi_bro(self):
        phi = (1 + np.sqrt(5)) / 2
        phi_bro = (1 - np.sqrt(5)) / 2

        input_phi_point = self.input_line.number_to_point(phi)
        output_phi_point = self.output_line.number_to_point(phi)
        input_phi_bro_point = self.input_line.number_to_point(phi_bro)
        output_phi_bro_point = self.output_line.number_to_point(phi_bro)

        tick = Line(UP, DOWN)
        tick.set_stroke(YELLOW, 3)
        tick.match_height(self.input_line.tick_marks)
        phi_tick = tick.copy().move_to(input_phi_point, DOWN)
        phi_bro_tick = tick.copy().move_to(input_phi_bro_point, DOWN)
        VGroup(phi_tick, phi_bro_tick).shift(SMALL_BUFF * DOWN)

        phi_label = Tex("1.618\\dots")
        phi_label.next_to(phi_tick, UP)
        phi_bro_label = Tex("-0.618\\dots")
        phi_bro_label.next_to(phi_bro_tick, UP)
        VGroup(phi_label, phi_bro_label).set_color(YELLOW)

        arrow_kwargs = {
            "buff": SMALL_BUFF,
            "rectangular_stem_width": 0.035,
            "tip_length": 0.2,
        }
        phi_arrow = Arrow(phi_tick, output_phi_point, **arrow_kwargs)
        phi_bro_arrow = Arrow(phi_bro_tick, output_phi_bro_point, **arrow_kwargs)

        def fade_arrow(arrow):
            # arrow.set_stroke(GREY_D, 0.5)
            arrow.set_stroke(width=0.1)
            arrow.tip.set_fill(opacity=0)
            arrow.tip.set_stroke(width=0)
            return arrow

        self.play(
            LaggedStartMap(
                ApplyFunction, self.all_arrows,
                lambda a: (fade_arrow, a)
            ),
            FadeIn(phi_arrow),
            FadeIn(phi_bro_arrow),
        )
        self.play(
            Write(phi_label),
            GrowFromCenter(phi_tick)
        )
        self.play(
            Write(phi_bro_label),
            GrowFromCenter(phi_bro_tick)
        )

        self.set_variables_as_attrs(
            input_phi_point, output_phi_point,
            input_phi_bro_point, output_phi_bro_point,
            phi_label, phi_tick,
            phi_bro_label, phi_bro_tick,
            phi_arrow, phi_bro_arrow
        )

    def zoom_in_on_phi(self):
        phi = (1 + np.sqrt(5)) / 2
        # phi_point = self.get_input_point(phi)
        local_sample_dots = self.get_local_sample_dots(
            phi, dot_radius=0.005, sample_radius=1
        )
        local_coordinate_values = [1.55, 1.6, 1.65, 1.7]

        # zcbr = self.zoomed_camera_background_rectangle
        zcbr_group = self.zoomed_camera_background_rectangle_group
        zcbr_group.add(self.phi_tick)

        title = self.title
        deriv_text = Tex(
            "|", "\\frac{df}{dx}(\\varphi)", "|", "< 1",
            tex_to_color_map={"\\varphi": YELLOW}
        )
        deriv_text.get_parts_by_tex("|").match_height(
            deriv_text, stretch=True
        )
        deriv_text.move_to(title, UP)
        approx_value = Tex("\\approx |%.2f|" % (-1 / phi**2))
        approx_value.move_to(deriv_text)
        deriv_text_lhs = deriv_text[:-1]
        deriv_text_rhs = deriv_text[-1]

        self.zoom_in_on_input(
            phi,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values
        )
        self.wait()
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coordinate_values,
        )
        self.wait()
        self.play(
            FadeInFromDown(deriv_text_lhs),
            FadeInFromDown(deriv_text_rhs),
            title.to_corner, UL
        )
        self.wait()
        self.play(
            deriv_text_lhs.next_to, approx_value, LEFT,
            deriv_text_rhs.next_to, approx_value, RIGHT,
            FadeIn(approx_value)
        )
        self.wait()
        for n in range(self.num_repeated_local_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                local_sample_dots=local_sample_dots,
                path_arc=60 * DEGREES,
                run_time=2
            )

        self.deriv_text = VGroup(
            deriv_text_lhs, deriv_text_rhs, approx_value
        )

    def zoom_in_on_phi_bro(self):
        zcbr = self.zoomed_camera_background_rectangle
        # zcbr_group = self.zoomed_camera_background_rectangle_group
        zoomed_frame = self.zoomed_camera.frame

        phi_bro = (1 - np.sqrt(5)) / 2
        # phi_bro_point = self.get_input_point(phi_bro)
        local_sample_dots = self.get_local_sample_dots(phi_bro)
        local_coordinate_values = [-0.65, -0.6, -0.55]

        deriv_text = Tex(
            "\\left| \\frac{df}{dx}\\left(\\frac{-1}{\\varphi}\\right) \\right|",
            "\\approx |%.2f|" % (-1 / (phi_bro**2)),
            "> 1"
        )
        deriv_text.move_to(self.deriv_text, UL)
        deriv_text[0][10:14].set_color(YELLOW)

        self.play(
            zoomed_frame.set_height, 4,
            zoomed_frame.center,
            self.deriv_text.fade, 1,
            run_time=2
        )
        self.wait()
        zcbr.set_fill(opacity=0)
        self.zoom_in_on_input(
            phi_bro,
            local_sample_dots=local_sample_dots,
            local_coordinate_values=local_coordinate_values,
            zoom_factor=self.zoom_factor,
            first_anim_kwargs={"run_time": 2},
        )
        self.wait()
        self.play(FadeInFromDown(deriv_text))
        self.wait()
        zcbr.set_fill(opacity=1)
        self.apply_function(
            self.func,
            apply_function_to_number_line=False,
            local_sample_dots=local_sample_dots,
            target_coordinate_values=local_coordinate_values,
        )
        self.wait()
        for n in range(self.num_repeated_local_applications):
            self.apply_function(
                self.func,
                apply_function_to_number_line=False,
                local_sample_dots=local_sample_dots,
                path_arc=20 * DEGREES,
                run_time=2,
            )


class StabilityAndInstability(AnalyzeFunctionWithTransformations):
    CONFIG = {
        "num_initial_applications": 0,
    }

    def construct(self):
        self.force_skipping()
        self.add_function_title()
        self.repeatedly_apply_function()
        self.show_phi_and_phi_bro()
        self.revert_to_original_skipping_status()

        self.label_stability()
        self.write_derivative_fact()

    def label_stability(self):
        self.title.to_corner(UL)

        stable_label = TexText("Stable fixed point")
        unstable_label = TexText("Unstable fixed point")
        labels = VGroup(stable_label, unstable_label)
        labels.scale(0.8)
        stable_label.next_to(self.phi_label, UP, aligned_edge=ORIGIN)
        unstable_label.next_to(self.phi_bro_label, UP, aligned_edge=ORIGIN)

        phi_point = self.input_phi_point
        phi_bro_point = self.input_phi_bro_point

        arrow_groups = VGroup()
        for point in phi_point, phi_bro_point:
            arrows = VGroup(*[a for a in self.all_arrows if get_norm(a.get_start() - point) < 0.75]).copy()
            arrows.set_fill(PINK, 1)
            arrows.set_stroke(PINK, 3)
            arrows.second_anim = LaggedStartMap(
                ApplyMethod, arrows,
                lambda m: (m.set_color, YELLOW),
                rate_func=there_and_back_with_pause,
                lag_ratio=0.7,
                run_time=2,
            )
            arrows.anim = AnimationGroup(*list(map(GrowArrow, arrows)))
            arrow_groups.add(arrows)
        phi_arrows, phi_bro_arrows = arrow_groups

        self.add_foreground_mobjects(self.phi_arrow, self.phi_bro_arrow)
        self.play(
            Write(stable_label),
            phi_arrows.anim,
        )
        self.play(phi_arrows.second_anim)
        self.play(
            Write(unstable_label),
            phi_bro_arrows.anim,
        )
        self.play(phi_bro_arrows.second_anim)
        self.wait()

        self.stable_label = stable_label
        self.unstable_label = unstable_label
        self.phi_arrows = phi_arrows
        self.phi_bro_arrows = phi_bro_arrows

    def write_derivative_fact(self):
        stable_label = self.stable_label
        unstable_label = self.unstable_label
        labels = VGroup(stable_label, unstable_label)
        phi_arrows = self.phi_arrows
        phi_bro_arrows = self.phi_bro_arrows
        arrow_groups = VGroup(phi_arrows, phi_bro_arrows)

        deriv_labels = VGroup()
        for char, label in zip("<>", labels):
            deriv_label = Tex(
                "\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|",
                char, "1"
            )
            deriv_label.get_parts_by_tex("\\big|").match_height(
                deriv_label, stretch=True
            )
            deriv_label.set_color_by_tex("x", YELLOW, substring=False)
            deriv_label.next_to(label, UP)
            deriv_labels.add(deriv_label)

        dot_groups = VGroup()
        for arrow_group in arrow_groups:
            dot_group = VGroup()
            for arrow in arrow_group:
                start_point, end_point = [
                    line.number_to_point(line.point_to_number(p))
                    for line, p in [
                        (self.input_line, arrow.get_start()),
                        (self.output_line, arrow.get_end()),
                    ]
                ]
                dot = Dot(start_point, radius=0.05)
                dot.set_color(YELLOW)
                dot.generate_target()
                dot.target.move_to(end_point)
                dot_group.add(dot)
            dot_groups.add(dot_group)

        for deriv_label, dot_group in zip(deriv_labels, dot_groups):
            self.play(FadeInFromDown(deriv_label))
            self.play(LaggedStartMap(GrowFromCenter, dot_group))
            self.play(*list(map(MoveToTarget, dot_group)), run_time=2)
            self.wait()


class StaticAlgebraicObject(Scene):
    def construct(self):
        frac = get_phi_continued_fraction(40)
        frac.set_width(FRAME_WIDTH - 1)
        # frac.shift(2 * DOWN)
        frac.to_edge(DOWN)
        frac.set_stroke(WHITE, width=0.5)

        title = Tex(
            "\\infty \\ne \\lim",
            tex_to_color_map={"\\ne": RED}
        )
        title.scale(1.5)
        title.to_edge(UP)

        polynomial = Tex("x^2 - x - 1 = 0")
        polynomial.move_to(title)

        self.add(title)
        self.play(LaggedStartMap(
            GrowFromCenter, frac,
            lag_ratio=0.1,
            run_time=3
        ))
        self.wait()
        factor = 1.1
        self.play(frac.scale, factor, run_time=0.5)
        self.play(
            frac.scale, 1 / factor,
            frac.set_color, GREY_B,
            run_time=0.5, rate_func=lambda t: t**5,
        )
        self.wait()
        self.play(
            FadeOut(title),
            FadeIn(polynomial)
        )
        self.wait(2)


class NotBetterThanGraphs(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "Um, yeah, I'll stick \\\\ with graphs thanks",
            target_mode="sassy",
        )
        self.play(
            self.teacher.change, "guilty",
            self.get_student_changes("sad", "sassy", "hesitant")
        )
        self.wait(2)
        self.play(
            RemovePiCreatureBubble(self.students[1]),
            self.teacher.change, "raise_right_hand"
        )
        self.change_all_student_modes(
            "confused", look_at_arg=self.screen
        )
        self.wait(3)
        self.teacher_says(
            "You must flex those \\\\ conceptual muscles",
            added_anims=[self.get_student_changes(
                *3 * ["thinking"],
                look_at_arg=self.teacher.eyes
            )]
        )
        self.wait(3)


class WhatComesAfterWrapper(Wrapper):
    CONFIG = {"title": "Beyond the first year"}

    def construct(self):
        Wrapper.construct(self)
        new_title = TexText("Next video")
        new_title.set_color(BLUE)
        new_title.move_to(self.title)

        self.play(
            FadeInFromDown(new_title),
            self.title.shift, UP,
            self.title.fade, 1,
        )
        self.wait(3)


class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics):
    CONFIG = {
        "pi_creatures_start_on_screen": False,
    }

    def construct(self):
        MoreTopics.construct(self)
        self.show_horror()
        self.zero_in_on_complex_analysis()

    def create_pi_creatures(self):
        creatures = VGroup(*[
            PiCreature(color=color)
            for color in [BLUE_E, BLUE_C, BLUE_D]
        ])
        creatures.arrange(RIGHT, buff=LARGE_BUFF)
        creatures.scale(0.5)
        creatures.to_corner(DR)
        return creatures

    def show_horror(self):
        creatures = self.get_pi_creatures()
        modes = ["horrified", "tired", "horrified"]
        for creature, mode in zip(creatures, modes):
            creature.generate_target()
            creature.target.change(mode, self.other_topics)
        creatures.fade(1)

        self.play(LaggedStartMap(MoveToTarget, creatures))
        self.wait(2)

    def zero_in_on_complex_analysis(self):
        creatures = self.get_pi_creatures()
        complex_analysis = self.other_topics[1]
        self.other_topics.remove(complex_analysis)

        self.play(
            complex_analysis.scale, 1.25,
            complex_analysis.center,
            complex_analysis.to_edge, UP,
            LaggedStartMap(FadeOut, self.other_topics),
            LaggedStartMap(FadeOut, self.lines),
            FadeOut(self.calculus),
            *[
                ApplyMethod(creature.change, "pondering")
                for creature in creatures
            ]
        )
        self.wait(4)


class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene):
    CONFIG = {
        "show_basis_vectors": False,
        "show_coordinates": True,
        "zoom_factor": 0.05,
    }

    def setup(self):
        LinearTransformationScene.setup(self)
        ZoomedScene.setup(self)

    def construct(self):
        def example_function(point):
            x, y, z = point
            return np.array([
                x + np.sin(y),
                y + np.sin(x),
                0
            ])

        zoomed_camera = self.zoomed_camera
        zoomed_display = self.zoomed_display
        frame = zoomed_camera.frame
        frame.move_to(3 * LEFT + 1 * UP)
        frame.set_color(YELLOW)
        zoomed_display.display_frame.set_color(YELLOW)
        zd_rect = BackgroundRectangle(
            zoomed_display,
            fill_opacity=1,
            buff=MED_SMALL_BUFF,
        )
        self.add_foreground_mobject(zd_rect)
        zd_rect.anim = UpdateFromFunc(
            zd_rect,
            lambda rect: rect.replace(zoomed_display).scale(1.1)
        )
        zd_rect.next_to(FRAME_HEIGHT * UP, UP)

        tiny_grid = NumberPlane(
            x_radius=2,
            y_radius=2,
            color=BLUE_E,
            secondary_color=GREY_D,
        )
        tiny_grid.replace(frame)

        jacobian_words = TexText("Jacobian")
        jacobian_words.add_background_rectangle()
        jacobian_words.scale(1.5)
        jacobian_words.move_to(zoomed_display, UP)
        zoomed_display.next_to(jacobian_words, DOWN)

        self.play(self.get_zoom_in_animation())
        self.activate_zooming()
        self.play(
            self.get_zoomed_display_pop_out_animation(),
            zd_rect.anim
        )
        self.play(
            ShowCreation(tiny_grid),
            Write(jacobian_words),
            run_time=2
        )
        self.add_transformable_mobject(tiny_grid)
        self.add_foreground_mobject(jacobian_words)
        self.wait()
        self.apply_nonlinear_transformation(
            example_function,
            added_anims=[MaintainPositionRelativeTo(
                zoomed_camera.frame, tiny_grid,
            )],
            run_time=5
        )
        self.wait()


class PrinciplesOverlay(PiCreatureScene):
    CONFIG = {
        "default_pi_creature_kwargs": {
            "color": GREY_BROWN,
            "flip_at_start": True,
        },
        "default_pi_creature_start_corner": DR,
    }

    def construct(self):
        morty = self.pi_creature
        q_marks = VGroup(*[Tex("?") for x in range(40)])
        q_marks.arrange_in_grid(4, 10)
        q_marks.space_out_submobjects(1.4)
        for mark in q_marks:
            mark.shift(
                random.random() * RIGHT,
                random.random() * UP,
            )
            mark.scale(1.5)
            mark.set_stroke(BLACK, 1)
        q_marks.next_to(morty, UP)
        q_marks.shift_onto_screen()
        q_marks.sort(
            lambda p: get_norm(p - morty.get_top())
        )

        self.play(morty.change, "pondering")
        self.wait(2)
        self.play(morty.change, "raise_right_hand")
        self.wait()
        self.play(morty.change, "thinking")
        self.wait(4)
        self.play(FadeInFromDown(q_marks[0]))
        self.wait(2)
        self.play(LaggedStartMap(
            FadeInFromDown, q_marks[1:],
            run_time=3
        ))
        self.wait(3)


class ManyInfiniteExpressions(Scene):
    def construct(self):
        frac = get_phi_continued_fraction(10)
        frac.set_height(2)
        frac.to_corner(UL)

        n = 9
        radical_str_parts = [
            "%d + \\sqrt{" % d
            for d in range(1, n + 1)
        ]
        radical_str_parts += ["\\cdots"]
        radical_str_parts += ["}"] * n
        radical = Tex("".join(radical_str_parts))
        radical.to_corner(UR)
        radical.to_edge(DOWN)
        radical.set_color_by_gradient(YELLOW, RED)

        n = 12
        power_tower = Tex(
            *["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n
        )
        power_tower.to_corner(UR)
        power_tower.set_color_by_gradient(BLUE, GREEN)

        self.play(*[
            LaggedStartMap(
                GrowFromCenter, group,
                lag_ratio=0.1,
                run_time=8,
            )
            for group in (frac, radical, power_tower)
        ])
        self.wait(2)


class HoldUpPromo(PrinciplesOverlay):
    def construct(self):
        morty = self.pi_creature

        url = TexText("https://brilliant.org/3b1b/")
        url.to_corner(UL)

        rect = ScreenRectangle(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)

        self.play(FadeOut(rect), FadeOut(url))
        self.play(morty.change, "raise_right_hand")
        self.wait()
        self.play(morty.change, "hooray")
        self.wait(3)


class EndScreen(PatreonEndScreen):
    CONFIG = {
        "specific_patrons": [
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Desmos",
            "<NAME>",
            "CrypticSwarm",
            "<NAME>",
            "Ho\\`ang T\\`ung L\\^am",
            # "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Fela",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "supershabam",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "1st ViewMaths",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Awoo",
            "Dr . <NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Ankalagon",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "<NAME>",
            "Sh\\`im\\'in Ku\\=ang",
            "<NAME>",
            "<NAME>",
            "<NAME>",
        ]
    }


# class Thumbnail(GraphicalIntuitions):
class Thumbnail(AnalyzeFunctionWithTransformations):
    CONFIG = {
        "x_axis_width": 12,
        "graph_origin": 1.5 * DOWN + 4 * LEFT,
        "num_initial_applications": 1,
        "input_line_zero_point": 2 * UP,
    }

    def construct(self):
        self.add_function_title()
        self.title.fade(1)
        self.titles.fade(1)
        self.repeatedly_apply_function()
        self.all_arrows.set_stroke(width=1)

        full_rect = FullScreenFadeRectangle()
        cross = Cross(full_rect)
        cross.set_stroke(width=40)

        # self.add(cross)
# -*- coding: utf-8 -*- # Scene types # These would override number_line_config # "path_arc": 30 * DEGREES, # Mapping animations # input_line_copy.set_stroke(width=2) # Test for if mini_line is in self? # Zooming # Decide how to move camera frame into place # Make sure frame is in final place # Add miniature number_line # Add tiny coordinates # Add tiny dots # To make it seem small # Helpers # Scenes # hard_work_image # neat_example_image # not_so_neat_example_image # physics_image # piles_of_formulas_image # getting_stuck_image # aha_image # graphical_intuition_image # image = rects.get_image() # Helpers # Images matched to narration's introductory list # Remove? # Obviously a silly thing to do, but I'm sweeping # through trying to make sure old scenes don't # completely break in spots which used to have # Continual animations # Fake it # Slope line # deriv_label_update, ### # FadeIn(new_title) # First, just show integers as examples # num.get_bottom(), # map(Tex.add_background_rectangle, numbers) # self.add_foreground_mobject(numbers) # Show more sample_dots # zoom_words.shift_onto_screen() # Go back # Zoom in even more # Transform new zoomed view # Write derivative # Much copy-pasting from previous scenes.  Not great, but # the fastest way to get the ease-of-tweaking I'd like. # target_coordinate_values = self.get_local_coordinate_values( #     self.func(x), dx=self.zoom_factor # ) # target_coordinate_values=target_coordinate_values, # Animations # arrow.set_stroke(GREY_D, 0.5) # phi_point = self.get_input_point(phi) # zcbr = self.zoomed_camera_background_rectangle # zcbr_group = self.zoomed_camera_background_rectangle_group # phi_bro_point = self.get_input_point(phi_bro) # frac.shift(2 * DOWN) # "<NAME>", # class Thumbnail(GraphicalIntuitions): # self.add(cross)