import scipy
from big_ol_pile_of_manim_imports import *
from old_projects.eoc.chapter1 import Thumbnail as Chapter1Thumbnail
from old_projects.eoc.chapter2 import Car, MoveCar, ShowSpeedometer, \
    IncrementNumber, GraphCarTrajectory, SecantLineToTangentLine, \
    VELOCITY_COLOR, TIME_COLOR, DISTANCE_COLOR

def v_rate_func(t):
    return 4*t - 4*(t**2)

def s_rate_func(t):
    return 3*(t**2) - 2*(t**3)

def v_func(t):
    return t*(8-t)

def s_func(t):
    return 4*t**2 - (t**3)/3.


class Chapter8OpeningQuote(OpeningQuote, PiCreatureScene):
    CONFIG = {
        "quote" : [
            " One should never try to prove anything that \\\\ is not ",
            "almost obvious", ". "
        ],
        "quote_arg_separator" : "",
        "highlighted_quote_terms" : {
            "almost obvious" : BLUE,
        },
        "author" : "Alexander Grothendieck"
    }
    def construct(self):
        self.remove(self.pi_creature)
        OpeningQuote.construct(self)

        words_copy = self.quote.get_part_by_tex("obvious").copy()
        author = self.author
        author.save_state()
        formula = self.get_formula()
        formula.next_to(author, DOWN, MED_LARGE_BUFF)
        formula.to_edge(LEFT)

        self.revert_to_original_skipping_status()
        self.play(FadeIn(self.pi_creature))
        self.play(
            author.next_to, self.pi_creature.get_corner(UP+LEFT), UP,
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.wait(3)
        self.play(
            author.restore,
            self.pi_creature.change_mode, "plain"
        )
        self.play(
            words_copy.next_to, self.pi_creature, 
                LEFT, MED_SMALL_BUFF, UP,
            self.pi_creature.change_mode, "thinking"
        )
        self.wait(2)
        self.play(
            Write(formula),
            self.pi_creature.change_mode, "confused"
        )
        self.wait()

    def get_formula(self):
        result = TexMobject(
            "{d(\\sin(\\theta)) \\over \\,", "d\\theta}", "=",
            "\\lim_{", "h", " \\to 0}", 
            "{\\sin(\\theta+", "h", ") - \\sin(\\theta) \\over", " h}", "=",
            "\\lim_{", "h", " \\to 0}", 
            "{\\big[ \\sin(\\theta)\\cos(", "h", ") + ",
            "\\sin(", "h", ")\\cos(\\theta)\\big] - \\sin(\\theta) \\over", "h}",
            "= \\dots"
        )
        result.set_color_by_tex("h", GREEN, substring = False)
        result.set_color_by_tex("d\\theta", GREEN)

        result.set_width(FRAME_WIDTH - 2*MED_SMALL_BUFF)
        return result

class ThisVideo(TeacherStudentsScene):
    def construct(self):
        series = VideoSeries()
        series.to_edge(UP)
        this_video = series[7]
        this_video.save_state()
        next_video = series[8]

        deriv, integral, v_t, dt, equals, v_T = formula = TexMobject(
            "\\frac{d}{dT}", 
            "\\int_0^T", "v(t)", "\\,dt", 
            "=", "v(T)"
        )
        formula.set_color_by_tex("v", VELOCITY_COLOR)
        formula.next_to(self.teacher.get_corner(UP+LEFT), UP, MED_LARGE_BUFF)

        self.play(FadeIn(series, lag_ratio = 0.5))
        self.play(
            this_video.shift, this_video.get_height()*DOWN/2,
            this_video.set_color, YELLOW,
            self.teacher.change_mode, "raise_right_hand",
        )
        self.play(Write(VGroup(integral, v_t, dt)))
        self.change_student_modes(*["erm"]*3)
        self.wait()
        self.play(Write(VGroup(deriv, equals, v_T)), )
        self.change_student_modes(*["confused"]*3)
        self.wait(3)
        self.play(
            this_video.restore,
            next_video.shift, next_video.get_height()*DOWN/2,
            next_video.set_color, YELLOW,
            integral[0].copy().next_to, next_video, DOWN, MED_LARGE_BUFF,
            FadeOut(formula),
            *it.chain(*[
                [pi.change_mode, "plain", pi.look_at, next_video]
                for pi in self.pi_creatures
            ])
        )
        self.wait(2)

class InCarRestrictedView(ShowSpeedometer):
    CONFIG = {
        "speedometer_title_text" : "Your view",
    }
    def construct(self):
        car = Car()
        car.move_to(self.point_A)
        self.car = car
        car.randy.save_state()
        Transform(car.randy, Randolph()).update(1)
        car.randy.next_to(car, RIGHT, MED_LARGE_BUFF)
        car.randy.look_at(car)

        window = car[1][6].copy()
        window.is_subpath = False
        window.set_fill(BLACK, opacity = 0.75)
        window.set_stroke(width = 0)

        square = Square(stroke_color = WHITE)
        square.replace(VGroup(self.speedometer, self.speedometer_title))
        square.scale_in_place(1.5)
        square.pointwise_become_partial(square, 0.25, 0.75)

        time_label = TextMobject("Time (in seconds):", "0")
        time_label.shift(2*UP)

        dots = VGroup(*list(map(Dot, [self.point_A, self.point_B])))
        line = Line(*dots, buff = 0)
        line.set_color(DISTANCE_COLOR)
        brace = Brace(line, DOWN)
        brace_text = brace.get_text("Distance traveled?")


        #Sit in car
        self.add(car)
        self.play(Blink(car.randy))
        self.play(car.randy.restore, Animation(car))
        self.play(ShowCreation(window, run_time = 2))
        self.wait()

        #Show speedometer
        self.introduce_added_mobjects()
        self.play(ShowCreation(square))
        self.wait()

        #Travel
        self.play(FadeIn(time_label))
        self.play(
            MoveCar(car, self.point_B, rate_func = s_rate_func),
            IncrementNumber(time_label[1], run_time = 8),
            MaintainPositionRelativeTo(window, car),
            *self.get_added_movement_anims(
                rate_func = v_rate_func,
                radians = -(16.0/70)*4*np.pi/3
            ),
            run_time = 8
        )
        eight = TexMobject("8").move_to(time_label[1])
        self.play(Transform(
            time_label[1], eight,
            rate_func = squish_rate_func(smooth, 0, 0.5)
        ))
        self.wait()

        #Ask about distance
        self.play(*list(map(ShowCreation, dots)))
        self.play(ShowCreation(line))
        self.play(
            GrowFromCenter(brace),
            Write(brace_text)
        )
        self.wait(2)

class GraphDistanceVsTime(GraphCarTrajectory):
    CONFIG = {
        "y_min" : 0,
        "y_max" : 100,
        "y_axis_height" : 6,
        "y_tick_frequency" : 10,
        "y_labeled_nums" : list(range(10, 100, 10)),
        "y_axis_label" : "Distance (in meters)",
        "x_min" : -1,
        "x_max" : 9,
        "x_axis_width" : 9,
        "x_tick_frequency" : 1,
        "x_leftmost_tick" : None, #Change if different from x_min
        "x_labeled_nums" : list(range(1, 9)),
        "x_axis_label" : "$t$",
        "time_of_journey" : 8,
        "care_movement_rate_func" : s_rate_func,
        "num_graph_anchor_points" : 100
    }
    def construct(self):
        self.setup_axes()
        graph = self.get_graph(
            s_func, 
            color = DISTANCE_COLOR,
            x_min = 0,
            x_max = 8,
        )
        origin = self.coords_to_point(0, 0)
        graph_label = self.get_graph_label(
            graph, "s(t)", color = DISTANCE_COLOR
        )
        self.introduce_graph(graph, origin)

class PlotVelocity(GraphScene):
    CONFIG = {
        "x_min" : -1,
        "x_max" : 9,
        "x_axis_width" : 9,
        "x_tick_frequency" : 1,
        "x_labeled_nums" : list(range(1, 9)),
        "x_axis_label" : "$t$",
        "y_min" : 0,
        "y_max" : 25,
        "y_axis_height" : 6,
        "y_tick_frequency" : 5,
        "y_labeled_nums" : list(range(5, 30, 5)),
        "y_axis_label" : "Velocity in $\\frac{\\text{meters}}{\\text{second}}$",
        "num_graph_anchor_points" : 50,
    }
    def construct(self):
        self.setup_axes()
        self.add_speedometer()
        self.plot_points()
        self.draw_curve()

    def add_speedometer(self):
        speedometer = Speedometer()
        speedometer.next_to(self.y_axis_label_mob, RIGHT, LARGE_BUFF)
        speedometer.to_edge(UP)

        self.play(DrawBorderThenFill(
            speedometer, 
            lag_ratio = 0.5,
            rate_func=linear,
        ))

        self.speedometer = speedometer

    def plot_points(self):
        times = list(range(0, 9))
        points = [
            self.coords_to_point(t, v_func(t))
            for t in times
        ]
        dots = VGroup(*[Dot(p, radius = 0.07) for p in points])
        dots.set_color(VELOCITY_COLOR)

        pre_dots = VGroup()
        dot_intro_anims = []

        for time, dot in zip(times, dots):
            pre_dot = dot.copy()
            self.speedometer.move_needle_to_velocity(v_func(time))
            pre_dot.move_to(self.speedometer.get_needle_tip())
            pre_dot.set_fill(opacity = 0)
            pre_dots.add(pre_dot)
            dot_intro_anims += [
                ApplyMethod(
                    pre_dot.set_fill, YELLOW, 1,
                    run_time = 0.1,
                ),
                ReplacementTransform(
                    pre_dot, dot,
                    run_time = 0.9,
                )
            ]
        self.speedometer.move_needle_to_velocity(0)

        self.play(
            Succession(
                *dot_intro_anims, rate_func=linear
            ),
            ApplyMethod(
                self.speedometer.move_needle_to_velocity,
                v_func(4),
                rate_func = squish_rate_func(
                    lambda t : 1-v_rate_func(t),
                    0, 0.95,
                )
            ),
            run_time = 5
        )
        self.wait()

    def draw_curve(self):
        graph, label = self.get_v_graph_and_label()

        self.revert_to_original_skipping_status()
        self.play(ShowCreation(graph, run_time = 3))
        self.play(Write(graph_label))
        self.wait()

    ##

    def get_v_graph_and_label(self):
        graph = self.get_graph(
            v_func, 
            x_min = 0,
            x_max = 8,
            color = VELOCITY_COLOR
        )
        graph_label = TexMobject("v(t)", "=t(8-t)")
        graph_label.set_color_by_tex("v(t)", VELOCITY_COLOR)
        graph_label.next_to(
            graph.point_from_proportion(7./8.),
            UP+RIGHT
        )
        self.v_graph = graph
        self.v_graph_label = graph_label
        return graph, graph_label

class Chapter2Wrapper(Scene):
    CONFIG = {
        "title" : "Chapter 2: The paradox of the derivative",
    }
    def construct(self):
        title = TextMobject(self.title)
        title.to_edge(UP)
        rect = Rectangle(width = 16, height = 9, color = WHITE)
        rect.set_height(1.5*FRAME_Y_RADIUS)
        rect.next_to(title, DOWN)

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

class GivenDistanceWhatIsVelocity(GraphCarTrajectory):
    def construct(self):
        self.force_skipping()
        self.setup_axes()
        graph = self.graph_sigmoid_trajectory_function()
        origin = self.coords_to_point(0, 0)

        self.introduce_graph(graph, origin)
        self.comment_on_slope(graph, origin)
        self.revert_to_original_skipping_status()
        self.show_velocity_graph()

class DerivativeOfDistance(SecantLineToTangentLine):
    def construct(self):
        self.setup_axes()
        self.remove(self.y_axis_label_mob, self.x_axis_label_mob)
        self.add_derivative_definition(self.y_axis_label_mob)
        self.add_graph()
        self.draw_axes()
        self.show_tangent_line()

class AskAboutAntiderivative(PlotVelocity):
    def construct(self):
        self.setup_axes()
        self.add_v_graph()
        self.write_s_formula()
        self.write_antiderivative()


    def add_v_graph(self):
        graph, label = self.get_v_graph_and_label()
        self.play(ShowCreation(graph))
        self.play(Write(label))

        self.graph = graph
        self.graph_label = label

    def write_s_formula(self):
        ds_dt = TexMobject("ds", "\\over\\,", "dt")
        ds_dt.set_color_by_tex("ds", DISTANCE_COLOR)
        ds_dt.set_color_by_tex("dt", TIME_COLOR)
        ds_dt.next_to(self.graph_label, UP, LARGE_BUFF)

        v_t = self.graph_label.get_part_by_tex("v(t)")
        arrow = Arrow(
            ds_dt.get_bottom(), v_t.get_top(),
            color = WHITE,
        )

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

    def write_antiderivative(self):
        randy = Randolph()
        randy.to_corner(DOWN+LEFT)
        randy.shift(2*RIGHT)
        words = TexMobject(
            "{d(", "???", ") \\over \\,", "dt}", "=", "t(8-t)"
        )
        words.set_color_by_tex("t(8-t)", VELOCITY_COLOR)
        words.set_color_by_tex("???", DISTANCE_COLOR)
        words.set_color_by_tex("dt", TIME_COLOR)
        words.scale(0.7)

        self.play(FadeIn(randy))
        self.play(PiCreatureSays(
            randy, words, 
            target_mode = "confused",
            bubble_kwargs = {"height" : 3, "width" : 4},
        ))
        self.play(Blink(randy))
        self.wait()

class Antiderivative(PiCreatureScene):
    def construct(self):
        functions = self.get_functions("t^2", "2t")
        alt_functions = self.get_functions("???", "t(8-t)")
        top_arc, bottom_arc = arcs = self.get_arcs(functions)
        derivative, antiderivative = self.get_arc_labels(arcs)
        group = VGroup(functions, arcs, derivative, antiderivative)

        self.add(functions, top_arc, derivative)
        self.wait()
        self.play(
            ShowCreation(bottom_arc),
            Write(antiderivative),
            self.pi_creature.change_mode, "raise_right_hand"
        )
        self.wait(2)
        for pair in reversed(list(zip(functions, alt_functions))):
            self.play(
                Transform(*pair),
                self.pi_creature.change_mode, "pondering"
            )
        self.wait(2)

        self.pi_creature_says(
            "But first!", 
            target_mode = "surprised",
            look_at_arg = 50*OUT,
            added_anims = [group.to_edge, LEFT],
            run_time = 1,
        )
        self.wait()

    def get_functions(self, left_tex, right_tex):
        left = TexMobject(left_tex)
        left.shift(2*LEFT)
        left.set_color(DISTANCE_COLOR)
        right = TexMobject(right_tex)
        right.shift(2*RIGHT)
        right.set_color(VELOCITY_COLOR)
        result = VGroup(left, right)
        result.shift(UP)
        return result

    def get_arcs(self, functions):
        f1, f2 = functions
        top_line = Line(f1.get_corner(UP+RIGHT), f2.get_corner(UP+LEFT))
        bottom_line = Line(f1.get_corner(DOWN+RIGHT), f2.get_corner(DOWN+LEFT))
        top_arc = Arc(start_angle = 5*np.pi/6, angle = -2*np.pi/3)
        bottom_arc = top_arc.copy()
        bottom_arc.rotate(np.pi)
        arcs = VGroup(top_arc, bottom_arc)
        arcs.set_width(top_line.get_width())
        for arc in arcs:
            arc.add_tip()
        top_arc.next_to(top_line, UP)
        bottom_arc.next_to(bottom_line, DOWN)
        bottom_arc.set_color(MAROON_B)

        return arcs

    def get_arc_labels(self, arcs):
        top_arc, bottom_arc = arcs
        derivative = TextMobject("Derivative")
        derivative.next_to(top_arc, UP)
        antiderivative = TextMobject("``Antiderivative''")
        antiderivative.next_to(bottom_arc, DOWN)
        antiderivative.set_color(bottom_arc.get_color())

        return VGroup(derivative, antiderivative)

class AreaUnderVGraph(PlotVelocity):
    def construct(self):
        self.setup_axes()
        self.add(*self.get_v_graph_and_label())
        self.show_rects()

    def show_rects(self):
        rect_list = self.get_riemann_rectangles_list(
            self.v_graph, 7, 
            max_dx = 1.0,
            x_min = 0,
            x_max = 8,
        )
        flat_graph = self.get_graph(lambda t : 0)
        rects = self.get_riemann_rectangles(
            flat_graph, x_min = 0, x_max = 8, dx = 1.0
        )

        for new_rects in rect_list:
            new_rects.set_fill(opacity = 0.8)
            rects.align_submobjects(new_rects)
            for alt_rect in rects[::2]:
                alt_rect.set_fill(opacity = 0)
            self.play(Transform(
                rects, new_rects,
                run_time = 2,
                lag_ratio = 0.5
            ))
        self.wait()

class ConstantVelocityCar(Scene):
    def construct(self):
        car = Car()
        car.move_to(5*LEFT + 3*DOWN)

        self.add(car)
        self.wait()
        self.play(MoveCar(
            car, 7*RIGHT+3*DOWN,
            run_time = 5,
            rate_func=linear,
        ))
        self.wait()

class ConstantVelocityPlot(PlotVelocity):
    CONFIG = {
        "x_axis_label" : "Time",
        "units_of_area_color" : BLUE_E,
    }
    def construct(self):
        self.setup_axes()
        self.x_axis_label_mob.shift(DOWN)
        self.draw_graph()
        self.show_product()
        self.comment_on_area_wierdness()
        self.note_units()

    def draw_graph(self):
        graph = self.get_graph(
            lambda t : 10,
            x_min = 0, 
            x_max = 8,
            color = VELOCITY_COLOR
        )

        self.play(ShowCreation(graph, rate_func=linear, run_time = 3))
        self.wait()

        self.graph = graph

    def show_product(self):
        rect = Rectangle(
            stroke_width = 0,
            fill_color = DISTANCE_COLOR,
            fill_opacity = 0.5
        )
        rect.replace(
            VGroup(self.graph, VectorizedPoint(self.graph_origin)),
            stretch = True
        )

        right_brace = Brace(rect, RIGHT)
        top_brace = Brace(rect, UP)
        v_label = right_brace.get_text(
            "$10 \\frac{\\text{meters}}{\\text{second}}$",
        )
        v_label.set_color(VELOCITY_COLOR)
        t_label = top_brace.get_text(
            "8 seconds"
        )
        t_label.set_color(TIME_COLOR)

        s_label = TexMobject("10", "\\times",  "8", "\\text{ meters}")
        s_label.set_color_by_tex("10", VELOCITY_COLOR)
        s_label.set_color_by_tex("8", TIME_COLOR)
        s_label.move_to(rect)

        self.play(
            GrowFromCenter(right_brace),
            Write(v_label),
        )
        self.play(
            GrowFromCenter(top_brace),
            Write(t_label),
        )
        self.play(
            FadeIn(rect),
            Write(s_label),
            Animation(self.graph)
        )
        self.wait(2)

        self.area_rect = rect
        self.s_label = s_label

    def comment_on_area_wierdness(self):
        randy = Randolph()
        randy.to_corner(DOWN+LEFT)
        bubble = randy.get_bubble(
            "Distance \\\\ is area?",
            bubble_class = ThoughtBubble,
            height = 3,
            width = 4,
            fill_opacity = 1,
        )
        bubble.content.scale_in_place(0.8)
        bubble.content.shift(SMALL_BUFF*UP)
        VGroup(bubble[-1], bubble.content).shift(1.5*LEFT)

        self.play(FadeIn(randy))
        self.play(randy.change_mode, "pondering")
        self.play(
            self.area_rect.set_color, YELLOW,
            *list(map(Animation, self.get_mobjects())),
            rate_func = there_and_back
        )
        self.play(Blink(randy))
        self.play(
            randy.change_mode, "confused",
            randy.look_at, randy.bubble,
            ShowCreation(bubble), 
            Write(bubble.content),
        )
        self.wait()
        self.play(Blink(randy))
        self.wait()
        self.play(
            randy.change_mode, "pondering",
            FadeOut(bubble),
            FadeOut(bubble.content),
        )

        self.randy = randy

    def note_units(self):
        x_line, y_line  = lines = VGroup(*[
            axis.copy()
            for axis in (self.x_axis, self.y_axis)
        ])
        lines.set_color(TIME_COLOR)
        square = Square(
            stroke_color = BLACK,
            stroke_width = 1,
            fill_color = self.units_of_area_color,
            fill_opacity = 1,
        )
        square.replace(
            VGroup(*[
                VectorizedPoint(self.coords_to_point(i, i))
                for i in (0, 1)
            ]),
            stretch = True
        )
        units_of_area = VGroup(*[
            square.copy().move_to(
                self.coords_to_point(x, y),
                DOWN+LEFT
            )
            for x in range(8)
            for y in range(10)
        ])

        self.play(ShowCreation(x_line))
        self.play(Indicate(self.x_axis_label_mob))
        self.play(FadeOut(x_line))
        self.play(
            ShowCreation(y_line),
            self.randy.look_at, self.y_axis_label_mob
        )
        self.play(Indicate(self.y_axis_label_mob))
        self.play(FadeOut(y_line))

        for FadeClass in FadeIn, FadeOut:
            self.play(
                FadeClass(
                    units_of_area, 
                    lag_ratio = 0.5,
                    run_time = 3
                ),
                Animation(self.s_label),
                self.randy.look_at, self.area_rect
            )
        self.play(Blink(self.randy))
        self.wait()

class PiecewiseConstantCar(Scene):
    def construct(self):
        car = Car()
        start_point = 5*LEFT
        car.move_to(start_point)

        self.add(car)
        self.wait()
        for shift in 2, 6, 12:
            car.randy.rotate_in_place(np.pi/8)
            anim = MoveCar(
                car, start_point+shift*RIGHT,
                rate_func=linear
            )

            anim.target_mobject[0].rotate_in_place(-np.pi/8)
            # for mob in anim.starting_mobject, anim.mobject:
            #     mob.randy.rotate_in_place(np.pi/6)
            self.play(anim)
        self.wait()

class PiecewiseConstantPlot(PlotVelocity):
    CONFIG = {
        "y_axis_label" : "",
        "min_graph_proportion" : 0.1,
        "max_graph_proportion" : 0.8,
        "num_riemann_approximations" : 7,
        "riemann_rect_fill_opacity" : 0.75,
        "tick_size" : 0.2,
    }
    def construct(self):
        self.setup_graph()
        self.always_changing()
        self.show_piecewise_constant_graph()
        self.compute_distance_on_each_interval()
        self.approximate_original_curve()
        self.revert_to_specific_approximation()
        self.show_specific_rectangle()
        self.show_v_dt_for_all_rectangles()
        self.write_integral_symbol()
        self.roles_of_dt()
        self.what_does_sum_approach()
        self.label_integral()

    def setup_graph(self):
        self.setup_axes()
        self.add(*self.get_v_graph_and_label())

    def always_changing(self):
        dot = Dot()
        arrow = Arrow(LEFT, RIGHT)
        words = TextMobject("Always changing")
        group = VGroup(dot, arrow, words)
        def update_group(group, alpha):
            dot, arrow, words = group
            prop = interpolate(
                self.min_graph_proportion,
                self.max_graph_proportion,
                alpha
            )
            graph_point = self.v_graph.point_from_proportion(prop)
            dot.move_to(graph_point)
            x_val = self.x_axis.point_to_number(graph_point)
            angle = self.angle_of_tangent(x_val, self.v_graph)
            angle += np.pi/2
            vect = rotate_vector(RIGHT, angle)
            arrow.rotate(angle - arrow.get_angle() + np.pi)
            arrow.shift(
                graph_point + MED_SMALL_BUFF*vect - arrow.get_end()
            )
            words.next_to(arrow.get_start(), UP)
            return group
        update_group(group, 0)

        self.play(
            Write(words),
            ShowCreation(arrow),
            DrawBorderThenFill(dot),
            run_time = 1
        )
        self.play(UpdateFromAlphaFunc(
            group, update_group,
            rate_func = there_and_back,
            run_time = 5
        ))
        self.wait()
        self.play(FadeOut(group))

    def show_piecewise_constant_graph(self):
        pw_constant_graph = self.get_pw_constant_graph()
        alt_lines = [
            line.copy().set_color(YELLOW)
            for line in pw_constant_graph[:4]
        ]
        for line in alt_lines:
            line.start_dot = Dot(line.get_start())
            line.end_dot = Dot(line.get_end())
            VGroup(line.start_dot, line.end_dot).set_color(line.get_color())
        line = alt_lines[0]

        faders = [self.v_graph, self.v_graph_label]
        for mob in faders:
            mob.save_state()
            mob.generate_target()
            mob.target.fade(0.7)

        self.play(*list(map(MoveToTarget, faders)))
        self.play(ShowCreation(pw_constant_graph, run_time = 2))
        self.wait()
        self.play(ShowCreation(line))
        self.wait()
        for new_line in alt_lines[1:]:
            for mob in line.end_dot, new_line.start_dot, new_line:
                self.play(Transform(
                    line, mob,
                    run_time = 1./3
                ))
            self.remove(line)
            self.add(new_line)
            self.wait(2)
            line = new_line
        self.play(FadeOut(line))

        self.pw_constant_graph = pw_constant_graph

    def compute_distance_on_each_interval(self):
        rect_list = self.get_riemann_rectangles_list(
            self.v_graph, self.num_riemann_approximations, 
            max_dx = 1,
            x_min = 0,
            x_max = 8,
        )
        for rects in rect_list:
            rects.set_fill(opacity = self.riemann_rect_fill_opacity)
        flat_rects = self.get_riemann_rectangles(
            self.get_graph(lambda t : 0),
            x_min = 0, x_max = 8, dx = 1
        )
        rects = rect_list[0]
        rect = rects[1]
        flat_rects.submobjects[1] = rect.copy()

        right_brace = Brace(rect, RIGHT)
        top_brace = Brace(rect, UP)
        right_brace.label = right_brace.get_text("$7\\frac{\\text{m}}{\\text{s}}$")
        top_brace.label = top_brace.get_text("$1$s")

        self.play(FadeIn(rect))
        for brace in right_brace, top_brace:
            self.play(
                GrowFromCenter(brace),
                Write(brace.label, run_time = 1),
            )
            brace.add(brace.label)
            self.wait()
        self.play(
            ReplacementTransform(
                flat_rects, rects,
                run_time = 2,
                lag_ratio = 0.5,
            ),
            Animation(right_brace)
        )
        self.play(*list(map(FadeOut, [top_brace, right_brace])))
        self.wait()

        self.rects = rects
        self.rect_list = rect_list

    def approximate_original_curve(self):
        rects = self.rects
        self.play(
            FadeOut(self.pw_constant_graph),
            *[
                m.restore 
                for m in (self.v_graph, self.v_graph_label)
            ]+[Animation(self.rects)]
        )
        for new_rects in self.rect_list[1:]:
            self.transform_between_riemann_rects(rects, new_rects)
            self.wait()

    def revert_to_specific_approximation(self):
        rects = self.rects
        rects.save_state()
        target_rects = self.rect_list[2]
        target_rects.set_fill(opacity = 1)

        ticks = self.get_ticks(target_rects)
        tick_pair = VGroup(*ticks[4:6])
        brace = Brace(tick_pair, DOWN, buff = 0)
        dt_label = brace.get_text("$dt$", buff = SMALL_BUFF)

        example_text = TextMobject(
            "For example, \\\\",
            "$dt$", "$=0.25$"
        )
        example_text.to_corner(UP+RIGHT)
        example_text.set_color_by_tex("dt", YELLOW)

        self.play(ReplacementTransform(
            rects, target_rects,
            run_time = 2,
            lag_ratio = 0.5
        ))
        rects.restore()
        self.wait()
        self.play(
            ShowCreation(ticks),
            FadeOut(self.x_axis.numbers)
        )
        self.play(
            GrowFromCenter(brace),
            Write(dt_label)
        )
        self.wait()
        self.play(
            FadeIn(
                example_text, 
                run_time = 2,
                lag_ratio = 0.5,
            ),
            ReplacementTransform(
                dt_label.copy(),
                example_text.get_part_by_tex("dt")
            )
        )
        self.wait()

        self.rects = rects = target_rects
        self.ticks = ticks
        self.dt_brace = brace
        self.dt_label = dt_label
        self.dt_example_text = example_text

    def show_specific_rectangle(self):
        rects = self.rects
        rect = rects[4].copy()
        rect_top = Line(
            rect.get_corner(UP+LEFT),
            rect.get_corner(UP+RIGHT),
            color = self.v_graph.get_color()
        )

        t_vals = [1, 1.25]
        t_labels = VGroup(*[
            TexMobject("t=%s"%str(t))
            for t in t_vals
        ])
        t_labels.scale(0.7)
        t_labels.next_to(rect, DOWN)
        for vect, label in zip([LEFT, RIGHT], t_labels):
            label.shift(1.5*vect)
            label.add(Arrow(
                label.get_edge_center(-vect),
                rect.get_corner(DOWN+vect),
                buff = SMALL_BUFF,
                tip_length = 0.15,
                color = WHITE
            ))

        v_lines = VGroup()
        h_lines = VGroup()
        height_labels = VGroup()
        for t in t_vals:
            v_line = self.get_vertical_line_to_graph(
                t, self.v_graph,
                color = YELLOW
            )
            y_axis_point = self.graph_origin[0]*RIGHT
            y_axis_point += v_line.get_end()[1]*UP
            h_line = DashedLine(v_line.get_end(), y_axis_point)
            label = TexMobject("%.1f"%v_func(t))
            label.scale(0.5)
            label.next_to(h_line, LEFT, SMALL_BUFF)
            v_lines.add(v_line)
            h_lines.add(h_line)
            height_labels.add(label)

        circle = Circle(radius = 0.25, color = WHITE)
        circle.move_to(rect.get_top())

        self.play(
            rects.set_fill, None, 0.25,
            Animation(rect)
        )
        self.wait()
        for label in t_labels:
            self.play(FadeIn(label))
        self.wait()
        for v_line, h_line, label in zip(v_lines, h_lines, height_labels):
            self.play(ShowCreation(v_line))
            self.play(ShowCreation(h_line))
            self.play(Write(label, run_time = 1))
            self.wait()
        self.wait()
        t_label_copy = t_labels[0].copy()
        self.play(
            t_label_copy.scale, 1./0.7,
            t_label_copy.next_to, self.v_graph_label, DOWN+LEFT, 0
        )
        self.wait()
        self.play(FadeOut(t_label_copy))
        self.wait()

        self.play(ShowCreation(circle))
        self.play(ShowCreation(rect_top))
        self.play(FadeOut(circle))
        rect.add(rect_top)
        self.wait()
        for x in range(2):
            self.play(
                rect.stretch_to_fit_height, v_lines[1].get_height(),
                rect.move_to, rect.get_bottom(), DOWN,
                Animation(v_lines),
                run_time = 4,
                rate_func = there_and_back
            )

        self.play(*list(map(FadeOut, [
            group[1]
            for group in (v_lines, h_lines, height_labels)
        ])))
        self.play(
            v_lines[0].set_color, RED,
            rate_func = there_and_back,
        )
        self.wait()

        area = TextMobject(
            "7$\\frac{\\text{m}}{\\text{s}}$",
            "$\\times$",
            "0.25s",
            "=",
            "1.75m"
        )
        area.next_to(rect, RIGHT, LARGE_BUFF)
        arrow = Arrow(
            area.get_left(), rect.get_center(), 
            buff = 0,
            color = WHITE
        )
        area.shift(SMALL_BUFF*RIGHT)

        self.play(
            Write(area),
            ShowCreation(arrow)
        )
        self.wait(2)
        self.play(*list(map(FadeOut, [
            area, arrow, 
            v_lines[0], h_lines[0], height_labels[0],
            rect, t_labels
        ])))

    def show_v_dt_for_all_rectangles(self):
        dt_brace_group = VGroup(self.dt_brace, self.dt_label)
        rects_subset = self.rects[10:20]

        last_rect = None
        for rect in rects_subset:
            brace = Brace(rect, LEFT, buff = 0)
            v_t = TexMobject("v(t)")
            v_t.next_to(brace, LEFT, SMALL_BUFF)
            anims = [
                rect.set_fill, None, 1,
                dt_brace_group.next_to, rect, DOWN, SMALL_BUFF
            ]
            if last_rect is not None:
                anims += [
                    last_rect.set_fill, None, 0.25,
                    ReplacementTransform(last_brace, brace),
                    ReplacementTransform(last_v_t, v_t),
                ]
            else:
                anims += [
                    GrowFromCenter(brace),
                    Write(v_t)
                ]
            self.play(*anims)
            self.wait()

            last_rect = rect
            last_brace = brace
            last_v_t = v_t

        self.v_t = last_v_t
        self.v_t_brace = last_brace

    def write_integral_symbol(self):
        integral = TexMobject(
            "\\int", "^8", "_0", "v(t)", "\\,dt"
        )
        integral.to_corner(UP+RIGHT)
        int_copy = integral.get_part_by_tex("int").copy()
        bounds = list(map(integral.get_part_by_tex, ["0", "8"]))

        sum_word = TextMobject("``Sum''")
        sum_word.next_to(integral, DOWN, MED_LARGE_BUFF, LEFT)
        alt_sum_word = sum_word.copy()
        int_symbol = TexMobject("\\int")
        int_symbol.replace(alt_sum_word[1], dim_to_match = 1)
        alt_sum_word.submobjects[1] = int_symbol

        self.play(FadeOut(self.dt_example_text))
        self.play(Write(integral.get_part_by_tex("int")))
        self.wait()
        self.play(Transform(int_copy, int_symbol))
        self.play(Write(alt_sum_word), Animation(int_copy))
        self.remove(int_copy)
        self.play(ReplacementTransform(alt_sum_word, sum_word))
        self.wait()

        for bound in bounds:
            self.play(Write(bound))
        self.wait()
        for bound, num in zip(bounds, [0, 8]):
            bound_copy = bound.copy()
            point = self.coords_to_point(num, 0)
            self.play(
                bound_copy.scale, 1.5,
                bound_copy.next_to, point, DOWN, MED_LARGE_BUFF
            )
        self.play(ApplyWave(self.ticks, direction = UP))
        self.wait()

        for mob, tex in (self.v_t, "v(t)"), (self.dt_label, "dt"):
            self.play(ReplacementTransform(
                mob.copy().set_color(YELLOW), 
                integral.get_part_by_tex(tex),
                run_time = 2
            ))
        self.wait()

        self.integral = integral
        self.sum_word = sum_word

    def roles_of_dt(self):
        rects = self.rects
        next_rects = self.rect_list[3]

        morty = Mortimer().flip()
        morty.to_corner(DOWN+LEFT)
        int_dt = self.integral.get_part_by_tex("dt")
        dt_copy = int_dt.copy()

        self.play(FadeIn(morty))
        self.play(
            morty.change_mode, "raise_right_hand",
            morty.look, UP+RIGHT,
            dt_copy.next_to, morty.get_corner(UP+RIGHT), UP,
            dt_copy.set_color, YELLOW
        )
        self.play(Blink(morty))
        self.play(
            ReplacementTransform(
                dt_copy.copy(), int_dt,
                run_time = 2
            ),
            morty.look_at, int_dt
        )
        self.wait(2)
        self.play(
            ReplacementTransform(dt_copy.copy(), self.dt_label),
            morty.look_at, self.dt_label
        )
        self.play(*[
            ApplyMethod(
                tick.shift, tick.get_height()*UP/2,
                run_time = 2,
                rate_func = squish_rate_func(
                    there_and_back,
                    alpha, alpha+0.2,
                )
            )
            for tick, alpha in zip(
                self.ticks, 
                np.linspace(0, 0.8, len(self.ticks))
            )
        ])
        self.wait()

        #Shrink dt just a bit
        self.play(
            morty.change_mode, "pondering",
            rects.set_fill, None, 0.75,
            *list(map(FadeOut, [
                dt_copy, self.v_t, self.v_t_brace
            ]))
        )
        rects.align_submobjects(next_rects)
        for every_other_rect in rects[::2]:
            every_other_rect.set_fill(opacity = 0)
        self.play(
            self.dt_brace.stretch, 0.5, 0,
            self.dt_brace.move_to, self.dt_brace, LEFT,
            ReplacementTransform(
                rects, next_rects,
                run_time = 2,
                lag_ratio = 0.5
            ),
            Transform(
                self.ticks, self.get_ticks(next_rects),
                run_time = 2,
                lag_ratio = 0.5,
            ),
        )
        self.rects = rects = next_rects
        self.wait()
        self.play(Blink(morty))
        self.play(*[
            ApplyFunction(
                lambda r : r.shift(0.2*UP).set_fill(None, 1),
                rect,
                run_time = 2,
                rate_func = squish_rate_func(
                    there_and_back,
                    alpha, alpha+0.2,
                )
            )
            for rect, alpha in zip(
                rects, 
                np.linspace(0, 0.8, len(rects))
            )
        ]+[
            morty.change_mode, "thinking",
        ])
        self.wait()

        self.morty = morty

    def what_does_sum_approach(self):
        morty = self.morty
        rects = self.rects

        cross = TexMobject("\\times")
        cross.replace(self.sum_word, stretch = True)
        cross.set_color(RED)
        brace = Brace(self.integral, DOWN)
        dt_to_0 = brace.get_text("$dt \\to 0$")

        distance_words = TextMobject(
            "Area", "= Distance traveled"
        )
        distance_words.next_to(rects, UP)
        arrow = Arrow(
            distance_words[0].get_bottom(),
            rects.get_center(),
            color = WHITE
        )

        self.play(PiCreatureSays(
            morty, "Why not $\\Sigma$?",
            target_mode = "sassy"
        ))
        self.play(Blink(morty))
        self.wait()
        self.play(Write(cross))
        self.wait()
        self.play(
            RemovePiCreatureBubble(morty, target_mode = "plain"),
            *list(map(FadeOut, [
                cross, self.sum_word, self.ticks,
                self.dt_brace, self.dt_label,
            ]))
        )
        self.play(FadeIn(brace), FadeIn(dt_to_0))
        for new_rects in self.rect_list[4:]:
            rects.align_submobjects(new_rects)
            for every_other_rect in rects[::2]:
                every_other_rect.set_fill(opacity = 0)
            self.play(
                Transform(
                    rects, new_rects, 
                    run_time = 2,
                    lag_ratio = 0.5
                ),
                morty.look_at, rects,
            )
            self.wait()

        self.play(
            Write(distance_words),
            ShowCreation(arrow),
            morty.change_mode, "pondering",
            morty.look_at, distance_words,
        )
        self.wait()
        self.play(Blink(morty))
        self.wait()

        self.area_arrow = arrow

    def label_integral(self):
        words = TextMobject("``Integral of $v(t)$''")
        words.to_edge(UP)
        arrow = Arrow(
            words.get_right(),
            self.integral.get_left()
        )

        self.play(Indicate(self.integral))
        self.play(Write(words, run_time = 2))
        self.play(ShowCreation(arrow))
        self.wait()
        self.play(*[
            ApplyFunction(
                lambda r : r.shift(0.2*UP).set_fill(None, 1),
                rect,
                run_time = 3,
                rate_func = squish_rate_func(
                    there_and_back,
                    alpha, alpha+0.2,
                )
            )
            for rect, alpha in zip(
                self.rects, 
                np.linspace(0, 0.8, len(self.rects))
            )
        ]+[
            Animation(self.area_arrow),
            self.morty.change_mode, "happy",
            self.morty.look_at, self.rects,
        ])
        self.wait()

    #####

    def get_pw_constant_graph(self):
        result = VGroup()
        for left_x in range(8):
            xs = [left_x, left_x+1]
            y = self.v_graph.underlying_function(left_x)
            line = Line(*[
                self.coords_to_point(x, y)
                for x in xs
            ])
            line.set_color(self.v_graph.get_color())
            result.add(line)
        return result

    def get_ticks(self, rects):
        ticks = VGroup(*[
            Line(
                point+self.tick_size*UP/2, 
                point+self.tick_size*DOWN/2
            )
            for t in np.linspace(0, 8, len(rects)+1)
            for point in [self.coords_to_point(t, 0)]
        ])
        ticks.set_color(YELLOW)
        return ticks

class DontKnowHowToHandleNonConstant(TeacherStudentsScene):
    def construct(self):
        self.play(*[
            ApplyMethod(pi.change, "maybe", UP)
            for pi in self.get_pi_creatures()
        ])
        self.wait(3)

class CarJourneyApproximation(Scene):
    CONFIG = {
        "n_jumps" : 5,
        "bottom_words" : "Approximated motion (5 jumps)",
    }
    def construct(self):
        points = [5*LEFT + v for v in (UP, 2*DOWN)]
        cars = [Car().move_to(point) for point in points]
        h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
        words = [
            TextMobject("Real motion (smooth)").shift(3*UP),
            TextMobject(self.bottom_words).shift(0.5*DOWN),
        ]
        words[1].set_color(GREEN)


        self.add(h_line, *cars + words)
        self.wait()
        self.play(*[
            MoveCar(
                car, point+10*RIGHT,
                run_time = 5,
                rate_func = rf
            )
            for car, point, rf in zip(cars, points, [
                s_rate_func,
                self.get_approximated_rate_func(self.n_jumps)
            ])
        ])
        self.wait()

    def get_approximated_rate_func(self, n):
        new_v_rate_func = lambda t : v_rate_func(np.floor(t*n)/n)
        max_integral, err = scipy.integrate.quad(
            v_rate_func, 0, 1
        )
        def result(t):
            integral, err = scipy.integrate.quad(new_v_rate_func, 0, t)
            return integral/max_integral
        return result

class LessWrongCarJourneyApproximation(CarJourneyApproximation):
    CONFIG = {
        "n_jumps" : 20,
        "bottom_words" : "Better approximation (20 jumps)",
    }

class TellMeThatsNotSurprising(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Tell me that's \\\\ not surprising!",
            target_mode = "hooray",
            run_time = 1
        )
        self.wait(3)

class HowDoesThisHelp(TeacherStudentsScene):
    def construct(self):
        self.student_says(
            "How does this help\\textinterrobang",
            target_mode = "angry",
            run_time = 1
        )
        self.change_student_modes(
            "confused", "angry", "confused",
        )
        self.wait(2)
        self.teacher_says(
            "You're right.",
            target_mode = "shruggie",
            run_time = 1
        )
        self.change_student_modes(*["sassy"]*3)
        self.wait(2)

class AreaUnderACurve(GraphScene):
    CONFIG = {
        "y_max" : 4,
        "y_min" : 0,
        "num_iterations" : 7
    }
    def construct(self):
        self.setup_axes()
        graph = self.get_graph(self.func)
        rect_list = self.get_riemann_rectangles_list(
            graph, self.num_iterations
        )
        VGroup(*rect_list).set_fill(opacity = 0.8)
        rects = rect_list[0]

        self.play(ShowCreation(graph))
        self.play(Write(rects))
        for new_rects in rect_list[1:]:
            rects.align_submobjects(new_rects)
            for every_other_rect in rects[::2]:
                every_other_rect.set_fill(opacity = 0)
            self.play(Transform(
                rects, new_rects,
                run_time = 2,
                lag_ratio = 0.5
            ))
        self.wait()


    def func(self, x):
        return np.sin(x) + 1

class AltAreaUnderCurve(AreaUnderACurve):
    CONFIG = {
        "graph_origin" : 2*DOWN,
        "x_min" : -3,
        "x_max" : 3,
        "x_axis_width" : 12,
        "y_max" : 2,
        "y_axis_height" : 4,
    }
    def func(self, x):
        return np.exp(-x**2)

class Chapter1Wrapper(Chapter2Wrapper):
    CONFIG = {
        "title" : "Essence of calculus, chapter 1",
    }

class AreaIsDerivative(PlotVelocity, ReconfigurableScene):
    CONFIG = {
        "y_axis_label" : "",
        "num_rects" : 400,
        "dT" : 0.25,
        "variable_point_label" : "T",
        "area_opacity" : 0.8,
    }
    def setup(self):
        PlotVelocity.setup(self)
        ReconfigurableScene.setup(self)
        self.setup_axes()
        self.add(*self.get_v_graph_and_label())
        self.x_axis_label_mob.shift(MED_LARGE_BUFF*DOWN)
        self.v_graph_label.shift(MED_LARGE_BUFF*DOWN)
        self.foreground_mobjects = []

    def construct(self):
        self.introduce_variable_area()
        self.write_integral()
        self.nudge_input()
        self.show_rectangle_approximation()

    def introduce_variable_area(self):
        area = self.area = self.get_area(0, 6)
        x_nums = self.x_axis.numbers

        self.play(Write(area, run_time = 2))
        self.play(FadeOut(self.x_axis.numbers))
        self.add_T_label(6)
        self.change_area_bounds(
            new_t_max = 4,
            rate_func = there_and_back,
            run_time = 2
        )
        self.wait()

    def write_integral(self):
        integral = TexMobject("\\int", "^T", "_0", "v(t)", "\\,dt")
        integral.to_corner(UP+RIGHT)
        integral.shift(2*LEFT)
        top_T = integral.get_part_by_tex("T")
        moving_T = self.T_label_group[0]

        s_T = TexMobject("s(T)", "= ")
        s_T.set_color_by_tex("s", DISTANCE_COLOR)
        s_T.next_to(integral, LEFT)

        int_arrow, s_arrow = [
            Arrow(
                mob.get_left(), self.area.get_center(),
                color = WHITE
            )
            for mob in (integral, s_T)
        ]

        distance_word = TextMobject("Distance")
        distance_word.move_to(self.area)

        self.play(Write(integral))
        self.play(ShowCreation(int_arrow))
        self.foreground_mobjects.append(int_arrow)
        self.wait()
        self.change_area_bounds(
            new_t_max = 8,
            rate_func = there_and_back,
            run_time = 3,
        )
        self.play(Indicate(top_T))
        self.play(ReplacementTransform(
            top_T.copy(), moving_T
        ))
        self.change_area_bounds(
            new_t_max = 3,
            rate_func = there_and_back,
            run_time = 3
        )
        self.wait()
        self.play(Write(distance_word, run_time = 2))
        self.play(
            ReplacementTransform(int_arrow, s_arrow),
            FadeIn(s_T)
        )
        self.wait()
        self.play(FadeOut(distance_word))
        self.change_area_bounds(new_t_max = 0, run_time = 2)
        self.change_area_bounds(
            new_t_max = 8, 
            rate_func=linear,
            run_time = 7.9,
        )
        self.wait()
        self.change_area_bounds(new_t_max = 5)
        self.wait()

    def nudge_input(self):
        dark_area = self.area.copy()
        dark_area.set_fill(BLACK, opacity = 0.5)
        curr_T = self.x_axis.point_to_number(self.area.get_right())
        new_T = curr_T + self.dT

        rect = Rectangle(
            stroke_width = 0,
            fill_color = YELLOW,
            fill_opacity = 0.75
        )
        rect.replace(
            VGroup(
                VectorizedPoint(self.coords_to_point(new_T, 0)),
                self.right_v_line,
            ),
            stretch = True
        )

        dT_brace = Brace(rect, DOWN, buff = 0)
        dT_label = dT_brace.get_text("$dT$", buff = SMALL_BUFF)
        dT_label_group = VGroup(dT_label, dT_brace)

        ds_label = TexMobject("ds")
        ds_label.next_to(rect, RIGHT, LARGE_BUFF, UP)
        ds_label.set_color(DISTANCE_COLOR)
        ds_arrow = Arrow(ds_label.get_left(), rect.get_left())
        ds_arrow.set_color(WHITE)

        v_brace = Brace(rect, LEFT, buff = SMALL_BUFF)
        v_T_label = v_brace.get_text("$v(T)$", buff = SMALL_BUFF)

        self.change_area_bounds(new_t_max = new_T)
        self.play(
            FadeIn(dark_area),
            *list(map(Animation, self.foreground_mobjects))
        )
        self.play(
            FadeOut(self.T_label_group),
            FadeIn(dT_label_group)
        )
        self.wait()
        self.play(Write(ds_label))
        self.play(ShowCreation(ds_arrow))
        self.wait(2)
        self.play(GrowFromCenter(v_brace))
        self.play(ReplacementTransform(
            self.v_graph_label.get_part_by_tex("v").copy(),
            v_T_label,
            run_time = 2
        ))
        self.wait()
        self.play(Indicate(dT_label))
        self.wait()

        self.rect = rect
        self.dT_label_group = dT_label_group
        self.v_T_label_group = VGroup(v_T_label, v_brace)
        self.dark_area = dark_area
        self.ds_label = ds_label
        self.ds_arrow = ds_arrow

    def show_rectangle_approximation(self):
        formula1 = TexMobject("ds", "=", "v(T)", "dT")
        formula2 = TexMobject("{ds", "\\over\\,", "dT}", "=", "v(T)")
        for formula in formula1, formula2:
            formula.next_to(self.v_graph_label, UP, LARGE_BUFF)
            formula.set_color_by_tex("ds", DISTANCE_COLOR)

        self.play(
            DrawBorderThenFill(self.rect),
            Animation(self.ds_arrow)
        )
        self.wait()
        self.play(*[
            ReplacementTransform(
                mob, formula1.get_part_by_tex(tex),
                run_time = 2
            )
            for mob, tex in [
                (self.ds_label, "ds"),
                (self.ds_arrow, "="),
                (self.v_T_label_group[0].copy(), "v(T)"),
                (self.dT_label_group[0].copy(), "dT"),
            ]
        ])
        self.wait()
        self.transition_to_alt_config(
            dT = self.dT/5.0,
            transformation_kwargs = {"run_time" : 2},
        )
        self.wait()
        self.play(*[
            ReplacementTransform(
                formula1.get_part_by_tex(tex),
                formula2.get_part_by_tex(tex),
            )
            for tex in ("ds", "=", "v(T)", "dT")
        ] + [
            Write(formula2.get_part_by_tex("over"))
        ])
        self.wait()


    ####

    def add_T_label(self, x_val, **kwargs):
        triangle = RegularPolygon(n=3, start_angle = np.pi/2)
        triangle.set_height(MED_SMALL_BUFF)
        triangle.move_to(self.coords_to_point(x_val, 0), UP)
        triangle.set_fill(WHITE, 1)
        triangle.set_stroke(width = 0)
        T_label = TexMobject(self.variable_point_label)
        T_label.next_to(triangle, DOWN)
        v_line = self.get_vertical_line_to_graph(
            x_val, self.v_graph,
            color = YELLOW
        )

        self.play(
            DrawBorderThenFill(triangle),
            ShowCreation(v_line),
            Write(T_label, run_time = 1),
            **kwargs
        )

        self.T_label_group = VGroup(T_label, triangle)
        self.right_v_line = v_line

    def get_area(self, t_min, t_max):
        numerator = max(t_max - t_min, 0.01)
        dx = float(numerator) / self.num_rects
        return self.get_riemann_rectangles(
            self.v_graph,
            x_min = t_min,
            x_max = t_max,
            dx = dx,
            stroke_width = 0,
        ).set_fill(opacity = self.area_opacity)

    def change_area_bounds(self, new_t_min = None, new_t_max = None, **kwargs):
        curr_t_min = self.x_axis.point_to_number(self.area.get_left())
        curr_t_max = self.x_axis.point_to_number(self.area.get_right())
        if new_t_min is None:
            new_t_min = curr_t_min
        if new_t_max is None:
            new_t_max = curr_t_max

        group = VGroup(self.area, self.right_v_line, self.T_label_group)
        def update_group(group, alpha):
            area, v_line, T_label = group
            t_min = interpolate(curr_t_min, new_t_min, alpha)
            t_max = interpolate(curr_t_max, new_t_max, alpha)
            new_area = self.get_area(t_min, t_max)
            new_v_line = self.get_vertical_line_to_graph(
                t_max, self.v_graph
            )
            new_v_line.set_color(v_line.get_color())
            T_label.move_to(new_v_line.get_bottom(), UP)

            #Fade close to 0
            T_label[0].set_fill(opacity = min(1, t_max)) 

            Transform(area, new_area).update(1)
            Transform(v_line, new_v_line).update(1)
            return group

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

class DirectInterpretationOfDsDt(TeacherStudentsScene):
    def construct(self):
        equation = TexMobject("{ds", "\\over\\,", "dT}", "(T)", "=", "v(T)")
        ds, over, dt, of_T, equals, v = equation
        equation.next_to(self.get_pi_creatures(), UP, LARGE_BUFF)
        equation.shift(RIGHT)
        v.set_color(VELOCITY_COLOR)

        s_words = TextMobject("Tiny change in", "distance")
        s_words.next_to(ds, UP+LEFT, LARGE_BUFF)
        s_words.shift_onto_screen()
        s_arrow = Arrow(s_words[1].get_bottom(), ds.get_left())
        s_words.add(s_arrow)
        s_words.set_color(DISTANCE_COLOR)

        t_words = TextMobject("Tiny change in", "time")
        t_words.next_to(dt, DOWN+LEFT)
        t_words.to_edge(LEFT)
        t_arrow = Arrow(t_words[1].get_top(), dt.get_left())
        t_words.add(t_arrow)
        t_words.set_color(TIME_COLOR)

        self.add(ds, over, dt, of_T)
        for words, part in (s_words, ds), (t_words, dt):
            self.play(
                FadeIn(
                    words, 
                    run_time = 2,
                    lag_ratio = 0.5,
                ),
                self.students[1].change_mode, "raise_right_hand"
            )
            self.play(part.set_color, words.get_color())
        self.wait()
        self.play(Write(VGroup(equals, v)))
        self.change_student_modes(*["pondering"]*3)
        self.wait(3)

class FindAntiderivative(Antiderivative):
    def construct(self):
        self.introduce()
        self.first_part()
        self.second_part()
        self.combine()
        self.add_plus_C()

    def introduce(self):
        q_marks, rhs = functions = self.get_functions("???", "t(8-t)")
        expanded_rhs = TexMobject("8t - t^2")
        expanded_rhs.move_to(rhs, LEFT)
        expanded_rhs.set_color(rhs.get_color())
        self.v_part1 = VGroup(*expanded_rhs[:2])
        self.v_part2 = VGroup(*expanded_rhs[2:])
        for part in self.v_part1, self.v_part2:
            part.save_state()

        top_arc, bottom_arc = arcs = self.get_arcs(functions)
        derivative, antiderivative = words = self.get_arc_labels(arcs)

        self.add(functions)
        self.play(*list(map(ShowCreation, arcs)))
        for word in words:
            self.play(FadeIn(word, lag_ratio = 0.5))
        self.wait()
        self.change_mode("confused")
        self.wait(2)
        self.play(*[
            ReplacementTransform(
                rhs[i], expanded_rhs[j],
                run_time = 2,
                path_arc = np.pi
            )
            for i, j in enumerate([1, 4, 0, 2, 3, 4])
        ]+[
            self.pi_creature.change_mode, "hesitant"
        ])
        self.wait()

        self.q_marks = q_marks
        self.arcs = arcs
        self.words = words

    def first_part(self):
        four_t_squared, two_t = self.get_functions("4t^2", "2t")
        four = four_t_squared[0]
        four.shift(UP)
        four.set_fill(opacity = 0)
        t_squared = VGroup(*four_t_squared[1:])
        two_t.move_to(self.v_part1, LEFT)

        self.play(self.v_part2.to_corner, UP+RIGHT)
        self.play(
            self.pi_creature.change, "plain", self.v_part1
        )
        self.play(ApplyWave(
            self.q_marks, 
            direction = UP, 
            amplitude = SMALL_BUFF
        ))
        self.wait(2)
        self.play(
            FadeOut(self.q_marks),
            FadeIn(t_squared),
            self.v_part1.shift, DOWN+RIGHT,
        )
        self.play(*[
            ReplacementTransform(
                t_squared[i].copy(), two_t[1-i],
                run_time = 2,
                path_arc = -np.pi/6.
            )
            for i in (0, 1)
        ])
        self.change_mode("thinking")
        self.wait()
        self.play(four.set_fill, YELLOW, 1)
        self.play(four.shift, DOWN)
        self.play(FadeOut(two_t))
        self.play(self.v_part1.restore)
        self.play(four.set_color, DISTANCE_COLOR)
        self.wait(2)

        self.s_part1 = four_t_squared

    def second_part(self):
        self.arcs_copy = self.arcs.copy()
        self.words_copy = self.words.copy()
        part1_group = VGroup(
            self.s_part1, self.v_part1, 
            self.arcs_copy, self.words_copy
        )

        neg_third_t_cubed, three_t_squared = self.get_functions(
            "- \\frac{1}{3} t^3", "3t^2"
        )
        three_t_squared.move_to(self.v_part1, LEFT)
        neg = neg_third_t_cubed[0]
        third = VGroup(*neg_third_t_cubed[1:4])
        t_cubed = VGroup(*neg_third_t_cubed[4:])
        three = three_t_squared[0]
        t_squared = VGroup(*three_t_squared[1:])

        self.play(
            part1_group.scale, 0.5,
            part1_group.to_corner, UP+LEFT,
            self.pi_creature.change_mode, "plain"
        )
        self.play(
            self.v_part2.restore,
            self.v_part2.shift, LEFT
        )
        self.play(FadeIn(self.q_marks))
        self.wait()

        self.play(
            FadeOut(self.q_marks),
            FadeIn(t_cubed),
            self.v_part2.shift, DOWN+RIGHT
        )
        self.play(*[
            ReplacementTransform(
                t_cubed[i].copy(), three_t_squared[j],
                path_arc = -np.pi/6,
                run_time = 2,
            )
            for i, j in [(0, 1), (1, 0), (1, 2)]
        ])
        self.wait()
        self.play(FadeIn(third))
        self.play(FadeOut(three))
        self.wait(2)
        self.play(Write(neg))
        self.play(
            FadeOut(t_squared),
            self.v_part2.shift, UP+LEFT
        )
        self.wait(2)

        self.s_part2 = neg_third_t_cubed

    def combine(self):
        self.play(
            self.v_part1.restore,
            self.v_part2.restore,
            self.s_part1.scale, 2,
            self.s_part1.next_to, self.s_part2, LEFT,
            FadeOut(self.arcs_copy),
            FadeOut(self.words_copy),
            run_time = 2,
        )
        self.change_mode("happy")
        self.wait(2)

    def add_plus_C(self):
        s_group = VGroup(self.s_part1, self.s_part2)
        plus_Cs = [
            TexMobject("+%d"%d)
            for d in range(1, 8)
        ]
        for plus_C in plus_Cs:
            plus_C.set_color(YELLOW)
            plus_C.move_to(s_group, RIGHT)
        plus_C = plus_Cs[0]

        self.change_mode("sassy")
        self.wait()
        self.play(
            s_group.next_to, plus_C.copy(), LEFT,
            GrowFromCenter(plus_C),
        )
        self.wait()
        for new_plus_C in plus_Cs[1:]:
            self.play(Transform(plus_C, new_plus_C))
            self.wait()

class GraphSPlusC(GraphDistanceVsTime):
    CONFIG = {
        "y_axis_label" : "Distance"
    }
    def construct(self):
        self.setup_axes()
        graph = self.get_graph(
            s_func, 
            color = DISTANCE_COLOR,
            x_min = 0,
            x_max = 8,
        )
        tangent = self.get_secant_slope_group(
            6, graph, dx = 0.01
        ).secant_line
        v_line = self.get_vertical_line_to_graph(
            6, graph, line_class = DashedLine
        )
        v_line.scale_in_place(2)
        v_line.set_color(WHITE)
        graph_label, plus_C = full_label = TexMobject(
            "s(t) = 4t^2 - \\frac{1}{3}t^3", "+C"
        )
        plus_C.set_color(YELLOW)
        full_label.next_to(graph.points[-1], DOWN)
        full_label.to_edge(RIGHT)

        self.play(ShowCreation(graph))
        self.play(FadeIn(graph_label))
        self.wait()
        self.play(
            graph.shift, UP,
            run_time = 2,
            rate_func = there_and_back
        )
        self.play(ShowCreation(tangent))
        graph.add(tangent)
        self.play(ShowCreation(v_line))
        self.play(
            graph.shift, 2*DOWN, 
            run_time = 4,
            rate_func = there_and_back,
        )
        self.play(Write(plus_C))
        self.play(
            graph.shift, 2*UP,
            rate_func = there_and_back,
            run_time = 4,
        )
        self.wait()

class LowerBound(AreaIsDerivative):
    CONFIG = {
        "graph_origin" : 2.5*DOWN + 6*LEFT
    }

    def construct(self):
        self.add_integral_and_area()
        self.mention_lower_bound()
        self.drag_right_endpoint_to_zero()
        self.write_antiderivative_difference()
        self.show_alternate_antiderivative_difference()
        self.add_constant_to_antiderivative()

    def add_integral_and_area(self):
        self.area = self.get_area(0, 6)
        self.integral = self.get_integral("0", "T")
        self.remove(self.x_axis.numbers)
        self.add(self.area, self.integral)
        self.add_T_label(6, run_time = 0)

    def mention_lower_bound(self):
        lower_bound = self.integral.get_part_by_tex("0")
        circle = Circle(color = YELLOW)
        circle.replace(lower_bound)
        circle.scale_in_place(3)
        zero_label = lower_bound.copy()

        self.play(ShowCreation(circle))
        self.play(Indicate(lower_bound))
        self.play(
            zero_label.scale, 1.5,
            zero_label.next_to, self.graph_origin, DOWN, MED_LARGE_BUFF,
            FadeOut(circle)
        )
        self.wait()

        self.zero_label = zero_label

    def drag_right_endpoint_to_zero(self):
        zero_integral = self.get_integral("0", "0")
        zero_integral[1].set_color(YELLOW)
        zero_int_bounds = list(reversed(
            zero_integral.get_parts_by_tex("0")
        ))
        for bound in zero_int_bounds:
            circle = Circle(color = YELLOW)
            circle.replace(bound)
            circle.scale_in_place(3)
            bound.circle = circle
        self.integral.save_state()
        equals_zero = TexMobject("=0")
        equals_zero.next_to(zero_integral, RIGHT)
        equals_zero.set_color(GREEN)

        self.change_area_bounds(0, 0, run_time = 3)
        self.play(ReplacementTransform(
            self.zero_label.copy(), equals_zero
        ))
        self.play(Transform(self.integral, zero_integral))
        self.wait(2)
        for bound in zero_int_bounds:
            self.play(ShowCreation(bound.circle))
            self.play(FadeOut(bound.circle))
        self.play(*[
            ReplacementTransform(
                bound.copy(), VGroup(equals_zero[1])
            )
            for bound in zero_int_bounds
        ])
        self.wait(2)
        self.change_area_bounds(0, 5)
        self.play(
            self.integral.restore,
            FadeOut(equals_zero)
        )

        self.zero_integral = zero_integral

    def write_antiderivative_difference(self):
        antideriv_diff = self.get_antiderivative_difference("0", "T")
        equals, at_T, minus, at_zero = antideriv_diff
        antideriv_diff_at_eight = self.get_antiderivative_difference("0", "8")
        at_eight = antideriv_diff_at_eight.left_part
        integral_at_eight = self.get_integral("0", "8")

        for part in at_T, at_zero, at_eight:
            part.brace = Brace(part, DOWN, buff = SMALL_BUFF)
            part.brace.save_state()

        antideriv_text = at_T.brace.get_text("Antiderivative", buff = SMALL_BUFF)
        antideriv_text.set_color(MAROON_B)
        value_at_eight = at_eight.brace.get_text(
            "%.2f"%s_func(8)
        )
        happens_to_be_zero = at_zero.brace.get_text("""
            Happens to
            equal 0
        """)

        big_brace = Brace(VGroup(at_T, at_zero))
        cancel_text = big_brace.get_text("Cancels when $T=0$")

        self.play(*list(map(Write, [equals, at_T])))
        self.play(
            GrowFromCenter(at_T.brace),
            Write(antideriv_text, run_time = 2)
        )
        self.change_area_bounds(0, 5.5, rate_func = there_and_back)
        self.wait()
        self.play(
            ReplacementTransform(at_T.copy(), at_zero),
            Write(minus)
        )
        self.wait()
        self.play(
            ReplacementTransform(at_T.brace, big_brace),
            ReplacementTransform(antideriv_text, cancel_text)
        )
        self.change_area_bounds(0, 0, run_time = 4)
        self.wait()
        self.play(
            ReplacementTransform(big_brace, at_zero.brace),
            ReplacementTransform(cancel_text, happens_to_be_zero),
        )
        self.wait(2)
        self.change_area_bounds(0, 8, run_time = 2)
        self.play(
            Transform(self.integral, integral_at_eight),
            Transform(antideriv_diff, antideriv_diff_at_eight),
            MaintainPositionRelativeTo(at_zero.brace, at_zero),
            MaintainPositionRelativeTo(happens_to_be_zero, at_zero.brace),
        )
        self.play(
            GrowFromCenter(at_eight.brace),
            Write(value_at_eight)
        )
        self.wait(2)
        self.play(*list(map(FadeOut, [
            at_eight.brace, value_at_eight,
            at_zero.brace, happens_to_be_zero,
        ])))

        self.antideriv_diff = antideriv_diff

    def show_alternate_antiderivative_difference(self):
        new_integral = self.get_integral("1", "7")
        new_antideriv_diff = self.get_antiderivative_difference("1", "7")
        numbers = [
            TexMobject("%d"%d).next_to(
                self.coords_to_point(d, 0), 
                DOWN, MED_LARGE_BUFF
            )
            for d in (1, 7)
        ]
        tex_mobs = [new_integral]+new_antideriv_diff[1::2]+numbers
        for tex_mob in tex_mobs:
            tex_mob.set_color_by_tex("1", RED)
            tex_mob.set_color_by_tex("7", GREEN)
            tex_mob.set_color_by_tex("\\frac{1}{3}", WHITE)

        self.change_area_bounds(1, 7, run_time = 2)
        self.play(
            self.T_label_group[0].set_fill, None, 0,
            *list(map(FadeIn, numbers))
        )
        self.play(
            Transform(self.integral, new_integral),
            Transform(self.antideriv_diff, new_antideriv_diff),
        )
        self.wait(3)
        for part in self.antideriv_diff[1::2]:
            self.play(Indicate(part, scale_factor = 1.1))
            self.wait()

    def add_constant_to_antiderivative(self):
        antideriv_diff = self.antideriv_diff
        plus_fives = VGroup(*[TexMobject("+5") for i in range(2)])
        plus_fives.set_color(YELLOW)
        for five, part in zip(plus_fives, antideriv_diff[1::2]):
            five.next_to(part, DOWN)
        group = VGroup(
            plus_fives[0],
            antideriv_diff[2].copy(),
            plus_fives[1]
        )

        self.play(Write(plus_fives, run_time = 2))
        self.wait(2)
        self.play(
            group.arrange,
            group.next_to, antideriv_diff, DOWN, MED_LARGE_BUFF
        )
        self.wait()
        self.play(FadeOut(group, run_time = 2))
        self.wait()

    #####

    def get_integral(self, lower_bound, upper_bound):
        result = TexMobject(
            "\\int", "^"+upper_bound, "_"+lower_bound, 
            "t(8-t)", "\\,dt"
        )
        result.next_to(self.graph_origin, RIGHT, MED_LARGE_BUFF)
        result.to_edge(UP)
        return result

    def get_antiderivative_difference(self, lower_bound, upper_bound):
        strings = []
        for bound in upper_bound, lower_bound:
            try:
                d = int(bound)
                strings.append("(%d)"%d)
            except:
                strings.append(bound)
        parts = []
        for s in strings:
            part = TexMobject(
                "\\left(",
                "4", s, "^2", "-", "\\frac{1}{3}", s, "^3"
                "\\right))"
            )
            part.set_color_by_tex(s, YELLOW, substring = False)
            parts.append(part)
        result = VGroup(
            TexMobject("="), parts[0], 
            TexMobject("-"), parts[1],
        )
        result.left_part, result.right_part = parts
        result.arrange(RIGHT)
        result.scale(0.9)
        result.next_to(self.integral, RIGHT)
        return result

class FundamentalTheorem(GraphScene):
    CONFIG = {
        "lower_bound" : 1,
        "upper_bound" : 7,
        "lower_bound_color" : RED,
        "upper_bound_color" : GREEN,
        "n_riemann_iterations" : 6,
    }

    def construct(self):
        self.add_graph_and_integral()
        self.show_f_dx_sum()
        self.show_rects_approaching_area()
        self.write_antiderivative()
        self.write_fundamental_theorem_of_calculus()
        self.show_integral_considering_continuum()
        self.show_antiderivative_considering_bounds()

    def add_graph_and_integral(self):
        self.setup_axes()
        integral = TexMobject("\\int", "^b", "_a", "f(x)", "\\,dx")
        integral.next_to(ORIGIN, LEFT)
        integral.to_edge(UP)
        integral.set_color_by_tex("a", self.lower_bound_color)
        integral.set_color_by_tex("b", self.upper_bound_color)
        graph = self.get_graph(
            lambda x : -0.01*x*(x-3)*(x-6)*(x-12) + 3,
        )
        self.add(integral, graph)
        self.graph = graph
        self.integral = integral

        self.bound_labels = VGroup()
        self.v_lines = VGroup()
        for bound, tex in (self.lower_bound, "a"), (self.upper_bound, "b"):
            label = integral.get_part_by_tex(tex).copy()
            label.scale(1.5)
            label.next_to(self.coords_to_point(bound, 0), DOWN)
            v_line = self.get_vertical_line_to_graph(
                bound, graph, color = label.get_color()
            )

            self.bound_labels.add(label)
            self.v_lines.add(v_line)
            self.add(label, v_line)

    def show_f_dx_sum(self):
        kwargs = {
            "x_min" : self.lower_bound,
            "x_max" : self.upper_bound,
            "fill_opacity" : 0.75,
            "stroke_width" : 0.25,
        }
        low_opacity = 0.25
        start_rect_index = 3
        num_shown_sum_steps = 5
        last_rect_index = start_rect_index + num_shown_sum_steps + 1

        self.rect_list = self.get_riemann_rectangles_list(
            self.graph, self.n_riemann_iterations, **kwargs
        )
        rects = self.rects = self.rect_list[0]
        rects.save_state()

        start_rect = rects[start_rect_index]
        f_brace = Brace(start_rect, LEFT, buff = 0)
        dx_brace = Brace(start_rect, DOWN, buff = 0)
        f_brace.label = f_brace.get_text("$f(x)$")
        dx_brace.label = dx_brace.get_text("$dx$")

        flat_rects = self.get_riemann_rectangles(
            self.get_graph(lambda x : 0), dx = 0.5, **kwargs
        )

        self.transform_between_riemann_rects(
            flat_rects, rects, 
            replace_mobject_with_target_in_scene = True,
        )
        self.play(*[
            ApplyMethod(
                rect.set_fill, None, 
                1 if rect is start_rect else low_opacity
            )
            for rect in rects
        ])
        self.play(*it.chain(
            list(map(GrowFromCenter, [f_brace, dx_brace])),
            list(map(Write, [f_brace.label, dx_brace.label])),
        ))
        self.wait()
        for i in range(start_rect_index+1, last_rect_index):
            self.play(
                rects[i-1].set_fill, None, low_opacity,
                rects[i].set_fill, None, 1,
                f_brace.set_height, rects[i].get_height(),
                f_brace.next_to, rects[i], LEFT, 0,
                dx_brace.next_to, rects[i], DOWN, 0,
                *[
                    MaintainPositionRelativeTo(brace.label, brace)
                    for brace in (f_brace, dx_brace)
                ]
            )
        self.wait()
        self.play(*it.chain(
            list(map(FadeOut, [
                f_brace, dx_brace, 
                f_brace.label, dx_brace.label
            ])),
            [rects.set_fill, None, kwargs["fill_opacity"]]
        ))

    def show_rects_approaching_area(self):
        for new_rects in self.rect_list:
            self.transform_between_riemann_rects(
                self.rects, new_rects
            )

    def write_antiderivative(self):
        deriv = TexMobject(
            "{d", "F", "\\over\\,", "dx}", "(x)", "=", "f(x)"
        )
        deriv_F = deriv.get_part_by_tex("F")
        deriv.next_to(self.integral, DOWN, MED_LARGE_BUFF)
        rhs = TexMobject(*"=F(b)-F(a)")
        rhs.set_color_by_tex("a", self.lower_bound_color)
        rhs.set_color_by_tex("b", self.upper_bound_color)
        rhs.next_to(self.integral, RIGHT)

        self.play(Write(deriv))
        self.wait(2)
        self.play(*it.chain(
            [
                ReplacementTransform(deriv_F.copy(), part)
                for part in rhs.get_parts_by_tex("F")
            ],
            [
                Write(VGroup(*rhs.get_parts_by_tex(tex)))
                for tex in "=()-"
            ]
        ))
        for tex in "b", "a":
            self.play(ReplacementTransform(
                self.integral.get_part_by_tex(tex).copy(),
                rhs.get_part_by_tex(tex)
            ))
            self.wait()
        self.wait(2)

        self.deriv = deriv
        self.rhs = rhs

    def write_fundamental_theorem_of_calculus(self):
        words = TextMobject("""
            Fundamental 
            theorem of 
            calculus
        """)
        words.to_edge(RIGHT)

        self.play(Write(words))
        self.wait()

    def show_integral_considering_continuum(self):
        self.play(*[
            ApplyMethod(mob.set_fill, None, 0.2)
            for mob in (self.deriv, self.rhs)
        ])
        self.play(
            self.rects.restore,
            run_time = 3,
            rate_func = there_and_back
        )
        self.wait()
        for x in range(2):
            self.play(*[
                ApplyFunction(
                    lambda m : m.shift(MED_SMALL_BUFF*UP).set_fill(opacity = 1),
                    rect,
                    run_time = 3,
                    rate_func = squish_rate_func(
                        there_and_back,
                        alpha, alpha+0.2
                    )
                )
                for rect, alpha in zip(
                    self.rects, 
                    np.linspace(0, 0.8, len(self.rects))
                )
            ])
        self.wait()

    def show_antiderivative_considering_bounds(self):
        self.play(
            self.integral.set_fill, None, 0.5,
            self.deriv.set_fill, None, 1,
            self.rhs.set_fill, None, 1,
        )
        for label, line in reversed(list(zip(self.bound_labels, self.v_lines))):
            new_line = line.copy().set_color(YELLOW)
            label.save_state()
            self.play(label.set_color, YELLOW)
            self.play(ShowCreation(new_line))
            self.play(ShowCreation(line))
            self.remove(new_line)
            self.play(label.restore)
        self.wait()
        self.play(self.integral.set_fill, None, 1)
        self.wait(3)

class LetsRecap(TeacherStudentsScene):
    def construct(self):
        self.teacher_says(
            "Let's recap",
            target_mode = "hesitant",
        )
        self.change_student_modes(*["happy"]*3)
        self.wait(3)

class NegativeArea(GraphScene):
    CONFIG = {
        "x_axis_label" : "Time",
        "y_axis_label" : "Velocity",
        "graph_origin" : 1.5*DOWN + 5*LEFT,
        "y_min" : -3,
        "y_max" : 7,
        "small_dx" : 0.01,
        "sample_input" : 5,
    }
    def construct(self):
        self.setup_axes()
        self.add_graph_and_area()
        self.write_negative_area()
        self.show_negative_point()
        self.show_car_going_backwards()
        self.write_v_dt()
        self.show_rectangle()
        self.write_signed_area()

    def add_graph_and_area(self):
        graph = self.get_graph(
            lambda x : -0.02*(x+1)*(x-3)*(x-7)*(x-10),
            x_min = 0,
            x_max = 8,
            color = VELOCITY_COLOR
        )
        area = self.get_riemann_rectangles(
            graph, 
            x_min = 0,
            x_max = 8,
            dx = self.small_dx,
            start_color = BLUE_D,
            end_color = BLUE_D,
            fill_opacity = 0.75,
            stroke_width = 0,
        )

        self .play(
            ShowCreation(graph),
            FadeIn(
                area, 
                run_time = 2,
                lag_ratio = 0.5,
            )
        )

        self.graph = graph
        self.area = area

    def write_negative_area(self):
        words = TextMobject("Negative area")
        words.set_color(RED)
        words.next_to(
            self.coords_to_point(7, -2),
            RIGHT,
        )
        arrow = Arrow(words, self.coords_to_point(
            self.sample_input, -1,
        ))

        self.play(
            Write(words, run_time = 2),
            ShowCreation(arrow)
        )
        self.wait(2)
        self.play(*list(map(FadeOut, [self.area, arrow])))

        self.negative_area_words = words

    def show_negative_point(self):
        v_line = self.get_vertical_line_to_graph(
            self.sample_input, self.graph,
            color = RED
        )
        self.play(ShowCreation(v_line))
        self.wait()
        self.v_line = v_line

    def show_car_going_backwards(self):
        car = Car()
        start_point = 3*RIGHT + 2*UP
        end_point = start_point + LEFT
        nudged_end_point = end_point + MED_SMALL_BUFF*LEFT
        car.move_to(start_point)
        arrow = Arrow(RIGHT, LEFT, color = RED)
        arrow.next_to(car, UP+LEFT)
        arrow.shift(MED_LARGE_BUFF*RIGHT)

        self.play(FadeIn(car))
        self.play(ShowCreation(arrow))
        self.play(MoveCar(
            car, end_point, 
            moving_forward = False,
            run_time = 3
        ))
        self.wait()
        ghost_car = car.copy().fade()
        right_nose_line = self.get_car_nose_line(car)
        self.play(ShowCreation(right_nose_line))
        self.add(ghost_car)
        self.play(MoveCar(
            car, nudged_end_point,
            moving_forward = False
        ))
        left_nose_line = self.get_car_nose_line(car)
        self.play(ShowCreation(left_nose_line))

        self.nose_lines = VGroup(left_nose_line, right_nose_line)
        self.car = car
        self.ghost_car = ghost_car

    def write_v_dt(self):
        brace = Brace(self.nose_lines, DOWN, buff = 0)
        equation = TexMobject("ds", "=", "v(t)", "dt")
        equation.next_to(brace, DOWN, SMALL_BUFF, LEFT)
        equation.set_color_by_tex("ds", DISTANCE_COLOR)
        equation.set_color_by_tex("dt", TIME_COLOR)

        negative = TextMobject("Negative")
        negative.set_color(RED)
        negative.next_to(equation.get_corner(UP+RIGHT), UP, LARGE_BUFF)
        ds_arrow, v_arrow = arrows = VGroup(*[
            Arrow(
                negative.get_bottom(),
                equation.get_part_by_tex(tex).get_top(),
                color = RED,
            )
            for tex in ("ds", "v(t)")
        ])

        self.play(
            GrowFromCenter(brace),
            Write(equation)
        )
        self.wait()
        self.play(FadeIn(negative))
        self.play(ShowCreation(v_arrow))
        self.wait(2)
        self.play(ReplacementTransform(
            v_arrow.copy(),
            ds_arrow
        ))
        self.wait(2)

        self.ds_equation = equation
        self.negative_word = negative
        self.negative_word_arrows = arrows

    def show_rectangle(self):
        rect_list = self.get_riemann_rectangles_list(
            self.graph, x_min = 0, x_max = 8,
            n_iterations = 6,
            start_color = BLUE_D,
            end_color = BLUE_D,
            fill_opacity = 0.75,
        )
        rects = rect_list[0]
        rect = rects[len(rects)*self.sample_input//8]

        dt_brace = Brace(rect, UP, buff = 0)
        v_brace = Brace(rect, LEFT, buff = 0)
        dt_label = dt_brace.get_text("$dt$", buff = SMALL_BUFF)
        dt_label.set_color(YELLOW)
        v_label = v_brace.get_text("$v(t)$", buff = SMALL_BUFF)
        v_label.add_background_rectangle()

        self.play(FadeOut(self.v_line), FadeIn(rect))
        self.play(
            GrowFromCenter(dt_brace), 
            GrowFromCenter(v_brace), 
            Write(dt_label),
            Write(v_label),
        )
        self.wait(2)
        self.play(*it.chain(
            [FadeIn(r) for r in rects if r is not rect],
            list(map(FadeOut, [
                dt_brace, v_brace, dt_label, v_label
            ]))
        ))
        self.wait()
        for new_rects in rect_list[1:]:
            self.transform_between_riemann_rects(rects, new_rects)
        self.wait()

    def write_signed_area(self):
        words = TextMobject("``Signed area''")
        words.next_to(self.coords_to_point(self.sample_input, 0), UP)
        symbols = VGroup(*[
            TexMobject(sym).move_to(self.coords_to_point(*coords))
            for sym, coords in [
                ("+", (1, 2)),
                ("-", (5, -1)),
                ("+", (7.6, 0.5)),
            ]
        ])
        self.play(Write(words))
        self.play(Write(symbols))
        self.wait()

    ####

    def get_car_nose_line(self, car):
        line = DashedLine(car.get_top(), car.get_bottom())
        line.move_to(car.get_right())
        return line

class NextVideo(TeacherStudentsScene):
    def construct(self):
        series = VideoSeries()
        series.to_edge(UP)
        next_video = series[8]
        integral = TexMobject("\\int")
        integral.next_to(next_video, DOWN, LARGE_BUFF)

        self.play(FadeIn(series, lag_ratio = 0.5))
        self.play(
            next_video.set_color, YELLOW,
            next_video.shift, next_video.get_height()*DOWN/2,
            self.teacher.change_mode, "raise_right_hand"
        )
        self.play(Write(integral))
        self.wait(5)

class Chapter8PatreonThanks(PatreonThanks):
    CONFIG = {
        "specific_patrons" : [
            "Ali Yahya",
            "CrypticSwarm",
            "Kaustuv DeBiswas",
            "Kathryn Schmiedicke",
            "Karan Bhargava",
            "Ankit Agarwal",
            "Yu Jun",
            "Dave Nicponski",
            "Damion Kistler",
            "Juan Benet",
            "Othman Alikhan",
            "Markus Persson",
            "Dan Buchoff",
            "Derek Dai",
            "Joseph John Cox",
            "Luc Ritchie",
            "Robert Teed",
            "Jason Hise",
            "Meshal Alshammari",
            "Bernd Sing",
            "Nils Schneider",
            "James Thornton",
            "Mustafa Mahdi",
            "Jonathan Eppele",
            "Mathew Bramson",
            "Jerry Ling",
            "Mark Govea",
            "Vecht",
            "Shimin Kuang",
            "Rish Kundalia",
            "Achille Brighton",
            "Ripta Pasay",
        ]
    }

class Thumbnail(Chapter1Thumbnail):
    CONFIG = {
        "x_axis_label" : "",
        "y_axis_label" : "",
        "graph_origin" : 1.5*DOWN + 4*LEFT,
        "y_axis_height" : 5,
        "x_max" : 5,
        "x_axis_width" : 11,
    }
    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,
        )
        words = TextMobject("Integrals")
        words.set_width(8)
        words.to_edge(UP)

        self.add(graph, rects, words)












