From 4e3f7292af6e7ba0583b75a2ac770102f0fd16ba Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Fri, 15 Jul 2016 18:16:06 -0700 Subject: [PATCH] Through coordinates section in chapter 1 --- animation/transform.py | 2 +- eola/chapter1.py | 613 ++++++++++++++++++++++++++++++---- eola/utils.py | 120 ++++++- helpers.py | 2 +- mobject/mobject.py | 26 +- mobject/vectorized_mobject.py | 5 + topics/characters.py | 24 +- topics/geometry.py | 2 + topics/number_line.py | 25 +- 9 files changed, 742 insertions(+), 77 deletions(-) diff --git a/animation/transform.py b/animation/transform.py index 51a9d64a..0cac5435 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -117,7 +117,7 @@ class Rotate(ApplyMethod): "in_place" : False, } def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs): - kwargs["path_func"] = path_along_arc(angle) + kwargs["path_arc"] = angle digest_config(self, kwargs, locals()) if self.in_place: method = mobject.rotate_in_place diff --git a/eola/chapter1.py b/eola/chapter1.py index 72d352ec..bd353dfc 100644 --- a/eola/chapter1.py +++ b/eola/chapter1.py @@ -51,12 +51,42 @@ class OpeningQuote(Scene): self.play(Write(author, run_time = 4)) self.dither() -class IntroVector(Scene): + +class DifferentConceptions(Scene): def construct(self): + physy = Physicist() + mathy = Mathematician(mode = "pondering") + compy = ComputerScientist() + creatures = [physy, compy, mathy] + physy.title = TextMobject("Physics student").to_corner(DOWN+LEFT) + compy.title = TextMobject("CS student").to_corner(DOWN+RIGHT) + mathy.title = TextMobject("Mathematician").to_edge(DOWN) + names = VMobject(physy.title, mathy.title, compy.title) + names.arrange_submobjects(RIGHT, buff = 1) + names.to_corner(DOWN+LEFT) + for pi in creatures: + pi.next_to(pi.title, UP) + + vector, symbol, coordinates = self.intro_vector() + for pi in creatures: + self.play( + Write(pi.title), + FadeIn(pi), + run_time = 1 + ) + self.dither(2) + self.remove(symbol, coordinates) + self.physics_conception(creatures, vector) + self.cs_conception(creatures) + self.handle_mathy(creatures) + + def intro_vector(self): plane = NumberPlane() - labels = plane.get_coordinate_labels() + labels = VMobject(*plane.get_coordinate_labels()) vector = Vector(RIGHT+2*UP, color = YELLOW) coordinates = vector_coordinate_label(vector) + symbol = TexMobject("\\vec{\\textbf{v}}") + symbol.shift(0.5*(RIGHT+UP)) self.play(ShowCreation( plane, @@ -67,71 +97,542 @@ class IntroVector(Scene): vector, submobject_mode = "one_at_a_time" )) - self.play(Write(VMobject(*labels)), Write(coordinates)) + self.play( + Write(labels), + Write(coordinates), + Write(symbol) + ) + self.dither(2) + self.play( + FadeOut(plane), + FadeOut(labels), + ApplyMethod(vector.shift, 4*LEFT+UP), + ApplyMethod(coordinates.shift, 2.5*RIGHT+0.5*DOWN), + ApplyMethod(symbol.shift, 0.5*(UP+LEFT)) + ) + self.remove(plane, labels) + return vector, symbol, coordinates + + def physics_conception(self, creatures, original_vector): + self.fade_all_but(creatures, 0) + physy, compy, mathy = creatures + + vector = Vector(2*RIGHT) + vector.next_to(physy, UP+RIGHT) + brace = Brace(vector, DOWN) + length = TextMobject("Length") + length.next_to(brace, DOWN) + group = VMobject(vector, brace, length) + group.rotate_in_place(np.pi/6) + vector.get_center = lambda : vector.get_start() + + direction = TextMobject("Direction") + direction.next_to(vector, RIGHT) + direction.shift(UP) + + two_dimensional = TextMobject("Two-dimensional") + three_dimensional = TextMobject("Three-dimensional") + two_dimensional.to_corner(UP+RIGHT) + three_dimensional.to_corner(UP+RIGHT) + + random_vectors = VMobject(*[ + Vector( + random.uniform(-2, 2)*RIGHT + \ + random.uniform(-2, 2)*UP + ).shift( + random.uniform(0, 4)*RIGHT + \ + random.uniform(-1, 2)*UP + ).highlight(random_color()) + for x in range(5) + ]) + + self.play( + Transform(original_vector, vector), + ApplyMethod(physy.change_mode, "speaking") + ) + self.remove(original_vector) + self.add(vector ) + self.dither() + self.play( + GrowFromCenter(brace), + Write(length), + run_time = 1 + ) + self.dither() + self.remove(brace, length) + self.play( + Rotate(vector, np.pi/3, in_place = True), + Write(direction), + run_time = 1 + ) + for angle in -2*np.pi/3, np.pi/3: + self.play(Rotate( + vector, angle, + in_place = True, + run_time = 1 + )) + self.play(ApplyMethod(physy.change_mode, "plain")) + self.remove(direction) + for point in 2*UP, 4*RIGHT, ORIGIN: + self.play(ApplyMethod(vector.move_to, point)) + self.dither() + self.play( + Write(two_dimensional), + ApplyMethod(physy.change_mode, "pondering"), + ShowCreation(random_vectors, submobject_mode = "lagged_start"), + run_time = 1 + ) + self.dither(2) + self.remove(random_vectors, vector) + self.play(Transform(two_dimensional, three_dimensional)) + self.dither(5) + self.remove(two_dimensional) + self.restore_creatures(creatures) + + def cs_conception(self, creatures): + self.fade_all_but(creatures, 1) + physy, compy, mathy = creatures + + title = TextMobject("Vectors $\\Leftrightarrow$ lists of numbers") + title.to_edge(UP) + + vectors = VMobject(*map(matrix_to_mobject, [ + [2, 1], + [5, 0, 0, -3], + [2.3, -7.1, 0.1], + ])) + vectors.arrange_submobjects(RIGHT, buff = 1) + vectors.to_edge(LEFT) + + self.play( + ApplyMethod(compy.change_mode, "sassy"), + Write(title, run_time = 1) + ) + self.play(Write(vectors)) + self.dither() + self.play(ApplyMethod(compy.change_mode, "pondering")) + self.house_example(vectors, title) + self.restore_creatures(creatures) + + + def house_example(self, starter_mobject, title): + house = SVGMobject("house") + house.set_stroke(width = 0) + house.set_fill(BLUE_C, opacity = 1) + house.scale_to_fit_height(3) + house.center() + square_footage_words = TextMobject("Square footage:") + price_words = TextMobject("Price: ") + square_footage = TexMobject("2{,}600\\text{ ft}^2") + price = TextMobject("\\$300{,}000") + + house.to_edge(LEFT).shift(UP) + square_footage_words.next_to(house, RIGHT) + square_footage_words.shift(0.5*UP) + square_footage_words.highlight(RED) + price_words.next_to(square_footage_words, DOWN, aligned_edge = LEFT) + price_words.highlight(GREEN) + square_footage.next_to(square_footage_words) + square_footage.highlight(RED) + price.next_to(price_words) + price.highlight(GREEN) + + vector = Matrix([square_footage.copy(), price.copy()]) + vector.next_to(house, RIGHT).shift(0.25*UP) + new_square_footage, new_price = vector.get_mob_matrix().flatten() + not_equals = TexMobject("\\ne") + not_equals.next_to(vector) + alt_vector = Matrix([ + TextMobject("300{,}000\\text{ ft}^2").highlight(RED), + TextMobject("\\$2{,}600").highlight(GREEN) + ]) + alt_vector.next_to(not_equals) + + brace = Brace(vector, RIGHT) + two_dimensional = TextMobject("2 dimensional") + two_dimensional.next_to(brace) + brackets = vector.get_brackets() + + self.play(Transform(starter_mobject, house)) + self.remove(starter_mobject) + self.add(house) + self.add(square_footage_words) + self.play(Write(square_footage, run_time = 2)) + self.add(price_words) + self.play(Write(price, run_time = 2)) + self.dither() + self.play( + FadeOut(square_footage_words), FadeOut(price_words), + Transform(square_footage, new_square_footage), + Transform(price, new_price), + Write(brackets), + run_time = 1 + ) + self.remove(square_footage_words, price_words) + self.dither() + self.play( + Write(not_equals), + Write(alt_vector), + run_time = 1 + ) + self.dither() + self.play(FadeOut(not_equals), FadeOut(alt_vector)) + self.remove(not_equals, alt_vector) + self.dither() + self.play( + GrowFromCenter(brace), + Write(two_dimensional), + run_time = 1 + ) + self.dither() + + everything = VMobject( + house, square_footage, price, brackets, brace, + two_dimensional, title + ) + self.play(ApplyMethod(everything.shift, 2*SPACE_WIDTH*LEFT)) + self.remove(everything) + + + def handle_mathy(self, creatures): + self.fade_all_but(creatures, 2) + physy, compy, mathy = creatures + + v_color = YELLOW + w_color = BLUE + sum_color = GREEN + + v_arrow = Vector([1, 1]) + w_arrow = Vector([2, 1]) + w_arrow.shift(v_arrow.get_end()) + sum_arrow = Vector(w_arrow.get_end()) + arrows = VMobject(v_arrow, w_arrow, sum_arrow) + arrows.scale(0.7) + arrows.to_edge(LEFT, buff = 2) + + v_array = matrix_to_mobject([3, -5]) + w_array = matrix_to_mobject([2, 1]) + sum_array = matrix_to_mobject(["3+2", "-5+1"]) + arrays = VMobject( + v_array, TexMobject("+"), w_array, TexMobject("="), sum_array + ) + arrays.arrange_submobjects(RIGHT) + arrays.scale(0.5) + arrays.to_edge(RIGHT).shift(UP) + + v_sym = TexMobject("\\vec{\\textbf{v}}") + w_sym = TexMobject("\\vec{\\textbf{w}}") + syms = VMobject(v_sym, TexMobject("+"), w_sym) + syms.arrange_submobjects(RIGHT) + syms.center().shift(2*UP) + + VMobject(v_arrow, v_array, v_sym).highlight(v_color) + VMobject(w_arrow, w_array, w_sym).highlight(w_color) + VMobject(sum_arrow, sum_array).highlight(sum_color) + + self.play( + Write(syms), Write(arrays), + ShowCreation(arrows, submobject_mode = "one_at_a_time"), + ApplyMethod(mathy.change_mode, "pondering"), + run_time = 2 + ) + self.play(Blink(mathy)) + self.add_scaling(arrows, syms, arrays) + + def add_scaling(self, arrows, syms, arrays): + s_arrows = VMobject( + TexMobject("2"), Vector([1, 1]).highlight(YELLOW), + TexMobject("="), Vector([2, 2]).highlight(WHITE) + ) + s_arrows.arrange_submobjects(RIGHT) + s_arrows.scale(0.75) + s_arrows.next_to(arrows, DOWN) + + s_arrays = VMobject( + TexMobject("2"), + matrix_to_mobject([3, -5]).highlight(YELLOW), + TextMobject("="), + matrix_to_mobject(["2(3)", "2(-5)"]) + ) + s_arrays.arrange_submobjects(RIGHT) + s_arrays.scale(0.5) + s_arrays.next_to(arrays, DOWN) + + s_syms = TexMobject(["2", "\\vec{\\textbf{v}}"]) + s_syms.split()[-1].highlight(YELLOW) + s_syms.next_to(syms, DOWN) + + self.play( + Write(s_arrows), Write(s_arrays), Write(s_syms), + run_time = 2 + ) self.dither() -class DifferentConceptions(Scene): + + def fade_all_but(self, creatures, index): + self.play(*[ + FadeOut(VMobject(pi, pi.title)) + for pi in creatures[:index] + creatures[index+1:] + ]) + + def restore_creatures(self, creatures): + self.play(*[ + ApplyFunction(lambda m : m.change_mode("plain").highlight(m.color), pi) + for pi in creatures + ] + [ + ApplyMethod(pi.title.set_fill, WHITE, 1.0) + for pi in creatures + ]) + + + +class HelpsToHaveOneThought(Scene): def construct(self): - physy = Physicist() - mathy = Mathematician(mode = "pondering") - compy = ComputerScientist() - people = [physy, compy, mathy] - physy.name = TextMobject("Physics student").to_corner(DOWN+LEFT) - compy.name = TextMobject("CS student").to_corner(DOWN+RIGHT) - mathy.name = TextMobject("Mathematician").to_edge(DOWN) - names = VMobject(physy.name, mathy.name, compy.name) - names.arrange_submobjects(RIGHT, buff = 1) - names.to_corner(DOWN+LEFT) - for pi in people: - pi.next_to(pi.name, UP) - self.add(pi) - - for pi in people: - self.play(Write(pi.name), run_time = 1) - self.preview_conceptions(people) - self.physics_conception(people) - self.cs_conception(people) - self.handle_mathy(people) - - - def preview_conceptions(self, people): - arrow = Vector(2*RIGHT+ UP) - array = matrix_to_mobject([[2], [1]]) - tex = TextMobject(""" - Set $V$ with operations \\\\ - $a : V \\times V \\to V$ and \\\\ - $s : \\mathds{R} \\times V \\to V$ such that... - """) - physy, compy, mathy = people - physy.bubble = physy.get_bubble("speech") - compy.bubble = compy.get_bubble("speech") - mathy.bubble = mathy.get_bubble(width = 4) - - for pi, sym in zip(people, [arrow, array, tex]): - pi.bubble.set_fill(BLACK, opacity = 1.0) - pi.bubble.add_content(sym) - self.play(FadeIn(pi.bubble)) - self.dither() - for pi in people: - self.remove(pi.bubble) - - - def physics_conception(self, people): - pass - - def cs_conception(self, people): - pass - - def handle_mathy(self, people): - pass + morty = Mortimer() + morty.to_corner(DOWN+RIGHT) + morty.look(DOWN+LEFT) + new_morty = morty.copy().change_mode("speaking") + new_morty.look(DOWN+LEFT) + randys = VMobject(*[ + Randolph(color = color).scale(0.8) + for color in BLUE_D, BLUE_C, BLUE_E + ]) + randys.arrange_submobjects(RIGHT) + randys.to_corner(DOWN+LEFT) + randy = randys.split()[1] + + speech_bubble = morty.get_bubble("speech") + words = TextMobject("Think of some vector...") + speech_bubble.position_mobject_inside(words) + thought_bubble = randy.get_bubble() + arrow = Vector([2, 1]).scale(0.7) + or_word = TextMobject("or") + array = Matrix([2, 1]).scale(0.5) + q_mark = TextMobject("?") + thought = VMobject(arrow, or_word, array, q_mark) + thought.arrange_submobjects(RIGHT, buff = 0.2) + thought_bubble.position_mobject_inside(thought) + thought_bubble.set_fill(BLACK, opacity = 1) + self.add(morty, randys) + self.play( + ShowCreation(speech_bubble), + Transform(morty, new_morty), + Write(words) + ) + self.dither(2) + self.play( + FadeOut(speech_bubble), + FadeOut(words), + ApplyMethod(randy.change_mode, "pondering"), + ShowCreation(thought_bubble), + Write(thought) + ) + self.dither(2) +class HowIWantYouToThinkAboutVectors(Scene): + def construct(self): + vector = Vector([-2, 3]) + plane = NumberPlane() + axis_labels = plane.get_axis_labels() + other_vectors = VMobject(*map(Vector, [ + [1, 2], [2, -1], [4, 0] + ])) + colors = [GREEN_B, MAROON_B, PINK] + for v, color in zip(other_vectors.split(), colors): + v.highlight(color) + shift_val = 4*RIGHT+DOWN + dot = Dot(radius = 0.1) + dot.highlight(RED) + tail_word = TextMobject("Tail") + tail_word.shift(0.5*DOWN+2.5*LEFT) + line = Line(tail_word, dot) + self.play(ShowCreation(vector, submobject_mode = "one_at_a_time")) + self.dither(2) + self.play( + ShowCreation(plane, summobject_mode = "lagged_start"), + Animation(vector) + ) + self.play(Write(axis_labels, run_time = 1)) + self.dither() + self.play( + GrowFromCenter(dot), + ShowCreation(line), + Write(tail_word, run_time = 1) + ) + self.dither() + self.play( + FadeOut(tail_word), + ApplyMethod(VMobject(dot, line).scale, 0.01) + ) + self.remove(tail_word, line, dot) + self.dither() + + self.play(ApplyMethod( + vector.shift, shift_val, + path_arc = 3*np.pi/2, + run_time = 3 + )) + self.play(ApplyMethod( + vector.shift, -shift_val, + rate_func = rush_into, + run_time = 0.5 + )) + self.dither(3) + + self.play(ShowCreation( + other_vectors, + submobject_mode = "one_at_a_time", + run_time = 3 + )) + self.dither(3) + + x_axis, y_axis = plane.get_axes().split() + x_label = axis_labels.split()[0] + x_axis = x_axis.copy() + x_label = x_label.copy() + everything = VMobject(*self.mobjects) + self.play( + FadeOut(everything), + Animation(x_axis), Animation(x_label) + ) + + +class ListsOfNumbersAddOn(Scene): + def construct(self): + arrays = VMobject(*map(matrix_to_mobject, [ + [-2, 3], [1, 2], [2, -1], [4, 0] + ])) + arrays.arrange_submobjects(buff = 0.4) + arrays.scale(2) + self.play(Write(arrays)) + self.dither(2) + + +class CoordinateSystemWalkthrough(VectorCoordinateScene): + def construct(self): + self.introduce_coordinate_plane() + self.show_vector_coordinates() + self.coords_to_vector([3, -1]) + self.vector_to_coords([-2, -1.5], integer_labels = False) + + def introduce_coordinate_plane(self): + plane = NumberPlane() + x_axis, y_axis = plane.get_axes().copy().split() + x_label, y_label = plane.get_axis_labels().split() + number_line = NumberLine(tick_frequency = 1) + x_tick_marks = number_line.get_tick_marks() + y_tick_marks = x_tick_marks.copy().rotate(np.pi/2) + tick_marks = VMobject(x_tick_marks, y_tick_marks) + tick_marks.highlight(WHITE) + plane_lines = filter( + lambda m : isinstance(m, Line), + plane.submobject_family() + ) + origin_words = TextMobject("Origin") + origin_words.shift(2*UP+2*LEFT) + dot = Dot(radius = 0.1).highlight(RED) + line = Line(origin_words.get_bottom(), dot.get_corner(UP+LEFT)) + + unit_brace = Brace(Line(RIGHT, 2*RIGHT)) + one = TexMobject("1").next_to(unit_brace, DOWN) + + self.add(x_axis, x_label) + self.dither() + self.play(ShowCreation(y_axis)) + self.play(Write(y_label, run_time = 1)) + self.dither(2) + self.play( + Write(origin_words), + GrowFromCenter(dot), + ShowCreation(line), + run_time = 1 + ) + self.dither(2) + self.play( + FadeOut(VMobject(origin_words, dot, line)) + ) + self.remove(origin_words, dot, line) + self.dither() + self.play( + ShowCreation(tick_marks, submobject_mode = "one_at_a_time") + ) + self.play( + GrowFromCenter(unit_brace), + Write(one, run_time = 1) + ) + self.dither(2) + self.remove(unit_brace, one) + self.play( + *map(GrowFromCenter, plane_lines) + [ + Animation(x_axis), Animation(y_axis) + ]) + self.dither() + self.play( + FadeOut(plane), + Animation(VMobject(x_axis, y_axis, tick_marks)) + ) + self.remove(plane) + self.add(tick_marks) + + def show_vector_coordinates(self): + starting_mobjects = list(self.mobjects) + + vector = Vector([-2, 3]) + x_line = Line(ORIGIN, -2*RIGHT) + y_line = Line(-2*RIGHT, -2*RIGHT+3*UP) + x_line.highlight(X_COLOR) + y_line.highlight(Y_COLOR) + + array = vector_coordinate_label(vector) + x_label, y_label = array.get_mob_matrix().flatten() + x_label_copy = x_label.copy() + x_label_copy.highlight(X_COLOR) + y_label_copy = y_label.copy() + y_label_copy.highlight(Y_COLOR) + + point = Dot(4*LEFT+2*UP) + point_word = TextMobject("(-4, 2) as \\\\ a point") + point_word.scale(0.7) + point_word.next_to(point, DOWN) + point.add(point_word) + + self.play(ShowCreation(vector, submobject_mode = "one_at_a_time")) + self.play(Write(array)) + self.dither(2) + self.play(ApplyMethod(x_label_copy.next_to, x_line, DOWN)) + self.play(ShowCreation(x_line)) + self.dither(2) + self.play(ApplyMethod(y_label_copy.next_to, y_line, LEFT)) + self.play(ShowCreation(y_line)) + self.dither(2) + self.play(FadeIn(point)) + self.dither() + self.play(ApplyFunction( + lambda m : m.scale_in_place(1.25).highlight(YELLOW), + array.get_brackets(), + rate_func = there_and_back + )) + self.dither() + self.play(FadeOut(point)) + self.remove(point) + self.dither() + self.clear() + self.add(*starting_mobjects) + + +class ThreeAxisLabels(Scene): + def construct(self): + z = TexMobject("z").scale(2) + z.show() + self.play(Write(z, run_time = 1)) + self.dither(2) diff --git a/eola/utils.py b/eola/utils.py index ac7b0935..eef022b4 100644 --- a/eola/utils.py +++ b/eola/utils.py @@ -5,17 +5,22 @@ from mobject import Mobject from mobject.vectorized_mobject import VMobject from mobject.tex_mobject import TexMobject, TextMobject from animation.transform import ApplyPointwiseFunction, Transform, \ - ApplyMethod, FadeOut -from animation.simple_animations import ShowCreation + ApplyMethod, FadeOut, ApplyFunction +from animation.simple_animations import ShowCreation, Write from topics.number_line import NumberPlane -from topics.geometry import Vector, Line, Circle +from topics.geometry import Vector, Line, Circle, Arrow from helpers import * VECTOR_LABEL_SCALE_VAL = 0.7 +X_COLOR = GREEN_C +Y_COLOR = RED_C + def matrix_to_tex_string(matrix): matrix = np.array(matrix).astype("string") + if matrix.ndim == 1: + matrix = matrix.reshape((matrix.size, 1)) n_rows, n_cols = matrix.shape prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols) suffix = "\\end{array} \\right]" @@ -35,7 +40,7 @@ def vector_coordinate_label(vector_mob, integer_labels = True, n_dim = 2): vect = vect.astype(int) vect = vect[:n_dim] vect = vect.reshape((n_dim, 1)) - label = matrix_to_mobject(vect) + label = Matrix(vect) label.scale(VECTOR_LABEL_SCALE_VAL) shift_dir = np.array(vector_mob.get_end()) @@ -146,7 +151,8 @@ class Matrix(VMobject): """ VMobject.__init__(self, **kwargs) matrix = np.array(matrix) - assert(matrix.ndim == 2) + if matrix.ndim == 1: + matrix = matrix.reshape((matrix.size, 1)) if not isinstance(matrix[0][0], Mobject): matrix = matrix.astype("string") matrix = self.string_matrix_to_mob_matrix(matrix) @@ -174,9 +180,6 @@ class Matrix(VMobject): mob.next_to(matrix[i-1][j], DOWN, self.v_buff) return self - def get_mob_matrix(self): - return self.mob_matrix - def add_brackets(self): bracket_pair = TexMobject("\\big[ \\big]") bracket_pair.scale(2) @@ -185,8 +188,15 @@ class Matrix(VMobject): l_bracket.next_to(self, LEFT) r_bracket.next_to(self, RIGHT) self.add(l_bracket, r_bracket) + self.brackets = VMobject(l_bracket, r_bracket) return self + def get_mob_matrix(self): + return self.mob_matrix + + def get_brackets(self): + return self.brackets + class NumericalMatrixMultiplication(Scene): CONFIG = { @@ -316,6 +326,100 @@ class NumericalMatrixMultiplication(Scene): +class VectorCoordinateScene(Scene): + def position_x_coordinate(self, x_coord, x_line, vector): + x_coord.next_to(x_line, -vector[1]*UP) + x_coord.highlight(X_COLOR) + return x_coord + + def position_y_coordinate(self, y_coord, y_line, vector): + y_coord.next_to(y_line, vector[0]*RIGHT) + y_coord.highlight(Y_COLOR) + return y_coord + + def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, cleanup = True): + starting_mobjects = list(self.mobjects) + array = Matrix(vector) + array.shift(coords_start) + arrow = Vector(vector) + x_line = Line(ORIGIN, vector[0]*RIGHT) + y_line = Line(x_line.get_end(), arrow.get_end()) + x_line.highlight(X_COLOR) + y_line.highlight(Y_COLOR) + x_coord, y_coord = array.get_mob_matrix().flatten() + + self.play(Write(array, run_time = 1)) + self.dither() + self.play(ApplyFunction( + lambda x : self.position_x_coordinate(x, x_line, vector), + x_coord + )) + self.play(ShowCreation(x_line)) + self.play( + ApplyFunction( + lambda y : self.position_y_coordinate(y, y_line, vector), + y_coord + ), + FadeOut(array.get_brackets()) + ) + self.play(ShowCreation(y_line)) + self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) + self.dither() + if cleanup: + self.clear() + self.add(*starting_mobjects) + + def vector_to_coords(self, vector, integer_labels = True, cleanup = True): + starting_mobjects = list(self.mobjects) + show_creation = False + if isinstance(vector, Arrow): + arrow = vector + vector = arrow.get_end()[:2] + else: + arrow = Vector(vector) + show_creation = True + array = vector_coordinate_label(arrow, integer_labels = integer_labels) + x_line = Line(ORIGIN, vector[0]*RIGHT) + y_line = Line(x_line.get_end(), arrow.get_end()) + x_line.highlight(X_COLOR) + y_line.highlight(Y_COLOR) + x_coord, y_coord = array.get_mob_matrix().flatten() + x_coord_start = self.position_x_coordinate( + x_coord.copy(), x_line, vector + ) + y_coord_start = self.position_y_coordinate( + y_coord.copy(), y_line, vector + ) + brackets = array.get_brackets() + + if show_creation: + self.play(ShowCreation(arrow, submobject_mode = "one_at_a_time")) + self.play( + ShowCreation(x_line), + Write(x_coord_start), + run_time = 1 + ) + self.play( + ShowCreation(y_line), + Write(y_coord_start), + run_time = 1 + ) + self.dither() + self.play( + Transform(x_coord_start, x_coord), + Transform(y_coord_start, y_coord), + Write(brackets), + run_time = 1 + ) + self.dither() + + self.remove(x_coord_start, y_coord_start) + self.add(x_coord, y_coord) + if cleanup: + self.clear() + self.add(*starting_mobjects) + + diff --git a/helpers.py b/helpers.py index 345c973c..c06c0749 100644 --- a/helpers.py +++ b/helpers.py @@ -431,7 +431,7 @@ def z_to_vector(vector): v = np.array(vector) / norm phi = np.arccos(v[2]) if any(v[:2]): - #projection of vector to {x^2 + y^2 = 1} + #projection of vector to unit circle axis_proj = v[:2] / np.linalg.norm(v[:2]) theta = np.arccos(axis_proj[0]) if axis_proj[1] < 0: diff --git a/mobject/mobject.py b/mobject/mobject.py index 43f83231..8d45508d 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -219,9 +219,14 @@ class Mobject(object): self.shift(target_point - anchor_point + buff*direction) return self - def stretch_to_fit(self, length, dim): + def stretch_to_fit(self, length, dim, stretch = True): old_length = self.length_over_dim(dim) - self.do_in_place(self.stretch, length/old_length, dim) + if old_length == 0: + return self + if stretch: + self.do_in_place(self.stretch, length/old_length, dim) + else: + self.do_in_place(self.scale, length/old_length) return self def stretch_to_fit_width(self, width): @@ -231,10 +236,19 @@ class Mobject(object): return self.stretch_to_fit(height, 1) def scale_to_fit_width(self, width): - return self.scale(width/self.get_width()) + return self.stretch_to_fit(width, 0, stretch = False) def scale_to_fit_height(self, height): - return self.scale(height/self.get_height()) + return self.stretch_to_fit(height, 1, stretch = False) + + def move_to(self, point_or_mobject, side_to_align = ORIGIN): + if isinstance(point_or_mobject, Mobject): + target = point_or_mobject.get_critical_point(side_to_align) + else: + target = point_or_mobject + anchor_point = self.get_critical_point(side_to_align) + self.shift(target - anchor_point) + return self def replace(self, mobject, stretch = False): if not mobject.get_num_points() and not mobject.submobjects: @@ -244,8 +258,8 @@ class Mobject(object): self.stretch_to_fit_width(mobject.get_width()) self.stretch_to_fit_height(mobject.get_height()) else: - self.scale(mobject.get_width()/self.get_width()) - self.center().shift(mobject.get_center()) + self.scale_to_fit_width(mobject.get_width()) + self.shift(mobject.get_center() - self.get_center()) return self def position_endpoints_on(self, start, end): diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 3e739dc9..4cd87e90 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -74,6 +74,11 @@ class VMobject(Mobject): self.set_stroke(color = color) return self + def fade(self, darkness = 0.5): + self.set_fill(opacity = 1-darkness) + Mobject.fade(self, darkness) + return self + def get_fill_color(self): try: self.fill_rgb[self.fill_rgb<0] = 0 diff --git a/topics/characters.py b/topics/characters.py index 8037386a..91ec91fa 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -77,18 +77,36 @@ class PiCreature(SVGMobject): def change_mode(self, mode): curr_center = self.get_center() curr_height = self.get_height() + looking_direction = None + looking_direction = self.get_looking_direction() should_be_flipped = self.is_flipped() self.__init__(mode) self.scale_to_fit_height(curr_height) self.shift(curr_center) - if should_be_flipped^self.is_flipped(): + self.look(looking_direction) + if should_be_flipped ^ self.is_flipped(): self.flip() return self - def look_left(self): - self.change_mode(self.mode + "_looking_left") + def look(self, direction): + x, y = direction[:2] + for pupil, eye in zip(self.pupils.split(), self.eyes.split()): + pupil.move_to(eye, side_to_align = direction) + if y > 0 and x != 0: # Look up and to a side + nudge_size = pupil.get_height()/4. + if x > 0: + nudge = nudge_size*(DOWN+LEFT) + else: + nudge = nudge_size*(DOWN+RIGHT) + pupil.shift(nudge) return self + def get_looking_direction(self): + return np.sign(np.round( + self.pupils.get_center() - self.eyes.get_center(), + decimals = 1 + )) + def is_flipped(self): return self.eyes.submobjects[0].get_center()[0] > \ self.eyes.submobjects[1].get_center()[0] diff --git a/topics/geometry.py b/topics/geometry.py index 1a2c7e6d..64c2da64 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -172,6 +172,8 @@ class Vector(Arrow): "buff" : 0, } def __init__(self, direction, **kwargs): + if len(direction) == 2: + direction = np.append(np.array(direction), 0) Arrow.__init__(self, ORIGIN, direction, **kwargs) class DoubleArrow(Arrow): diff --git a/topics/number_line.py b/topics/number_line.py index b3bf0b93..11ccff2e 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -28,7 +28,8 @@ class NumberLine(VMobject): def generate_points(self): self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT) - self.add(self.main_line) + self.tick_marks = VMobject() + self.add(self.main_line, self.tick_marks) for x in self.get_tick_numbers(): self.add_tick(x, self.tick_size) for x in self.numbers_with_elongated_ticks: @@ -37,12 +38,15 @@ class NumberLine(VMobject): self.shift(-self.number_to_point(self.number_at_center)) def add_tick(self, x, size): - self.add(Line( + self.tick_marks.add(Line( x*RIGHT+size*DOWN, x*RIGHT+size*UP, )) return self + def get_tick_marks(self): + return self.tick_marks + def get_tick_numbers(self): return np.arange(self.leftmost_tick, self.x_max, self.tick_frequency) @@ -160,6 +164,9 @@ class NumberPlane(VMobject): self.add(self.axes, self.main_lines, self.secondary_lines) self.stretch(self.space_unit_to_x_unit, 0) self.stretch(self.space_unit_to_y_unit, 1) + #Put x_axis before y_axis + y_axis, x_axis = self.axes.split() + self.axes = VMobject(x_axis, y_axis) def init_colors(self): VMobject.init_colors(self) @@ -206,6 +213,20 @@ class NumberPlane(VMobject): result.append(num) return result + def get_axes(self): + return self.axes + + def get_axis_labels(self, x_label = "x", y_label = "y"): + x_axis, y_axis = self.get_axes().split() + x_label_mob = TexMobject(x_label) + y_label_mob = TexMobject(y_label) + x_label_mob.next_to(x_axis, DOWN) + x_label_mob.to_edge(RIGHT) + y_label_mob.next_to(y_axis, RIGHT) + y_label_mob.to_edge(UP) + return VMobject(x_label_mob, y_label_mob) + + def add_coordinates(self, x_vals = None, y_vals = None): self.add(*self.get_coordinate_labels(x_vals, y_vals)) return self