from big_ol_pile_of_manim_imports import *
from old_projects.eoc.chapter2 import Car, MoveCar

class CircleScene(PiCreatureScene):
    CONFIG = {
        "radius" : 1.5,
        "stroke_color" : WHITE,
        "fill_color" : BLUE_E,
        "fill_opacity" : 0.75,
        "radial_line_color" : MAROON_B,
        "outer_ring_color" : GREEN_E,
        "ring_colors" : [BLUE, GREEN],
        "dR" : 0.1,
        "dR_color" : YELLOW,
        "unwrapped_tip" : ORIGIN,
        "include_pi_creature" : False,
        "circle_corner" : UP+LEFT,
    }
    def setup(self):
        PiCreatureScene.setup(self)
        self.circle = Circle(
            radius = self.radius,
            stroke_color = self.stroke_color,
            fill_color = self.fill_color,
            fill_opacity = self.fill_opacity,
        )
        self.circle.to_corner(self.circle_corner, buff = MED_LARGE_BUFF)
        self.radius_line = Line(
            self.circle.get_center(),
            self.circle.get_right(),
            color = self.radial_line_color
        )
        self.radius_brace = Brace(self.radius_line, buff = SMALL_BUFF)
        self.radius_label = self.radius_brace.get_text("$R$", buff = SMALL_BUFF)

        self.radius_group = VGroup(
            self.radius_line, self.radius_brace, self.radius_label
        )
        self.add(self.circle, *self.radius_group)

        if not self.include_pi_creature:
            self.remove(self.get_primary_pi_creature())

    def introduce_circle(self, added_anims = []):
        self.remove(self.circle)
        self.play(
            ShowCreation(self.radius_line),
            GrowFromCenter(self.radius_brace),
            Write(self.radius_label),
        )
        self.circle.set_fill(opacity = 0)

        self.play(
            Rotate(
                self.radius_line, 2*np.pi-0.001, 
                about_point = self.circle.get_center(),
            ),
            ShowCreation(self.circle),
            *added_anims,
            run_time = 2
        )
        self.play(
            self.circle.set_fill, self.fill_color, self.fill_opacity,
            Animation(self.radius_line),
            Animation(self.radius_brace),
            Animation(self.radius_label),
        )

    def increase_radius(self, numerical_dr = True, run_time = 2):
        radius_mobs = VGroup(
            self.radius_line, self.radius_brace, self.radius_label
        )
        nudge_line = Line(
            self.radius_line.get_right(),
            self.radius_line.get_right() + self.dR*RIGHT,
            color = self.dR_color
        )
        nudge_arrow = Arrow(
            nudge_line.get_center() + 0.5*RIGHT+DOWN,
            nudge_line.get_center(),
            color = YELLOW,
            buff = SMALL_BUFF,
            tip_length = 0.2,
        )
        if numerical_dr:
            nudge_label = TexMobject("%.01f"%self.dR)
        else:
            nudge_label = TexMobject("dr")
        nudge_label.set_color(self.dR_color)
        nudge_label.scale(0.75)
        nudge_label.next_to(nudge_arrow.get_start(), DOWN)

        radius_mobs.add(nudge_line, nudge_arrow, nudge_label)

        outer_ring = self.get_outer_ring()

        self.play(
            FadeIn(outer_ring),            
            ShowCreation(nudge_line),
            ShowCreation(nudge_arrow),
            Write(nudge_label),
            run_time = run_time/2.
        )
        self.wait(run_time/2.)
        self.nudge_line = nudge_line
        self.nudge_arrow = nudge_arrow
        self.nudge_label = nudge_label
        self.outer_ring = outer_ring
        return outer_ring

    def get_ring(self, radius, dR, color = GREEN):
        ring = Circle(radius = radius + dR).center()
        inner_ring = Circle(radius = radius)
        inner_ring.rotate(np.pi, RIGHT)
        ring.append_vectorized_mobject(inner_ring)
        ring.set_stroke(width = 0)
        ring.set_fill(color)
        ring.move_to(self.circle)
        ring.R = radius 
        ring.dR = dR
        return ring

    def get_rings(self, **kwargs):
        dR = kwargs.get("dR", self.dR)
        colors = kwargs.get("colors", self.ring_colors)
        radii = np.arange(0, self.radius, dR)
        colors = color_gradient(colors, len(radii))

        rings = VGroup(*[
            self.get_ring(radius, dR = dR, color = color)
            for radius, color in zip(radii, colors)
        ])
        return rings

    def get_outer_ring(self):
        return self.get_ring(
            radius = self.radius, dR = self.dR,
            color = self.outer_ring_color
        )

    def unwrap_ring(self, ring, **kwargs):
        self.unwrap_rings(ring, **kwargs)

    def unwrap_rings(self, *rings, **kwargs):
        added_anims = kwargs.get("added_anims", [])
        rings = VGroup(*rings)
        unwrapped = VGroup(*[
            self.get_unwrapped(ring, **kwargs)
            for ring in rings
        ])
        self.play(
            rings.rotate, np.pi/2,
            rings.next_to, unwrapped.get_bottom(), UP,
            run_time = 2,
            path_arc = np.pi/2,
        )
        self.play(
            Transform(rings, unwrapped, run_time = 3),
            *added_anims
        )

    def get_unwrapped(self, ring, to_edge = LEFT, **kwargs):
        R = ring.R
        R_plus_dr = ring.R + ring.dR
        n_anchors = ring.get_num_curves()
        result = VMobject()
        result.set_points_as_corners([
            interpolate(np.pi*R_plus_dr*LEFT,  np.pi*R_plus_dr*RIGHT, a)
            for a in np.linspace(0, 1, n_anchors/2)
        ]+[
            interpolate(np.pi*R*RIGHT+ring.dR*UP,  np.pi*R*LEFT+ring.dR*UP, a)
            for a in np.linspace(0, 1, n_anchors/2)
        ])
        result.set_style_data(
            stroke_color = ring.get_stroke_color(),
            stroke_width = ring.get_stroke_width(),
            fill_color = ring.get_fill_color(),
            fill_opacity = ring.get_fill_opacity(),
        )
        result.move_to(self.unwrapped_tip, aligned_edge = DOWN)
        result.shift(R_plus_dr*DOWN)
        if to_edge is not None:
            result.to_edge(to_edge)

        return result

    def create_pi_creature(self):
        self.pi_creature = Randolph(color = BLUE_C)
        self.pi_creature.to_corner(DOWN+LEFT)
        return self.pi_creature


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

class Chapter1OpeningQuote(OpeningQuote):
    CONFIG = {
        "quote" : [
            """The art of doing mathematics is finding
            that """, "special case", 
            """that contains all the 
            germs of generality."""
        ],
        "quote_arg_separator" : " ",
        "highlighted_quote_terms" : {
            "special case" : BLUE
        },
        "author" : "David Hilbert",
    }

class Introduction(TeacherStudentsScene):
    def construct(self):
        self.show_series()
        self.show_many_facts()
        self.invent_calculus()

    def show_series(self):
        series = VideoSeries()
        series.to_edge(UP)
        this_video = series[0]
        this_video.set_color(YELLOW)
        this_video.save_state()
        this_video.set_fill(opacity = 0)
        this_video.center()
        this_video.set_height(FRAME_HEIGHT)
        self.this_video = this_video


        words = TextMobject(
            "Welcome to \\\\",
            "Essence of calculus"
        )
        words.set_color_by_tex("Essence of calculus", YELLOW)

        self.teacher.change_mode("happy")
        self.play(
            FadeIn(
                series,
                lag_ratio = 0.5,
                run_time = 2
            ),
            Blink(self.get_teacher())
        )
        self.teacher_says(words, target_mode = "hooray")
        self.change_student_modes(
            *["hooray"]*3,
            look_at_arg = series[1].get_left(),
            added_anims = [
                ApplyMethod(this_video.restore, run_time = 3),
            ]
        )
        self.play(*[
            ApplyMethod(
                video.shift, 0.5*video.get_height()*DOWN,
                run_time = 3,
                rate_func = squish_rate_func(
                    there_and_back, alpha, alpha+0.3
                )
            )
            for video, alpha in zip(series, np.linspace(0, 0.7, len(series)))
        ]+[
            Animation(self.teacher.bubble),
            Animation(self.teacher.bubble.content),
        ])

        essence_words = words.get_part_by_tex("Essence").copy()
        self.play(
            FadeOut(self.teacher.bubble),
            FadeOut(self.teacher.bubble.content),
            essence_words.next_to, series, DOWN,
            *[
                ApplyMethod(pi.change_mode, "pondering")
                for pi in self.get_pi_creatures()
            ]
        )
        self.wait(3)

        self.series = series
        self.essence_words = essence_words

    def show_many_facts(self):
        rules = list(it.starmap(TexMobject, [
            ("{d(", "x", "^2)", "\\over \\,", "dx}", "=", "2", "x"),
            (
                "d(", "f", "g", ")", "=", 
                "f", "dg", "+", "g", "df",
            ),
            (
                "F(x)", "=", "\\int_0^x", 
                "\\frac{dF}{dg}(t)\\,", "dt"
            ),
            (
                "f(x)", "=", "\\sum_{n = 0}^\\infty",
                "f^{(n)}(a)", "\\frac{(x-a)^n}{n!}"
            ),
        ]))
        video_indices = [2, 3, 7, 10]
        tex_to_color = [
            ("x", BLUE),
            ("f", BLUE),
            ("df", BLUE),
            ("g", YELLOW),
            ("dg", YELLOW),
            ("f(x)", BLUE),
            ( "f^{(n)}(a)", BLUE),
        ]

        for rule in rules:
            for tex, color in tex_to_color:
                rule.set_color_by_tex(tex, color, substring = False)
            rule.next_to(self.teacher.get_corner(UP+LEFT), UP)
            rule.shift_onto_screen()

        student_index = 1
        student = self.get_students()[student_index]
        self.change_student_modes(
            "pondering", "sassy", "pondering",
            look_at_arg = self.teacher.eyes,
            added_anims = [
                self.teacher.change_mode, "plain"
            ]
        )
        self.wait(2)
        self.play(
            Write(rules[0]),
            self.teacher.change_mode, "raise_right_hand",
        )
        self.wait()
        alt_rules_list = list(rules[1:]) + [VectorizedPoint(self.teacher.eyes.get_top())]
        for last_rule, rule, video_index in zip(rules, alt_rules_list, video_indices):
            video = self.series[video_index]
            self.play(
                last_rule.replace, video,
                FadeIn(rule),
            )
            self.play(Animation(rule))
            self.wait()
        self.play(
            self.teacher.change_mode, "happy",
            self.teacher.look_at, student.eyes
        )

    def invent_calculus(self):
        student = self.get_students()[1]
        creatures = self.get_pi_creatures()
        creatures.remove(student)
        creature_copies = creatures.copy()
        self.remove(creatures)
        self.add(creature_copies)

        calculus = VGroup(*self.essence_words[-len("calculus"):])
        calculus.generate_target()
        invent = TextMobject("Invent")
        invent_calculus = VGroup(invent, calculus.target)
        invent_calculus.arrange(RIGHT, buff = MED_SMALL_BUFF)
        invent_calculus.next_to(student, UP, 1.5*LARGE_BUFF)
        invent_calculus.shift(RIGHT)
        arrow = Arrow(invent_calculus, student)

        fader = Rectangle(
            width = FRAME_WIDTH,
            height = FRAME_HEIGHT,
            stroke_width = 0,
            fill_color = BLACK,
            fill_opacity = 0.5,
        )

        self.play(
            FadeIn(fader),
            Animation(student),
            Animation(calculus)
        )
        self.play(
            Write(invent),
            MoveToTarget(calculus),
            student.change_mode, "erm",
            student.look_at, calculus
        )
        self.play(ShowCreation(arrow))
        self.wait(2)

class PreviewFrame(Scene):
    def construct(self):
        frame = Rectangle(height = 9, width = 16, color = WHITE)
        frame.set_height(1.5*FRAME_Y_RADIUS)

        colors = iter(color_gradient([BLUE, YELLOW], 3))
        titles = [
            TextMobject("Chapter %d:"%d, s).to_edge(UP).set_color(next(colors))
            for d, s in [
                (3, "Derivative formulas through geometry"),
                (4, "Chain rule, product rule, etc."),
                (7, "Limits"),
            ]
        ]
        title = titles[0]

        frame.next_to(title, DOWN)

        self.add(frame, title)
        self.wait(3)
        for next_title in titles[1:]:
            self.play(Transform(title, next_title))
            self.wait(3)

class ProductRuleDiagram(Scene):
    def construct(self):
        df = 0.4
        dg = 0.2
        rect_kwargs = {
            "stroke_width" : 0,
            "fill_color" : BLUE,
            "fill_opacity" : 0.6,
        }

        rect = Rectangle(width = 4, height = 3, **rect_kwargs)
        rect.shift(DOWN)
        df_rect = Rectangle(
            height = rect.get_height(),
            width = df,
            **rect_kwargs
        )
        dg_rect = Rectangle(
            height = dg,
            width = rect.get_width(),
            **rect_kwargs
        )
        corner_rect = Rectangle(
            height = dg, 
            width = df,
            **rect_kwargs
        )
        d_rects = VGroup(df_rect, dg_rect, corner_rect)
        for d_rect, direction in zip(d_rects, [RIGHT, DOWN, RIGHT+DOWN]):
            d_rect.next_to(rect, direction, buff = 0)
            d_rect.set_fill(YELLOW, 0.75)

        corner_pairs = [
            (DOWN+RIGHT, UP+RIGHT),
            (DOWN+RIGHT, DOWN+LEFT),
            (DOWN+RIGHT, DOWN+RIGHT),
        ]
        for d_rect, corner_pair in zip(d_rects, corner_pairs):
            line = Line(*[
                rect.get_corner(corner)
                for corner in corner_pair
            ])
            d_rect.line = d_rect.copy().replace(line, stretch = True)
            d_rect.line.set_color(d_rect.get_color())

        f_brace = Brace(rect, UP)
        g_brace = Brace(rect, LEFT)
        df_brace = Brace(df_rect, UP)
        dg_brace = Brace(dg_rect, LEFT)

        f_label = f_brace.get_text("$f$")
        g_label = g_brace.get_text("$g$")
        df_label = df_brace.get_text("$df$")
        dg_label = dg_brace.get_text("$dg$")

        VGroup(f_label, df_label).set_color(GREEN)
        VGroup(g_label, dg_label).set_color(RED)

        f_label.generate_target()
        g_label.generate_target()
        fg_group = VGroup(f_label.target, g_label.target)
        fg_group.generate_target()
        fg_group.target.arrange(RIGHT, buff = SMALL_BUFF)
        fg_group.target.move_to(rect.get_center())

        for mob in df_brace, df_label, dg_brace, dg_label:
            mob.save_state()
            mob.scale(0.01, about_point = rect.get_corner(
                mob.get_center() - rect.get_center()
            ))

        self.add(rect)
        self.play(
            GrowFromCenter(f_brace),
            GrowFromCenter(g_brace),
            Write(f_label),
            Write(g_label),
        )
        self.play(MoveToTarget(fg_group))
        self.play(*[
            mob.restore
            for mob in (df_brace, df_label, dg_brace, dg_label)
        ] + [
            ReplacementTransform(d_rect.line, d_rect)
            for d_rect in d_rects
        ])
        self.wait()
        self.play(
            d_rects.space_out_submobjects, 1.2,
            MaintainPositionRelativeTo(
                VGroup(df_brace, df_label),
                df_rect
            ),
            MaintainPositionRelativeTo(
                VGroup(dg_brace, dg_label),
                dg_rect
            ),
        )
        self.wait()

        deriv = TexMobject(
            "d(", "fg", ")", "=", 
            "f", "\\cdot", "dg", "+", "g", "\\cdot", "df"
        )
        deriv.to_edge(UP)
        alpha_iter = iter(np.linspace(0, 0.5, 5))
        self.play(*[
            ApplyMethod(
                mob.copy().move_to,
                deriv.get_part_by_tex(tex, substring = False),
                rate_func = squish_rate_func(smooth, alpha, alpha+0.5)
            )
            for mob, tex in [
                (fg_group, "fg"),
                (f_label, "f"),
                (dg_label, "dg"),
                (g_label, "g"),
                (df_label, "df"),
            ]
            for alpha in [next(alpha_iter)]
        ]+[
            Write(VGroup(*it.chain(*[
                deriv.get_parts_by_tex(tex, substring = False)
                for tex in ("d(", ")", "=", "\\cdot", "+")
            ])))
        ], run_time = 3)
        self.wait()

class IntroduceCircle(CircleScene):
    CONFIG = {
        "include_pi_creature" : True,
        "unwrapped_tip" : 2*RIGHT
    }
    def construct(self):
        self.force_skipping()

        self.introduce_area()
        self.question_area()
        self.show_calculus_symbols()

    def introduce_area(self):
        area = TexMobject("\\text{Area}", "=", "\\pi", "R", "^2")
        area.next_to(self.pi_creature.get_corner(UP+RIGHT), UP+RIGHT)

        self.remove(self.circle, self.radius_group)
        self.play(
            self.pi_creature.change_mode, "pondering",
            self.pi_creature.look_at, self.circle
        )
        self.introduce_circle()
        self.wait()
        R_copy = self.radius_label.copy()
        self.play(
            self.pi_creature.change_mode, "raise_right_hand",
            self.pi_creature.look_at, area,
            Transform(R_copy, area.get_part_by_tex("R"))
        )
        self.play(Write(area))
        self.remove(R_copy)
        self.wait()

        self.area = area

    def question_area(self):
        q_marks = TexMobject("???")
        q_marks.next_to(self.pi_creature, UP)
        rings = VGroup(*reversed(self.get_rings()))
        unwrapped_rings = VGroup(*[
            self.get_unwrapped(ring, to_edge = None)
            for ring in rings
        ])
        unwrapped_rings.arrange(UP, buff = SMALL_BUFF)
        unwrapped_rings.move_to(self.unwrapped_tip, UP)
        ring_anim_kwargs = {
            "run_time" : 3,
            "lag_ratio" : 0.5
        }

        self.play(
            Animation(self.area),
            Write(q_marks),
            self.pi_creature.change_mode, "confused",
            self.pi_creature.look_at, self.area,
        )
        self.wait()
        self.play(
            FadeIn(rings, **ring_anim_kwargs),
            Animation(self.radius_group),
            FadeOut(q_marks),
            self.pi_creature.change_mode, "thinking"
        )
        self.wait()
        self.play(
            rings.rotate, np.pi/2,
            rings.move_to, unwrapped_rings.get_top(),
            Animation(self.radius_group),
            path_arc = np.pi/2,
            **ring_anim_kwargs
        )
        self.play(
            Transform(rings, unwrapped_rings, **ring_anim_kwargs),
        )
        self.wait()

    def show_calculus_symbols(self):
        ftc = TexMobject(
            "\\int_0^R", "\\frac{dA}{dr}", "\\,dr",
            "=", "A(R)"
        )
        ftc.shift(2*UP)

        self.play(
            ReplacementTransform(
                self.area.get_part_by_tex("R").copy(),
                ftc.get_part_by_tex("int")
            ),
            self.pi_creature.change_mode, "plain"
        )
        self.wait()
        self.play(
            ReplacementTransform(
                self.area.get_part_by_tex("Area").copy(),
                ftc.get_part_by_tex("frac")
            ),
            ReplacementTransform(
                self.area.get_part_by_tex("R").copy(),
                ftc.get_part_by_tex("\\,dr")
            )
        )
        self.wait()
        self.play(Write(VGroup(*ftc[-2:])))
        self.wait(2)

class ApproximateOneRing(CircleScene, ReconfigurableScene):
    CONFIG = {
        "num_lines" : 24,
        "ring_index_proportion" : 0.6,
        "ring_shift_val" : 6*RIGHT,
        "ring_colors" : [BLUE, GREEN_E],
        "unwrapped_tip" : 2*RIGHT+0.5*UP,
    }
    def setup(self):
        CircleScene.setup(self)
        ReconfigurableScene.setup(self)

    def construct(self):
        self.force_skipping()

        self.write_radius_three()
        self.try_to_understand_area()
        self.slice_into_rings()
        self.isolate_one_ring()

        self.revert_to_original_skipping_status()
        self.straighten_ring_out()
        self.force_skipping()

        self.approximate_as_rectangle()

    def write_radius_three(self):
        three = TexMobject("3")
        three.move_to(self.radius_label)

        self.look_at(self.circle)
        self.play(Transform(
            self.radius_label, three,
            path_arc = np.pi
        ))
        self.wait()

    def try_to_understand_area(self):
        line_sets = [
            VGroup(*[
                Line(
                    self.circle.point_from_proportion(alpha),
                    self.circle.point_from_proportion(func(alpha)),
                )
                for alpha in np.linspace(0, 1, self.num_lines)
            ])
            for func in [
                lambda alpha : 1-alpha,
                lambda alpha : (0.5-alpha)%1,
                lambda alpha : (alpha + 0.4)%1,
                lambda alpha : (alpha + 0.5)%1,
            ]
        ]
        for lines in line_sets:
            lines.set_stroke(BLACK, 2)
        lines = line_sets[0]

        self.play(
            ShowCreation(
                lines, 
                run_time = 2, 
                lag_ratio = 0.5
            ),
            Animation(self.radius_group),
            self.pi_creature.change_mode, "maybe"
        )
        self.wait(2)
        for new_lines in line_sets[1:]:
            self.play(
                Transform(lines, new_lines),
                Animation(self.radius_group)
            )
            self.wait()
        self.wait()
        self.play(FadeOut(lines), Animation(self.radius_group))

    def slice_into_rings(self):
        rings = self.get_rings()
        rings.set_stroke(BLACK, 1)

        self.play(
            FadeIn(
                rings,
                lag_ratio = 0.5,
                run_time = 3
            ),
            Animation(self.radius_group),
            self.pi_creature.change_mode, "pondering",
            self.pi_creature.look_at, self.circle
        )
        self.wait(2)
        for x in range(2):
            self.play(
                Rotate(rings, np.pi, in_place = True, run_time = 2),
                Animation(self.radius_group),
                self.pi_creature.change_mode, "happy"
            )
        self.wait(2)

        self.rings = rings

    def isolate_one_ring(self):
        rings = self.rings
        index = int(self.ring_index_proportion*len(rings))
        original_ring = rings[index]
        ring = original_ring.copy()

        radius = Line(ORIGIN, ring.R*RIGHT, color = WHITE)
        radius.rotate(np.pi/4)
        r_label = TexMobject("r")
        r_label.next_to(radius.get_center(), UP+LEFT, SMALL_BUFF)
        area_q = TextMobject("Area", "?", arg_separator = "")
        area_q.set_color(YELLOW)


        self.play(
            ring.shift, self.ring_shift_val,
            original_ring.set_fill, None, 0.25,
            Animation(self.radius_group),
        )

        VGroup(radius, r_label).shift(ring.get_center())
        area_q.next_to(ring, RIGHT)

        self.play(ShowCreation(radius))
        self.play(Write(r_label))
        self.wait()
        self.play(Write(area_q))
        self.wait()
        self.play(*[
            ApplyMethod(
                r.set_fill, YELLOW, 
                rate_func = squish_rate_func(there_and_back, alpha, alpha+0.15),
                run_time = 3
            )
            for r, alpha in zip(rings, np.linspace(0, 0.85, len(rings)))
        ]+[
            Animation(self.radius_group)
        ])
        self.wait()
        self.change_mode("thinking")
        self.wait()

        self.original_ring = original_ring
        self.ring = ring
        self.ring_radius_group = VGroup(radius, r_label)
        self.area_q = area_q

    def straighten_ring_out(self):
        ring = self.ring.copy()
        trapezoid = TextMobject("Trapezoid?")
        rectangle_ish = TextMobject("Rectangle-ish")
        for text in trapezoid, rectangle_ish:
            text.next_to(
                self.pi_creature.get_corner(UP+RIGHT), 
                DOWN+RIGHT, buff = MED_LARGE_BUFF
            )

        self.unwrap_ring(ring, to_edge = RIGHT)
        self.change_mode("pondering")
        self.wait()
        self.play(Write(trapezoid))
        self.wait()
        self.play(trapezoid.shift, DOWN)
        strike = Line(
            trapezoid.get_left(), trapezoid.get_right(),
            stroke_color = RED,
            stroke_width = 8
        )
        self.play(
            Write(rectangle_ish),
            ShowCreation(strike),
            self.pi_creature.change_mode, "happy"
        )
        self.wait()
        self.play(*list(map(FadeOut, [trapezoid, strike])))

        self.unwrapped_ring = ring

    def approximate_as_rectangle(self):
        top_brace, side_brace = [
            Brace(
                self.unwrapped_ring, vect, buff = SMALL_BUFF,
                min_num_quads = 2,
            )
            for vect in (UP, LEFT)
        ]
        top_brace.scale_in_place(self.ring.R/(self.ring.R+self.dR))
        side_brace.set_stroke(WHITE, 0.5)


        width_label = TexMobject("2\\pi", "r")
        width_label.next_to(top_brace, UP, SMALL_BUFF)
        dr_label = TexMobject("dr")
        q_marks = TexMobject("???")
        concrete_dr = TexMobject("=0.1")
        concrete_dr.submobjects.reverse()
        for mob in dr_label, q_marks, concrete_dr:
            mob.next_to(side_brace, LEFT, SMALL_BUFF)
        dr_label.save_state()

        alt_side_brace = side_brace.copy()
        alt_side_brace.move_to(ORIGIN, UP+RIGHT)
        alt_side_brace.rotate(-np.pi/2)
        alt_side_brace.shift(
            self.original_ring.get_boundary_point(RIGHT)
        )
        alt_dr_label = dr_label.copy()
        alt_dr_label.next_to(alt_side_brace, UP, SMALL_BUFF)

        approx = TexMobject("\\approx")
        approx.next_to(
            self.area_q.get_part_by_tex("Area"), 
            RIGHT,
            align_using_submobjects = True,
        )
        two_pi_r_dr = VGroup(width_label, dr_label).copy()
        two_pi_r_dr.generate_target()
        two_pi_r_dr.target.arrange(
            RIGHT, buff = SMALL_BUFF, aligned_edge = DOWN
        )
        two_pi_r_dr.target.next_to(approx, RIGHT, aligned_edge = DOWN)

        self.play(GrowFromCenter(top_brace))
        self.play(
            Write(width_label.get_part_by_tex("pi")),
            ReplacementTransform(
                self.ring_radius_group[1].copy(),
                width_label.get_part_by_tex("r")
            )
        )
        self.wait()
        self.play(
            GrowFromCenter(side_brace),
            Write(q_marks)
        )
        self.change_mode("confused")
        self.wait()
        for num_rings in 20, 7:
            self.show_alternate_width(num_rings)
        self.play(ReplacementTransform(q_marks, dr_label))
        self.play(
            ReplacementTransform(side_brace.copy(), alt_side_brace),
            ReplacementTransform(dr_label.copy(), alt_dr_label),
            run_time = 2
        )
        self.wait()
        self.play(
            dr_label.next_to, concrete_dr.copy(), LEFT, SMALL_BUFF, DOWN,
            Write(concrete_dr, run_time = 2),
            self.pi_creature.change_mode, "pondering"
        )
        self.wait(2)
        self.play(
            MoveToTarget(two_pi_r_dr),
            FadeIn(approx),
            self.area_q.get_part_by_tex("?").fade, 1,
        )
        self.wait()
        self.play(
            FadeOut(concrete_dr),
            dr_label.restore
        )
        self.show_alternate_width(
            40, 
            transformation_kwargs = {"run_time" : 4},
            return_to_original_configuration = False,
        )
        self.wait(2)
        self.look_at(self.circle)
        self.play(
            ApplyWave(self.rings, amplitude = 0.1),
            Animation(self.radius_group),
            Animation(alt_side_brace),
            Animation(alt_dr_label),
            run_time = 3,
            lag_ratio = 0.5
        )
        self.wait(2)

    def show_alternate_width(self, num_rings, **kwargs):
        self.transition_to_alt_config(
            dR = self.radius/num_rings, **kwargs
        )

class MoveForwardWithApproximation(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Move forward with \\\\",
            "the", "approximation"
        )
        self.change_student_modes("hesitant", "erm", "sassy")
        self.wait()
        words = TextMobject(
            "It gets better", 
            "\\\\ for smaller ",
            "$dr$"
        )
        words.set_color_by_tex("dr", BLUE)
        self.teacher_says(words, target_mode = "shruggie")
        self.wait(3)

class GraphRectangles(CircleScene, GraphScene):
    CONFIG = {
        "graph_origin" : 3.25*LEFT+2.5*DOWN,
        "x_min" : 0,
        "x_max" : 4,
        "x_axis_width" : 7,
        "x_labeled_nums" : list(range(5)),
        "x_axis_label" : "$r$",
        "y_min" : 0,
        "y_max" : 20,
        "y_tick_frequency" : 2.5,
        "y_labeled_nums" : list(range(5, 25, 5)),
        "y_axis_label" : "",
        "exclude_zero_label" : False,
        "num_rings_in_ring_sum_start" : 3,
        "tick_height" : 0.2,
    }
    def setup(self):
        CircleScene.setup(self)
        GraphScene.setup(self)
        self.setup_axes()
        self.remove(self.axes)

        # self.pi_creature.change_mode("pondering")
        # self.pi_creature.look_at(self.circle)
        # self.add(self.pi_creature)

        three = TexMobject("3")
        three.move_to(self.radius_label)
        self.radius_label.save_state()
        Transform(self.radius_label, three).update(1)

    def construct(self):
        self.draw_ring_sum()
        self.draw_r_values()
        self.unwrap_rings_onto_graph()
        self.draw_graph()
        self.point_out_approximation()
        self.let_dr_approah_zero()
        self.compute_area_under_graph()
        self.show_circle_unwrapping()

    def draw_ring_sum(self):
        rings = self.get_rings()
        rings.set_stroke(BLACK, 1)
        ring_sum, draw_ring_sum_anims = self.get_ring_sum(rings)
        area_label = TexMobject(
            "\\text{Area}", "\\approx", 
            "2\\pi", "r", "\\,dr"
        )
        area_label.set_color_by_tex("r", YELLOW, substring = False)
        area_label.next_to(ring_sum, RIGHT, aligned_edge = UP)
        area = area_label.get_part_by_tex("Area")
        arrow_start = area.get_corner(DOWN+LEFT)
        arrows = VGroup(*[
            Arrow(
                arrow_start,
                ring.target.get_boundary_point(
                    arrow_start - ring.target.get_center()
                ),
                color = ring.get_color()
            )
            for ring in rings
            if ring.target.get_fill_opacity() > 0
        ])
        
        self.add(rings, self.radius_group)
        self.remove(self.circle)
        self.wait()
        self.play(*draw_ring_sum_anims)
        self.play(Write(area_label, run_time = 2))
        self.play(ShowCreation(arrows))
        self.wait()

        self.ring_sum = ring_sum
        area_label.add(arrows)
        self.area_label = area_label
        self.rings = rings

    def draw_r_values(self):
        values_of_r = TextMobject("Values of ", "$r$")
        values_of_r.set_color_by_tex("r", YELLOW)
        values_of_r.next_to(
            self.x_axis, UP, 
            buff = 2*LARGE_BUFF,
            aligned_edge = LEFT
        )

        r_ticks = VGroup(*[
            Line(
                self.coords_to_point(r, -self.tick_height),
                self.coords_to_point(r, self.tick_height),
                color = YELLOW
            )
            for r in np.arange(0, 3, 0.1)
        ])
        arrows = VGroup(*[
            Arrow(
                values_of_r.get_part_by_tex("r").get_bottom(),
                tick.get_top(),
                buff = SMALL_BUFF,
                color = YELLOW,
                tip_length = 0.15
            )
            for tick in (r_ticks[0], r_ticks[-1])
        ])
        first_tick = r_ticks[0].copy()
        moving_arrow = arrows[0].copy()

        index = 2
        dr_brace = Brace(
            VGroup(*r_ticks[index:index+2]), 
            DOWN, buff = SMALL_BUFF
        )
        dr_label = TexMobject("dr")
        dr_label.next_to(
            dr_brace, DOWN, 
            buff = SMALL_BUFF, 
            aligned_edge = LEFT
        )
        dr_group = VGroup(dr_brace, dr_label)

        self.play(
            FadeIn(values_of_r),
            FadeIn(self.x_axis),
        )
        self.play(
            ShowCreation(moving_arrow),
            ShowCreation(first_tick),
        )
        self.play(Indicate(self.rings[0]))
        self.wait()
        self.play(
            Transform(moving_arrow, arrows[-1]),
            ShowCreation(r_ticks, lag_ratio = 0.5),
            run_time = 2
        )
        self.play(Indicate(self.rings[-1]))
        self.wait()
        self.play(FadeIn(dr_group))
        self.wait()
        self.play(*list(map(FadeOut, [moving_arrow, values_of_r])))

        self.x_axis.add(r_ticks)
        self.r_ticks = r_ticks
        self.dr_group = dr_group
        
    def unwrap_rings_onto_graph(self):
        rings = self.rings
        graph = self.get_graph(lambda r : 2*np.pi*r)
        flat_graph = self.get_graph(lambda r : 0)
        rects, flat_rects = [
            self.get_riemann_rectangles(
                g, x_min = 0, x_max = 3, dx = self.dR,
                start_color = self.rings[0].get_fill_color(),
                end_color = self.rings[-1].get_fill_color(),
            )
            for g in (graph, flat_graph)
        ]
        self.graph, self.flat_rects = graph, flat_rects

        transformed_rings = VGroup()
        self.ghost_rings = VGroup()        
        for index, rect, r in zip(it.count(), rects, np.arange(0, 3, 0.1)):
            proportion = float(index)/len(rects)
            ring_index = int(len(rings)*proportion**0.6)
            ring = rings[ring_index]
            if ring in transformed_rings:
                ring = ring.copy()
            transformed_rings.add(ring)
            if ring.get_fill_opacity() > 0:
                ghost_ring = ring.copy()
                ghost_ring.set_fill(opacity = 0.25)
                self.add(ghost_ring, ring)
                self.ghost_rings.add(ghost_ring)

            ring.rect = rect

            n_anchors = ring.get_num_curves()            
            target = VMobject()
            target.set_points_as_corners([
                interpolate(ORIGIN,  DOWN, a)
                for a in np.linspace(0, 1, n_anchors/2)
            ]+[
                interpolate(DOWN+RIGHT, RIGHT, a)
                for a in np.linspace(0, 1, n_anchors/2)
            ])
            target.replace(rect, stretch = True)
            target.stretch_to_fit_height(2*np.pi*r)
            target.move_to(rect, DOWN)
            target.set_stroke(BLACK, 1)
            target.set_fill(ring.get_fill_color(), 1)
            ring.target = target
            ring.original_ring = ring.copy()

        foreground_animations = list(map(Animation, [self.x_axis, self.area_label]))
        example_ring = transformed_rings[2]

        self.play(
            MoveToTarget(
                example_ring,
                path_arc = -np.pi/2,
                run_time = 2
            ),
            Animation(self.x_axis),
        )
        self.wait(2)
        self.play(*[
            MoveToTarget(
                ring,
                path_arc = -np.pi/2,
                run_time = 4,
                rate_func = squish_rate_func(smooth, alpha, alpha+0.25)
            )
            for ring, alpha in zip(
                transformed_rings, 
                np.linspace(0, 0.75, len(transformed_rings))
            )
        ] + foreground_animations)
        self.wait()

        ##Demonstrate height of one rect
        highlighted_ring = transformed_rings[6].copy()
        original_ring = transformed_rings[6].original_ring
        original_ring.move_to(highlighted_ring, RIGHT)
        original_ring.set_fill(opacity = 1)
        highlighted_ring.save_state()

        side_brace = Brace(highlighted_ring, RIGHT)
        height_label = side_brace.get_text("2\\pi", "r")
        height_label.set_color_by_tex("r", YELLOW)

        self.play(
            transformed_rings.set_fill, None, 0.2,
            Animation(highlighted_ring),
            *foreground_animations
        )
        self.play(
            self.dr_group.arrange, DOWN,
            self.dr_group.next_to, highlighted_ring, 
            DOWN, SMALL_BUFF
        )
        self.wait()
        self.play(
            GrowFromCenter(side_brace),
            Write(height_label)
        )
        self.wait()
        self.play(Transform(highlighted_ring, original_ring))
        self.wait()
        self.play(highlighted_ring.restore)
        self.wait()
        self.play(
            transformed_rings.set_fill, None, 1,
            FadeOut(side_brace),
            FadeOut(height_label),
            *foreground_animations
        )
        self.remove(highlighted_ring)
        self.wait()

        ##Rescale
        self.play(*[
            ApplyMethod(
                ring.replace, ring.rect, 
                method_kwargs = {"stretch" : True}
            )
            for ring in transformed_rings
        ] + [
            Write(self.y_axis),
            FadeOut(self.area_label),
        ] + foreground_animations)
        self.remove(transformed_rings)
        self.add(rects)
        self.wait()

        self.rects = rects

    def draw_graph(self):
        graph_label = self.get_graph_label(
            self.graph, "2\\pi r", 
            direction = UP+LEFT,
            x_val = 2.5,
            buff = SMALL_BUFF
        )

        self.play(ShowCreation(self.graph))
        self.play(Write(graph_label))
        self.wait()
        self.play(*[
            Transform(
                rect, flat_rect,
                run_time = 2,
                rate_func = squish_rate_func(
                    lambda t : 0.1*there_and_back(t), 
                    alpha, alpha+0.5
                ),
                lag_ratio = 0.5
            )
            for rect, flat_rect, alpha in zip(
                self.rects, self.flat_rects,
                np.linspace(0, 0.5, len(self.rects))
            )
        ] + list(map(Animation, [self.x_axis, self.graph]))
        )
        self.wait(2)

    def point_out_approximation(self):
        rect = self.rects[10]
        rect.generate_target()
        rect.save_state()
        approximation = TextMobject("= Approximation")
        approximation.scale(0.8)
        group = VGroup(rect.target, approximation)
        group.arrange(RIGHT)
        group.to_edge(RIGHT)

        self.play(
            MoveToTarget(rect),
            Write(approximation),
        )
        self.wait(2)
        self.play(
            rect.restore,
            FadeOut(approximation)
        )
        self.wait()

    def let_dr_approah_zero(self):
        thinner_rects_list = [
            self.get_riemann_rectangles(
                self.graph,
                x_min = 0, 
                x_max = 3,
                dx = 1./(10*2**n),
                stroke_width = 1./(2**n),
                start_color = self.rects[0].get_fill_color(),
                end_color = self.rects[-1].get_fill_color(),
            )
            for n in range(1, 5)
        ]

        self.play(*list(map(FadeOut, [self.r_ticks, self.dr_group])))
        self.x_axis.remove(self.r_ticks, *self.r_ticks)
        for new_rects in thinner_rects_list:
            self.play(
                Transform(
                    self.rects, new_rects, 
                    lag_ratio = 0.5,
                    run_time = 2
                ),
                Animation(self.axes),
                Animation(self.graph),
            )
            self.wait()
        self.play(ApplyWave(
            self.rects,
            direction = RIGHT,
            run_time = 2,
            lag_ratio = 0.5,
        ))
        self.wait()

    def compute_area_under_graph(self):
        formula, formula_with_R = formulas = [
            self.get_area_formula(R)
            for R in ("3", "R")
        ]
        for mob in formulas:
            mob.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF)

        brace = Brace(self.rects, RIGHT)
        height_label = brace.get_text("$2\\pi \\cdot 3$")
        height_label_with_R = brace.get_text("$2\\pi \\cdot R$")
        base_line = Line(
            self.coords_to_point(0, 0),
            self.coords_to_point(3, 0),
            color = YELLOW
        )

        fresh_rings = self.get_rings(dR = 0.025)
        fresh_rings.set_stroke(width = 0)
        self.radius_label.restore()
        VGroup(
            fresh_rings, self.radius_group
        ).to_corner(UP+LEFT, buff = SMALL_BUFF)

        self.play(Write(formula.top_line, run_time = 2))
        self.play(FocusOn(base_line))
        self.play(ShowCreation(base_line))
        self.wait()
        self.play(
            GrowFromCenter(brace),
            Write(height_label)
        )
        self.wait()
        self.play(FocusOn(formula))
        self.play(Write(formula.mid_line))
        self.wait()
        self.play(Write(formula.bottom_line))
        self.wait(2)

        self.play(*list(map(FadeOut, [
            self.ghost_rings,
            self.ring_sum.tex_mobs
        ])))
        self.play(*list(map(FadeIn, [fresh_rings, self.radius_group])))
        self.wait()
        self.play(
            Transform(formula, formula_with_R),
            Transform(height_label, height_label_with_R),
        )
        self.wait(2)

        self.fresh_rings = fresh_rings

    def show_circle_unwrapping(self):
        rings = self.fresh_rings
        rings.rotate_in_place(np.pi)
        rings.submobjects.reverse()
        ghost_rings = rings.copy()
        ghost_rings.set_fill(opacity = 0.25)
        self.add(ghost_rings, rings, self.radius_group)

        unwrapped = VGroup(*[
            self.get_unwrapped(ring, to_edge = None)
            for ring in rings
        ])
        unwrapped.stretch_to_fit_height(1)
        unwrapped.stretch_to_fit_width(2)
        unwrapped.move_to(ORIGIN, DOWN)
        unwrapped.apply_function(
            lambda p : np.dot(p, 
                np.array([[1, 0, 0], [-1, 1, 0], [0, 0, 1]])
            ),
            maintain_smoothness = False
        )
        unwrapped.rotate(np.pi/2)
        unwrapped.replace(self.rects, stretch = True)

        self.play(self.rects.fade, 0.8)
        self.play(
            Transform(
                rings, unwrapped,
                run_time = 5,
                lag_ratio = 0.5,
            ),
            Animation(self.radius_group)
        )
        self.wait()

    #####

    def get_ring_sum(self, rings):
        arranged_group = VGroup()
        tex_mobs = VGroup()
        for ring in rings:
            ring.generate_target()
            ring.target.set_stroke(width = 0)

        for ring in rings[:self.num_rings_in_ring_sum_start]:
            plus = TexMobject("+")
            arranged_group.add(ring.target)
            arranged_group.add(plus)
            tex_mobs.add(plus)
        dots = TexMobject("\\vdots")
        plus = TexMobject("+")
        arranged_group.add(dots, plus)
        tex_mobs.add(dots, plus)
        last_ring = rings[-1]

        arranged_group.add(last_ring.target)
        arranged_group.arrange(DOWN, buff = SMALL_BUFF)
        arranged_group.set_height(FRAME_HEIGHT-1)
        arranged_group.to_corner(DOWN+LEFT, buff = MED_SMALL_BUFF)
        for mob in tex_mobs:
            mob.scale_in_place(0.7)

        middle_rings = rings[self.num_rings_in_ring_sum_start:-1]
        alphas = np.linspace(0, 1, len(middle_rings))
        for ring, alpha in zip(middle_rings, alphas):
            ring.target.set_fill(opacity = 0)
            ring.target.move_to(interpolate(
                dots.get_left(), last_ring.target.get_center(), alpha
            ))

        draw_ring_sum_anims = [Write(tex_mobs)]
        draw_ring_sum_anims += [
            MoveToTarget(
                ring,
                run_time = 3,
                path_arc = -np.pi/3,
                rate_func = squish_rate_func(smooth, alpha, alpha+0.8)
            )
            for ring, alpha in zip(rings, np.linspace(0, 0.2, len(rings)))
        ]
        draw_ring_sum_anims.append(FadeOut(self.radius_group))
        
        ring_sum = VGroup(rings, tex_mobs)
        ring_sum.rings = VGroup(*[r.target for r in rings])
        ring_sum.tex_mobs = tex_mobs
        
        return ring_sum, draw_ring_sum_anims

    def get_area_formula(self, R):
        formula = TexMobject(
            "\\text{Area}", "&= \\frac{1}{2}", "b", "h",
            "\\\\ &=", "\\frac{1}{2}", "(%s)"%R, "(2\\pi \\cdot %s)"%R,
            "\\\\ &=", "\\pi ", "%s"%R, "^2"
            
        )
        formula.set_color_by_tex("b", GREEN, substring = False)
        formula.set_color_by_tex("h", RED, substring = False)
        formula.set_color_by_tex("%s"%R, GREEN)
        formula.set_color_by_tex("(2\\pi ", RED)
        formula.set_color_by_tex("(2\\pi ", RED)
        formula.scale(0.8)

        formula.top_line = VGroup(*formula[:4])
        formula.mid_line = VGroup(*formula[4:8])
        formula.bottom_line = VGroup(*formula[8:])
        return formula

class ThinkLikeAMathematician(TeacherStudentsScene):
    def construct(self):
        pi_R_squraed = TexMobject("\\pi", "R", "^2")
        pi_R_squraed.set_color_by_tex("R", YELLOW)
        pi_R_squraed.move_to(self.get_students(), UP)
        pi_R_squraed.set_fill(opacity = 0)

        self.play(
            pi_R_squraed.shift, 2*UP,
            pi_R_squraed.set_fill, None, 1
        )
        self.change_student_modes(*["hooray"]*3)
        self.wait(2)
        self.change_student_modes(
            *["pondering"]*3,
            look_at_arg = self.teacher.eyes,
            added_anims = [PiCreatureSays(
                self.teacher, "But why did \\\\ that work?"
            )]
        )
        self.play(FadeOut(pi_R_squraed))
        self.look_at(2*UP+4*LEFT)
        self.wait(5)

class TwoThingsToNotice(TeacherStudentsScene):
    def construct(self):
        words = TextMobject(
            "Two things to \\\\ note about",
            "$dr$",
        )
        words.set_color_by_tex("dr", GREEN)
        self.teacher_says(words, run_time = 1)
        self.wait(3)

class RecapCircleSolution(GraphRectangles, ReconfigurableScene):
    def setup(self):
        GraphRectangles.setup(self)
        ReconfigurableScene.setup(self)

    def construct(self):
        self.break_up_circle()
        self.show_sum()
        self.dr_indicates_spacing()
        self.smaller_dr()
        self.show_riemann_sum()
        self.limiting_riemann_sum()
        self.full_precision()

    def break_up_circle(self):
        self.remove(self.circle)
        rings = self.get_rings()
        rings.set_stroke(BLACK, 1)
        ring_sum, draw_ring_sum_anims = self.get_ring_sum(rings)

        hard_problem = TextMobject("Hard problem")
        down_arrow = TexMobject("\\Downarrow")
        sum_words = TextMobject("Sum of many \\\\ small values")
        integral_condition = VGroup(hard_problem, down_arrow, sum_words)
        integral_condition.arrange(DOWN)
        integral_condition.scale(0.8)
        integral_condition.to_corner(UP+RIGHT)
        
        self.add(rings, self.radius_group)
        self.play(FadeIn(
            integral_condition, 
            lag_ratio = 0.5
        ))
        self.wait()
        self.play(*draw_ring_sum_anims)

        self.rings = rings
        self.integral_condition = integral_condition

    def show_sum(self):
        visible_rings = [ring for ring in self.rings if ring.get_fill_opacity() > 0]
        radii = self.dR*np.arange(len(visible_rings))
        radii[-1] = 3-self.dR

        radial_lines = VGroup()
        for ring in visible_rings:
            radius_line = Line(ORIGIN, ring.R*RIGHT, color = YELLOW)
            radius_line.rotate(np.pi/4)
            radius_line.shift(ring.get_center())
            radial_lines.add(radius_line)

        approximations = VGroup()
        for ring, radius in zip(visible_rings, radii):
            label = TexMobject(
                "\\approx", "2\\pi", 
                "(%s)"%str(radius), "(%s)"%str(self.dR)
            )
            label[2].set_color(YELLOW)
            label[3].set_color(GREEN)
            label.scale(0.75)
            label.next_to(ring, RIGHT)
            approximations.add(label)
        approximations[-1].shift(UP+0.5*LEFT)
        area_label = TexMobject("2\\pi", "r", "\\, dr")
        area_label.set_color_by_tex("r", YELLOW)
        area_label.set_color_by_tex("dr", GREEN)
        area_label.next_to(approximations, RIGHT, buff = 2*LARGE_BUFF)
        arrows = VGroup(*[
            Arrow(
                area_label.get_left(),
                approximation.get_right(),
                color = WHITE
            )
            for approximation in approximations
        ])

        self.play(Write(area_label))
        self.play(
            ShowCreation(arrows, lag_ratio = 0),
            FadeIn(radial_lines),
            *[
                ReplacementTransform(
                    area_label.copy(),
                    VGroup(*approximation[1:])
                )
                for approximation in approximations
            ]
        )
        self.wait()
        self.play(Write(VGroup(*[
            approximation[0]
            for approximation in approximations
        ])))
        self.wait()

        self.area_label = area_label
        self.area_arrows = arrows
        self.approximations = approximations

    def dr_indicates_spacing(self):
        r_ticks = VGroup(*[
            Line(
                self.coords_to_point(r, -self.tick_height),
                self.coords_to_point(r, self.tick_height),
                color = YELLOW
            )
            for r in np.arange(0, 3, self.dR)
        ])

        index = int(0.75*len(r_ticks))
        brace_ticks = VGroup(*r_ticks[index:index+2])
        dr_brace = Brace(brace_ticks, UP, buff = SMALL_BUFF)

        dr = self.area_label.get_part_by_tex("dr")
        dr_copy = dr.copy()
        circle = Circle().replace(dr)
        circle.scale_in_place(1.3)

        dr_num = self.approximations[0][-1]

        self.play(ShowCreation(circle))
        self.play(FadeOut(circle))
        self.play(ReplacementTransform(
            dr.copy(), dr_num,
            run_time = 2,
            path_arc = np.pi/2,
        ))
        self.wait()
        self.play(FadeIn(self.x_axis))
        self.play(Write(r_ticks, run_time = 1))
        self.wait()
        self.play(
            GrowFromCenter(dr_brace),
            dr_copy.next_to, dr_brace.copy(), UP
        )
        self.wait()

        self.r_ticks = r_ticks
        self.dr_brace_group = VGroup(dr_brace, dr_copy)

    def smaller_dr(self):
        self.transition_to_alt_config(dR = 0.05)

    def show_riemann_sum(self):
        graph = self.get_graph(lambda r : 2*np.pi*r)
        graph_label = self.get_graph_label(
            graph, "2\\pi r",
            x_val = 2.5,
            direction = UP+LEFT
        )
        rects = self.get_riemann_rectangles(
            graph,
            x_min = 0, 
            x_max = 3,
            dx = self.dR
        )

        self.play(
            Write(self.y_axis, run_time = 2),
            *list(map(FadeOut, [
                self.approximations,
                self.area_label,
                self.area_arrows,
                self.dr_brace_group,
                self.r_ticks,
            ]))
        )
        self.play(
            ReplacementTransform(
                self.rings.copy(), rects,
                run_time = 2,
                lag_ratio = 0.5
            ),
            Animation(self.x_axis),
        )
        self.play(ShowCreation(graph))
        self.play(Write(graph_label))
        self.wait()

        self.graph = graph
        self.graph_label = graph_label
        self.rects = rects

    def limiting_riemann_sum(self):
        thinner_rects_list = [
            self.get_riemann_rectangles(
                self.graph,
                x_min = 0, 
                x_max = 3,
                dx = 1./(10*2**n),
                stroke_width = 1./(2**n),
                start_color = self.rects[0].get_fill_color(),
                end_color = self.rects[-1].get_fill_color(),
            )
            for n in range(1, 4)
        ]

        for new_rects in thinner_rects_list:
            self.play(
                Transform(
                    self.rects, new_rects, 
                    lag_ratio = 0.5,
                    run_time = 2
                ),
                Animation(self.axes),
                Animation(self.graph),
            )
            self.wait()

    def full_precision(self):
        words = TextMobject("Area under \\\\ a graph")
        group = VGroup(TexMobject("\\Downarrow"), words)
        group.arrange(DOWN)
        group.set_color(YELLOW)
        group.scale(0.8)
        group.next_to(self.integral_condition, DOWN)

        arc = Arc(start_angle = 2*np.pi/3, angle = 2*np.pi/3)
        arc.scale(2)
        arc.add_tip()
        arc.add(arc[1].copy().rotate(np.pi, RIGHT))
        arc_next_to_group = VGroup(
            self.integral_condition[0][0],
            words[0]
        )
        arc.set_height(
            arc_next_to_group.get_height()-MED_LARGE_BUFF
        )
        arc.next_to(arc_next_to_group, LEFT, SMALL_BUFF)

        self.play(Write(group))
        self.wait()
        self.play(ShowCreation(arc))
        self.wait()

class ExampleIntegralProblems(PiCreatureScene, GraphScene):
    CONFIG = {
        "dt" : 0.2,
        "t_max" : 7,
        "x_max" : 8,
        "y_axis_height" : 5.5,
        "x_axis_label" : "$t$",
        "y_axis_label" : "",
        "graph_origin" : 3*DOWN + 4.5*LEFT
    }
    def construct(self):
        self.write_integral_condition()
        self.show_car()
        self.show_graph()
        self.let_dt_approach_zero()
        self.show_confusion()

    def write_integral_condition(self):
        words = TextMobject(
            "Hard problem $\\Rightarrow$ Sum of many small values"
        )
        words.to_edge(UP)

        self.play(
            Write(words),
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.wait()

        self.words = words

    def show_car(self):
        car = Car()
        start, end = 3*LEFT+UP, 5*RIGHT+UP
        car.move_to(start)

        line = Line(start, end)
        tick_height = MED_SMALL_BUFF
        ticks = VGroup(*[
            Line(
                p+tick_height*UP/2,
                p+tick_height*DOWN/2,
                color = YELLOW,
                stroke_width = 2
            )
            for t in np.arange(0, self.t_max, self.dt)
            for p in [
                line.point_from_proportion(smooth(t/self.t_max))
            ]
        ])

        index = int(len(ticks)/2)
        brace_ticks = VGroup(*ticks[index:index+2])
        brace = Brace(brace_ticks, UP)
        v_dt = TexMobject("v(t)", "dt")
        v_dt.next_to(brace, UP, SMALL_BUFF)
        v_dt.set_color(YELLOW)
        v_dt_brace_group = VGroup(brace, v_dt)

        self.play(
            FadeIn(car),
            self.pi_creature.change_mode, "plain"
        )
        self.play(
            MoveCar(car, end),
            FadeIn(
                ticks, 
                lag_ratio=1,
                rate_func=linear,
            ),
            ShowCreation(line),
            FadeIn(
                v_dt_brace_group,
                rate_func = squish_rate_func(smooth, 0.6, 0.8)
            ),
            run_time = self.t_max
        )
        self.wait()
        for mob in v_dt:
            self.play(Indicate(mob))
            self.wait(2)

        self.v_dt_brace_group = v_dt_brace_group
        self.line = line
        self.ticks = ticks
        self.car = car

    def show_graph(self):
        self.setup_axes()
        self.remove(self.axes)
        s_graph = self.get_graph(
            lambda t : 1.8*self.y_max*smooth(t/self.t_max)
        )
        v_graph = self.get_derivative_graph(s_graph)
        rects = self.get_riemann_rectangles(
            v_graph,
            x_min = 0, 
            x_max = self.t_max,
            dx = self.dt
        )
        rects.set_fill(opacity = 0.5)
        pre_rects = rects.copy()
        pre_rects.rotate(-np.pi/2)
        for index, pre_rect in enumerate(pre_rects):
            ti1 = len(self.ticks)*index/len(pre_rects)
            ti2 = min(ti1+1, len(self.ticks)-1)
            tick_pair = VGroup(self.ticks[ti1], self.ticks[ti2])
            pre_rect.stretch_to_fit_width(tick_pair.get_width())
            pre_rect.move_to(tick_pair)

        special_rect = rects[int(0.6*len(rects))]
        brace = Brace(special_rect, LEFT, buff = 0)

        v_dt_brace_group_copy = self.v_dt_brace_group.copy()
        start_brace, (v_t, dt) = v_dt_brace_group_copy

        self.play(
            FadeIn(
                pre_rects, 
                run_time = 2,
                lag_ratio = 0.5
            ),
            Animation(self.ticks)
        )
        self.play(
            ReplacementTransform(
                pre_rects, rects,
                run_time = 3,
                lag_ratio = 0.5
            ),
            Animation(self.ticks),
            Write(self.axes, run_time = 1)
        )
        self.play(ShowCreation(v_graph))
        self.change_mode("pondering")
        self.wait()
        self.play(
            v_t.next_to, brace, LEFT, SMALL_BUFF,
            dt.next_to, special_rect, DOWN,
            special_rect.set_fill, None, 1,
            ReplacementTransform(start_brace, brace),
        )
        self.wait(3)

        self.v_graph = v_graph
        self.rects = rects
        self.v_dt_brace_group_copy = v_dt_brace_group_copy

    def let_dt_approach_zero(self):
        thinner_rects_list = [
            self.get_riemann_rectangles(
                self.v_graph,
                x_min = 0,
                x_max = self.t_max,
                dx = self.dt/(2**n),
                stroke_width = 1./(2**n)
            )
            for n in range(1, 4)
        ]

        self.play(
            self.rects.set_fill, None, 1,
            Animation(self.x_axis),
            FadeOut(self.v_dt_brace_group_copy),
        )
        self.change_mode("thinking")
        self.wait()
        for thinner_rects in thinner_rects_list:
            self.play(
                Transform(
                    self.rects, thinner_rects,
                    run_time = 2,
                    lag_ratio = 0.5
                )
            )
            self.wait()

    def show_confusion(self):
        randy = Randolph(color = BLUE_C)
        randy.to_corner(DOWN+LEFT)
        randy.to_edge(LEFT, buff = MED_SMALL_BUFF)

        self.play(FadeIn(randy))
        self.play(
            randy.change_mode, "confused",
            randy.look_at, self.rects
        )
        self.play(
            self.pi_creature.change_mode, "confused",
            self.pi_creature.look_at, randy.eyes
        )
        self.play(Blink(randy))
        self.wait()

class MathematicianPonderingAreaUnderDifferentCurves(PiCreatureScene):
    def construct(self):
        self.play(
            self.pi_creature.change_mode, "raise_left_hand",
            self.pi_creature.look, UP+LEFT
        )
        self.wait(4)
        self.play(
            self.pi_creature.change_mode, "raise_right_hand",
            self.pi_creature.look, UP+RIGHT
        )
        self.wait(4)
        self.play(
            self.pi_creature.change_mode, "pondering",
            self.pi_creature.look, UP+LEFT
        )
        self.wait(2)

    def create_pi_creature(self):
        self.pi_creature = Randolph(color = BLUE_C)
        self.pi_creature.to_edge(DOWN)
        return self.pi_creature

class AreaUnderParabola(GraphScene):
    CONFIG = {
        "x_max" : 4,
        "x_labeled_nums" : list(range(-1, 5)),
        "y_min" : 0,
        "y_max" : 15,
        "y_tick_frequency" : 2.5,
        "y_labeled_nums" : list(range(5, 20, 5)),
        "n_rect_iterations" : 6,
        "default_right_x" : 3,
        "func" : lambda x : x**2,
        "graph_label_tex" : "x^2",
        "graph_label_x_val" : 3.8,
    }
    def construct(self):
        self.setup_axes()
        self.show_graph()
        self.show_area()
        self.ask_about_area()
        self.show_confusion()
        self.show_variable_endpoint()
        self.name_integral()

    def show_graph(self):
        graph = self.get_graph(self.func)
        graph_label = self.get_graph_label(
            graph, self.graph_label_tex, 
            direction = LEFT,
            x_val = self.graph_label_x_val,
        )

        self.play(ShowCreation(graph))
        self.play(Write(graph_label))
        self.wait()

        self.graph = graph
        self.graph_label = graph_label

    def show_area(self):
        dx_list = [0.25/(2**n) for n in range(self.n_rect_iterations)]
        rect_lists = [
            self.get_riemann_rectangles(
                self.graph,
                x_min = 0,
                x_max = self.default_right_x,
                dx = dx,
                stroke_width = 4*dx,
            )
            for dx in dx_list
        ]
        rects = rect_lists[0]
        foreground_mobjects = [self.axes, self.graph]

        self.play(
            DrawBorderThenFill(
                rects,
                run_time = 2,
                rate_func = smooth,
                lag_ratio = 0.5,
            ),
            *list(map(Animation, foreground_mobjects))
        )
        self.wait()
        for new_rects in rect_lists[1:]:
            self.play(
                Transform(
                    rects, new_rects,
                    lag_ratio = 0.5,
                ), 
                *list(map(Animation, foreground_mobjects))
            )
        self.wait()

        self.rects = rects
        self.dx = dx_list[-1]
        self.foreground_mobjects = foreground_mobjects

    def ask_about_area(self):
        rects = self.rects
        question = TextMobject("Area?")
        question.move_to(rects.get_top(), DOWN)
        mid_rect = rects[2*len(rects)/3]
        arrow = Arrow(question.get_bottom(), mid_rect.get_center())

        v_lines = VGroup(*[
            DashedLine(
                FRAME_HEIGHT*UP, ORIGIN,
                color = RED
            ).move_to(self.coords_to_point(x, 0), DOWN)
            for x in (0, self.default_right_x)
        ])

        self.play(
            Write(question),
            ShowCreation(arrow)
        )
        self.wait()
        self.play(ShowCreation(v_lines, run_time = 2))
        self.wait()

        self.foreground_mobjects += [question, arrow]
        self.question = question
        self.question_arrow = arrow
        self.v_lines = v_lines

    def show_confusion(self):
        morty = Mortimer()
        morty.to_corner(DOWN+RIGHT)

        self.play(FadeIn(morty))
        self.play(
            morty.change_mode, "confused",
            morty.look_at, self.question,
        )
        self.play(morty.look_at, self.rects.get_bottom())
        self.play(Blink(morty))
        self.play(morty.look_at, self.question)
        self.wait()
        self.play(Blink(morty))
        self.play(FadeOut(morty))

    def show_variable_endpoint(self):
        triangle = RegularPolygon(
            n = 3,
            start_angle = np.pi/2,
            stroke_width = 0,
            fill_color = WHITE,
            fill_opacity = 1,
        )
        triangle.set_height(0.25)
        triangle.move_to(self.v_lines[1].get_bottom(), UP)
        x_label = TexMobject("x")
        x_label.next_to(triangle, DOWN)
        self.right_point_slider = VGroup(triangle, x_label)

        A_func = TexMobject("A(x)")
        A_func.move_to(self.question, DOWN)

        self.play(FadeOut(self.x_axis.numbers))
        self.x_axis.remove(*self.x_axis.numbers)
        self.foreground_mobjects.remove(self.axes)
        self.play(DrawBorderThenFill(self.right_point_slider))
        self.move_right_point_to(2)
        self.wait()
        self.move_right_point_to(self.default_right_x)
        self.wait()
        self.play(ReplacementTransform(self.question, A_func))
        self.wait()

        self.A_func = A_func

    def name_integral(self):
        f_tex = "$%s$"%self.graph_label_tex
        words = TextMobject("``Integral'' of ", f_tex)
        words.set_color_by_tex(f_tex, self.graph_label.get_color())
        brace = Brace(self.A_func, UP)
        words.next_to(brace, UP)

        self.play(
            Write(words),
            GrowFromCenter(brace)
        )
        self.wait()
        for x in 4, 2, self.default_right_x:
            self.move_right_point_to(x, run_time = 2)

        self.integral_words_group = VGroup(brace, words)

    ####

    def move_right_point_to(self, target_x, **kwargs):
        v_line = self.v_lines[1]
        slider = self.right_point_slider
        rects = self.rects
        curr_x = self.x_axis.point_to_number(v_line.get_bottom())

        group = VGroup(rects, v_line, slider)
        def update_group(group, alpha):
            rects, v_line, slider = group
            new_x = interpolate(curr_x, target_x, alpha)
            new_rects = self.get_riemann_rectangles(
                self.graph,
                x_min = 0,
                x_max = new_x,
                dx = self.dx*new_x/3.0,
                stroke_width = rects[0].get_stroke_width(),
            )
            point = self.coords_to_point(new_x, 0)
            v_line.move_to(point, DOWN)
            slider.move_to(point, UP)
            Transform(rects, new_rects).update(1)
            return VGroup(rects, v_line, slider)

        self.play(
            UpdateFromAlphaFunc(
                group, update_group,
                **kwargs
            ),
            *list(map(Animation, self.foreground_mobjects))
        )

class WhoCaresAboutArea(TeacherStudentsScene):
    def construct(self):
        point = 2*RIGHT+3*UP

        self.student_says(
            "Who cares!?!", target_mode = "angry",
        )
        self.play(self.teacher.change_mode, "guilty")
        self.wait()
        self.play(
            RemovePiCreatureBubble(self.students[1]),
            self.teacher.change_mode, "raise_right_hand",
            self.teacher.look_at, point
        )
        self.change_student_modes(
            *["pondering"]*3, 
            look_at_arg = point,
            added_anims = [self.teacher.look_at, point]

        )
        self.wait(3)

class PlayWithThisIdea(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Play with", "the", "thought!",
            target_mode = "hooray"
        )
        self.change_student_modes(*["happy"]*3)
        self.wait()
        equation = TexMobject("A(x)", "\\leftrightarrow", "x^2")
        equation.set_color_by_tex("x^2", BLUE)
        self.teacher_says(equation, target_mode = "sassy")
        self.change_student_modes(*["thinking"]*3)
        self.wait(2)

class PlayingTowardsDADX(AreaUnderParabola, ReconfigurableScene):
    CONFIG = {
        "n_rect_iterations" : 6,
        "deriv_dx" : 0.2,
        "graph_origin" : 2.5*DOWN + 6*LEFT,
    }
    def setup(self):
        AreaUnderParabola.setup(self)
        ReconfigurableScene.setup(self)

    def construct(self):
        self.fast_forward_to_end_of_previous_scene()

        self.nudge_x()
        self.describe_sliver()
        self.shrink_dx()
        self.write_dA_dx()
        self.dA_remains_a_mystery()
        self.write_example_inputs()
        self.show_dA_dx_in_detail()
        self.show_smaller_x()

    def fast_forward_to_end_of_previous_scene(self):
        self.force_skipping()
        AreaUnderParabola.construct(self)
        self.revert_to_original_skipping_status()

    def nudge_x(self):
        shadow_rects = self.rects.copy()
        shadow_rects.set_fill(BLACK, opacity = 0.5)

        original_v_line = self.v_lines[1].copy()
        right_v_lines = VGroup(original_v_line, self.v_lines[1])
        curr_x = self.x_axis.point_to_number(original_v_line.get_bottom())

        self.add(original_v_line)
        self.foreground_mobjects.append(original_v_line)
        self.move_right_point_to(curr_x + self.deriv_dx)
        self.play(
            FadeIn(shadow_rects), 
            *list(map(Animation, self.foreground_mobjects))
        )

        self.shadow_rects = shadow_rects
        self.right_v_lines = right_v_lines

    def describe_sliver(self):
        dx_brace = Brace(self.right_v_lines, DOWN, buff = 0)
        dx_label = dx_brace.get_text("$dx$")
        dx_group = VGroup(dx_brace, dx_label)

        dA_rect = Rectangle(
            width = self.right_v_lines.get_width(),
            height = self.shadow_rects[-1].get_height(),
            stroke_width = 0,
            fill_color = YELLOW,
            fill_opacity = 0.5,
        ).move_to(self.right_v_lines, DOWN)
        dA_label = TexMobject("d", "A")
        dA_label.next_to(dA_rect, RIGHT, MED_LARGE_BUFF, UP)
        dA_label.set_color(GREEN)
        dA_arrow = Arrow(
            dA_label.get_bottom()+MED_SMALL_BUFF*DOWN, 
            dA_rect.get_center(),
            buff = 0,
            color = WHITE
        )

        difference_in_area = TextMobject(
            "d", "ifference in ", "A", "rea",
            arg_separator = ""
        )
        difference_in_area.set_color_by_tex("d", GREEN)
        difference_in_area.set_color_by_tex("A", GREEN)
        difference_in_area.scale(0.7)
        difference_in_area.next_to(dA_label, UP, MED_SMALL_BUFF, LEFT)

        side_brace = Brace(dA_rect, LEFT, buff = 0)
        graph_label_copy = self.graph_label.copy()

        self.play(
            FadeOut(self.right_point_slider),
            FadeIn(dx_group)
        )
        self.play(Indicate(dx_label))
        self.wait()
        self.play(ShowCreation(dA_arrow))
        self.wait()
        self.play(Write(dA_label, run_time = 2))
        self.wait()
        self.play(
            ReplacementTransform(dA_label[0].copy(), difference_in_area[0]),
            ReplacementTransform(dA_label[1].copy(), difference_in_area[2]),
            *list(map(FadeIn, [difference_in_area[1], difference_in_area[3]]))
        )
        self.wait(2)
        self.play(FadeIn(dA_rect), Animation(dA_arrow))
        self.play(GrowFromCenter(side_brace))
        self.play(
            graph_label_copy.set_color, WHITE,
            graph_label_copy.next_to, side_brace, LEFT, SMALL_BUFF
        )
        self.wait()
        self.play(Indicate(dx_group))
        self.wait()
        self.play(FadeOut(difference_in_area))

        self.dx_group = dx_group
        self.dA_rect = dA_rect
        self.dA_label = dA_label
        self.graph_label_copy = graph_label_copy

    def shrink_dx(self, **kwargs):
        self.transition_to_alt_config(
            deriv_dx = 0.05,
            transformation_kwargs = {"run_time" : 2},
            **kwargs
        )

    def write_dA_dx(self):
        f_tex = self.graph_label_tex
        equation = TexMobject("dA", "\\approx", f_tex, "dx")
        equation.to_edge(RIGHT).shift(3*UP)
        deriv_equation = TexMobject(
            "{dA", "\\over \\,", "dx}", "\\approx", f_tex
        )
        deriv_equation.move_to(equation, UP+LEFT)

        for tex_mob in equation, deriv_equation:
            tex_mob.set_color_by_tex(
                "dA", self.dA_label.get_color()
            )

        dA = VGroup(self.dA_label[0][0], self.dA_label[1][0])
        x_squared = self.graph_label_copy
        dx = self.dx_group[1]

        self.play(*[
            ReplacementTransform(
                mob.copy(),
                equation.get_part_by_tex(tex),
                run_time = 2
            )
            for mob, tex in [(x_squared, f_tex), (dx, "dx"), (dA, "dA")]
        ])
        self.play(Write(equation.get_part_by_tex("approx")))
        self.wait()
        for tex, mob in (f_tex, x_squared), ("dx", dx):
            self.play(*list(map(Indicate, [
                equation.get_part_by_tex(tex),
                mob
            ])))
            self.wait(2)
        self.play(*[
            ReplacementTransform(
                equation.get_part_by_tex(tex),
                deriv_equation.get_part_by_tex(tex),
                run_time = 2,
            )
            for tex in ("dA", "approx", f_tex, "dx")
        ] + [
            Write(deriv_equation.get_part_by_tex("over"))
        ])
        self.wait(2)
        self.shrink_dx(return_to_original_configuration = False)
        self.wait()

        self.deriv_equation = deriv_equation

    def dA_remains_a_mystery(self):
        randy = Randolph(color = BLUE_C)
        randy.to_corner(DOWN+LEFT)
        randy.look_at(self.A_func)

        A_circle, dA_circle = [
            Circle(color = color).replace(
                mob, stretch = True
            ).scale_in_place(1.5)
            for mob, color in [(self.A_func, RED), (self.deriv_equation, GREEN)]
        ]
        q_marks = TexMobject("???")
        q_marks.next_to(A_circle, UP)

        self.play(
            FadeOut(self.integral_words_group),
            FadeIn(randy)
        )
        self.play(
            ShowCreation(A_circle),
            randy.change_mode, "confused"
        )
        self.play(Write(q_marks, run_time = 2))
        self.play(Blink(randy))
        self.wait()
        self.play(
            randy.change_mode, "surprised",
            randy.look_at, dA_circle,
            ReplacementTransform(A_circle, dA_circle)
        )
        self.play(Blink(randy))
        self.wait()
        self.play(*list(map(FadeOut, [randy, q_marks, dA_circle])))

    def write_example_inputs(self):
        d = self.default_right_x
        three = TexMobject("x =", "%d"%d)
        three_plus_dx = TexMobject("x = ", "%d.001"%d)
        labels_lines_vects = list(zip(
            [three, three_plus_dx],
            self.right_v_lines,
            [LEFT, RIGHT]
        ))

        for label, line, vect in labels_lines_vects:
            point = line.get_bottom()
            label.next_to(point, DOWN+vect, MED_SMALL_BUFF)
            label.shift(LARGE_BUFF*vect)
            label.arrow = Arrow(
                label, point,
                buff = SMALL_BUFF,
                color = WHITE,
                tip_length = 0.15
            )
            line_copy = line.copy()
            line_copy.set_color(YELLOW)

            self.play(
                FadeIn(label),
                FadeIn(label.arrow),
                ShowCreation(line_copy)
            )
            self.play(FadeOut(line_copy))
        self.wait()

        self.three = three 
        self.three_plus_dx = three_plus_dx

    def show_dA_dx_in_detail(self):
        d = self.default_right_x
        expression = TexMobject(
            "{A(", "%d.001"%d, ") ", "-A(", "%d"%d, ")", 
            "\\over \\,", "0.001}", 
            "\\approx", "%d"%d, "^2"
        )
        expression.scale(0.9)
        expression.next_to(
            self.deriv_equation, DOWN, MED_LARGE_BUFF
        )
        expression.to_edge(RIGHT)

        self.play(
            ReplacementTransform(
                self.three_plus_dx.get_part_by_tex("%d.001"%d).copy(),
                expression.get_part_by_tex("%d.001"%d)
            ),
            Write(VGroup(
                expression.get_part_by_tex("A("),
                expression.get_part_by_tex(")"),
            )),
        )
        self.wait()
        self.play(
            ReplacementTransform(
                self.three.get_part_by_tex("%d"%d).copy(),
                expression.get_part_by_tex("%d"%d, substring = False)
            ),
            Write(VGroup(
                expression.get_part_by_tex("-A("),
                expression.get_parts_by_tex(")")[1],
            )),
        )
        self.wait(2)
        self.play(
            Write(expression.get_part_by_tex("over")),
            ReplacementTransform(
                expression.get_part_by_tex("%d.001"%d).copy(),
                expression.get_part_by_tex("0.001"),
            )
            
        )
        self.wait()
        self.play(
            Write(expression.get_part_by_tex("approx")),
            ReplacementTransform(
                self.graph_label_copy.copy(),
                VGroup(*expression[-2:]),
                run_time = 2
            )
        )
        self.wait()

    def show_smaller_x(self):
        self.transition_to_alt_config(
            default_right_x = 2,
            deriv_dx = 0.04,
            transformation_kwargs = {"run_time" : 2}
        )

class AlternateAreaUnderCurve(PlayingTowardsDADX):
    CONFIG = {
        "func" : lambda x : (x-2)**3 - 3*(x-2) + 6,
        "graph_label_tex" : "f(x)",
        "deriv_dx" : 0.1,
        "x_max" : 5,
        "x_axis_width" : 11,
        "graph_label_x_val" : 4.5,
    }
    def construct(self):
        #Superclass parts to skip
        self.force_skipping()
        self.setup_axes()
        self.show_graph()
        self.show_area()
        self.ask_about_area()
        self.show_confusion()

        #Superclass parts to show
        self.revert_to_original_skipping_status()
        self.show_variable_endpoint()
        self.name_integral()
        self.nudge_x()
        self.describe_sliver()
        self.write_dA_dx()

        #New animations
        self.approximation_improves_for_smaller_dx()
        self.name_derivative()

    def approximation_improves_for_smaller_dx(self):
        color = YELLOW
        approx = self.deriv_equation.get_part_by_tex("approx")
        dx_to_zero_words = TextMobject(
            "Gets better \\\\ as", 
            "$dx \\to 0$"
        )
        dx_to_zero_words.set_color_by_tex("dx", color)
        dx_to_zero_words.next_to(approx, DOWN, 1.5*LARGE_BUFF)
        arrow = Arrow(dx_to_zero_words, approx, color = color)

        self.play(
            approx.set_color, color,
            ShowCreation(arrow),
            FadeIn(dx_to_zero_words),
        )
        self.wait()
        self.transition_to_alt_config(
            deriv_dx = self.deriv_dx/4.0,
            transformation_kwargs = {"run_time" : 2}
        )

        self.dx_to_zero_words = dx_to_zero_words
        self.dx_to_zero_words_arrow = arrow

    def name_derivative(self):
        deriv_words = TextMobject("``Derivative'' of $A$")
        deriv_words.scale(0.9)
        deriv_words.to_edge(UP+RIGHT)
        moving_group = VGroup(
            self.deriv_equation, 
            self.dx_to_zero_words,
            self.dx_to_zero_words_arrow,
        )
        moving_group.generate_target()
        moving_group.target.next_to(deriv_words, DOWN, LARGE_BUFF)
        moving_group.target.to_edge(RIGHT)

        self.play(
            FadeIn(deriv_words),
            MoveToTarget(moving_group)
        )

        dA_dx = VGroup(*self.deriv_equation[:3])
        box = Rectangle(color = GREEN)
        box.replace(dA_dx, stretch = True)
        box.scale_in_place(1.3)
        brace = Brace(box, UP)
        faders = VGroup(
            self.dx_to_zero_words[0],
            self.dx_to_zero_words_arrow
        )
        dx_to_zero = self.dx_to_zero_words[1]

        self.play(*list(map(FadeIn, [box, brace])))
        self.wait()
        self.play(
            FadeOut(faders),
            dx_to_zero.next_to, box, DOWN
        )
        self.wait()


    ########
    def show_smaller_x(self):
        return

    def shrink_dx(self, **kwargs):
        return

class NextVideoWrapper(Scene):
    def construct(self):
        rect = Rectangle(height = 9, width = 16)
        rect.set_height(1.5*FRAME_Y_RADIUS)
        titles = [
            TextMobject("Chapter %d:"%d, s)
            for d, s in [
                (2, "The paradox of the derivative"),
                (3, "Derivative formulas through geometry"),
            ]
        ]
        for title in titles:
            title.to_edge(UP)
        rect.next_to(VGroup(*titles), DOWN)

        self.add(titles[0])
        self.play(ShowCreation(rect))
        self.wait(3)
        self.play(Transform(*titles))
        self.wait(3)

class ProblemSolvingTool(TeacherStudentsScene):
    def construct(self):
        self.teacher_says("""
            The derivative is a
            problem-solving tool
        """)
        self.wait(3)

class FundamentalTheorem(Scene):
    def construct(self):
        words = TextMobject("""
            Fundamental theorem of calculus
        """)
        words.to_edge(UP)
        arrow = DoubleArrow(LEFT, RIGHT).shift(2*RIGHT)
        deriv = TexMobject(
            "{dA", "\\over \\,", "dx}", "=", "x^2"
        )
        deriv.set_color_by_tex("dA", GREEN)
        deriv.next_to(arrow, RIGHT)

        self.play(ShowCreation(arrow))
        self.wait()
        self.play(Write(deriv))
        self.wait()
        self.play(Write(words))
        self.wait()

class NextVideos(TeacherStudentsScene):
    def construct(self):
        series = VideoSeries()
        series.to_edge(UP)
        this_video = series[0]
        this_video.set_color(YELLOW)

        self.add(series)
        self.teacher_says(
            "That's a high-level view"
        )
        self.wait()
        self.play(
            RemovePiCreatureBubble(
                self.teacher,
                target_mode = "raise_right_hand",
                look_at_arg = this_video,
            ),
            *it.chain(*[
                [pi.change_mode, "pondering", pi.look_at, this_video]
                for pi in self.get_students()
            ])
        )
        self.play(*[
            ApplyMethod(pi.look_at, series)
            for pi in self.get_pi_creatures()
        ])
        self.play(*[
            ApplyMethod(
                video.shift, 0.5*video.get_height()*DOWN,
                run_time = 3,
                rate_func = squish_rate_func(
                    there_and_back, alpha, alpha+0.3
                )
            )
            for video, alpha in zip(series, np.linspace(0, 0.7, len(series)))
        ])
        self.wait()

        student = self.get_students()[1]
        self.remove(student)
        everything = VGroup(*self.get_top_level_mobjects())
        self.add(student)
        words = TextMobject("""
            You could have
            invented this.
        """)
        words.next_to(student, UP, LARGE_BUFF)

        self.play(self.teacher.change_mode, "plain")
        self.play(
            everything.fade, 0.75,
            student.change_mode, "plain"
        )
        self.play(
            Write(words),
            student.look_at, words,
        )
        self.play(
            student.change_mode, "confused",
            student.look_at, words
        )
        self.wait(3)
        self.play(student.change_mode, "thinking")
        self.wait(4)

class Chapter1PatreonThanks(PatreonThanks):
    CONFIG = {
        "specific_patrons" : [
            "Ali Yahya",
            "CrypticSwarm",
            "Juan    Benet",
            "Yu  Jun",
            "Othman  Alikhan",
            "Markus  Persson",
            "Joseph  John Cox",
            "Luc Ritchie",
            "Einar Johansen",
            "Rish    Kundalia",
            "Achille Brighton",
            "Kirk Werklund",
            "Ripta   Pasay",
            "Felipe  Diniz",
        ],
        "patron_scale_val" : 0.9
    }

class EndScreen(PiCreatureScene):
    CONFIG = {
        "seconds_to_blink" : 3,
    }
    def construct(self):
        words = TextMobject("Clicky stuffs")
        words.scale(1.5)
        words.next_to(self.pi_creature, UP)
        words.to_edge(UP)

        self.play(
            FadeIn(
                words, 
                run_time = 2, 
                lag_ratio = 0.5
            ),
            self.pi_creature.change_mode, "hooray"
        )
        self.wait()
        mode_point_pairs = [
            ("raise_left_hand", 5*LEFT+3*UP),
            ("raise_right_hand", 5*RIGHT+3*UP),
            ("thinking", 5*LEFT+2*DOWN),
            ("thinking", 5*RIGHT+2*DOWN),
            ("thinking", 5*RIGHT+2*DOWN),
            ("happy", 5*LEFT+3*UP),
            ("raise_right_hand", 5*RIGHT+3*UP),
        ]
        for mode, point in mode_point_pairs:
            self.play(self.pi_creature.change, mode, point)
            self.wait()
        self.wait(3)


    def create_pi_creature(self):
        self.pi_creature = Randolph()
        self.pi_creature.shift(2*DOWN + 1.5*LEFT)
        return self.pi_creature

class Thumbnail(AlternateAreaUnderCurve):
    CONFIG = {
        "x_axis_label" : "",
        "y_axis_label" : "",
        "graph_origin" : 2.4*DOWN + 3*LEFT,
    }
    def construct(self):
        self.setup_axes()
        self.remove(*self.x_axis.numbers)
        self.remove(*self.y_axis.numbers)
        graph = self.get_graph(self.func)
        rects = self.get_riemann_rectangles(
            graph,
            x_min = 0,
            x_max = 4,
            dx = 0.25,
            start_color = BLUE_E,
        )
        words = TextMobject("""
            Essence of
            calculus
        """)
        words.set_width(9)
        words.to_edge(UP)

        self.add(graph, rects, words)




















