From ac930952f151acf284f6e01e98e8f7256f29f9f4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 2 Nov 2015 19:09:55 -0800 Subject: [PATCH] Beginning transform KA article, many small fixes to the mobject refactor --- animation/animation.py | 3 +- animation/meta_animations.py | 1 - animation/simple_animations.py | 1 - animation/transform.py | 3 +- extract_scene.py | 6 +- mobject/mobject.py | 15 +- .../complex_multiplication_article.py | 6 +- old_projects/pythagorean_proof.py | 10 +- scene/scene.py | 15 +- topics/arithmetic.py | 2 +- topics/characters.py | 6 +- topics/combinatorics.py | 4 +- topics/complex_numbers.py | 2 +- topics/geometry.py | 4 +- topics/graph_theory.py | 4 +- topics/number_line.py | 66 +++++- transform_article.py | 217 ++++++++++++++++++ 17 files changed, 310 insertions(+), 55 deletions(-) create mode 100644 transform_article.py diff --git a/animation/animation.py b/animation/animation.py index d82c6641..82b05c8f 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -4,7 +4,6 @@ import numpy as np import warnings import time import os -import copy import progressbar import inspect @@ -21,7 +20,7 @@ class Animation(object): mobject = instantiate(mobject) assert(isinstance(mobject, Mobject)) digest_config(self, kwargs, locals()) - self.starting_mobject = copy.deepcopy(self.mobject) + self.starting_mobject = self.mobject.copy() if self.alpha_func is None: self.alpha_func = (lambda x : x) if self.name is None: diff --git a/animation/meta_animations.py b/animation/meta_animations.py index 489fa1a4..5673b7c1 100644 --- a/animation/meta_animations.py +++ b/animation/meta_animations.py @@ -1,6 +1,5 @@ import numpy as np import itertools as it -from copy import deepcopy from helpers import * from animation import Animation diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 2a39e4af..ed837cec 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -1,6 +1,5 @@ import numpy as np import itertools as it -from copy import deepcopy from helpers import * diff --git a/animation/transform.py b/animation/transform.py index c9258e66..896a9d43 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -46,6 +46,7 @@ class Transform(Animation): [self.mobject, self.starting_mobject, self.ending_mobject] ) for m, start, end in zip(*families): + # print m, start, end m.points = self.interpolation_function( start.points, end.points, alpha ) @@ -129,7 +130,7 @@ class ApplyFunction(Transform): Transform.__init__( self, mobject, - function(copy.deepcopy(mobject)), + function(mobject.copy()), **kwargs ) self.name = "ApplyFunctionTo"+str(mobject) diff --git a/extract_scene.py b/extract_scene.py index fac85ccc..5f8e98db 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -103,7 +103,7 @@ def handle_scene(scene, **config): scene.show_frame() path = os.path.join(MOVIE_DIR, config["movie_prefix"], "images") if not os.path.exists(path): - os.mkdir(path) + os.makedirs(path) scene.save_image(path, name) if config["write"]: scene.write_to_movie(os.path.join(config["movie_prefix"], name)) @@ -156,10 +156,10 @@ def get_scene_args(SceneClass, config): index = preset_extensions.index(config["args_extension"]) return [args_list[index]] if config["args_extension"] == "" : + if len(args_list) == 1: + return args_list name_to_args = dict(zip(preset_extensions, args_list)) return prompt_user_for_choice(name_to_args) - if len(args_list) == 1: - return args_list return [SceneClass.string_to_args(config["args_extension"])] def get_scene_classes(scene_names_to_classes, config): diff --git a/mobject/mobject.py b/mobject/mobject.py index 9b66dd67..a113982d 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -104,6 +104,8 @@ class Mobject(object): os.path.join(MOVIE_DIR, (name or str(self)) + ".png") ) + def copy(self): + return deepcopy(self) #### Fundamental operations ###### @@ -226,7 +228,7 @@ class Mobject(object): """ target_point = np.sign(direction) * (SPACE_WIDTH, SPACE_HEIGHT, 0) anchor_point = self.get_critical_point(direction) - self.shift(target - anchor_point - buff * np.array(direction)) + self.shift(target_point - anchor_point - buff * np.array(direction)) return self def to_corner(self, corner = LEFT+DOWN, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): @@ -437,16 +439,7 @@ class Mobject(object): else: larger, smaller = mobject1, mobject2 for sub_mob in larger.sub_mobjects[-diff:]: - center = sub_mob.get_center() - point_distances = np.apply_along_axis( - lambda p : np.linalg.norm(p - center), - 1, larger.points - ) - index = np.argmin(point_distances) - smaller.add(Point( - smaller.points[index], - color = Color(rgb = smaller.rgbs[index]) - )) + smaller.add(Point(sub_mob.get_center())) for m1, m2 in zip(mobject1.sub_mobjects, mobject2.sub_mobjects): Mobject.align_data(m1, m2) diff --git a/old_projects/complex_multiplication_article.py b/old_projects/complex_multiplication_article.py index 5095282f..b4076433 100644 --- a/old_projects/complex_multiplication_article.py +++ b/old_projects/complex_multiplication_article.py @@ -205,7 +205,7 @@ class DrawComplexAngleAndMagnitude(Scene): label.shift(point - edge + buff*dot_to_label_dir) label.highlight(YELLOW) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) def add_angle_label(self, number): @@ -214,7 +214,7 @@ class DrawComplexAngleAndMagnitude(Scene): radius = 0.2 ) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) def add_lines(self, tex_representation, number): point = self.plane.number_to_point(number) @@ -250,5 +250,5 @@ class DrawComplexAngleAndMagnitude(Scene): axis = OUT if point[1] > 0 else IN norm_label.next_to(brace, rotate_vector(point, np.pi/2, axis)) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) diff --git a/old_projects/pythagorean_proof.py b/old_projects/pythagorean_proof.py index e042a4fe..6d0b5d6a 100644 --- a/old_projects/pythagorean_proof.py +++ b/old_projects/pythagorean_proof.py @@ -165,7 +165,7 @@ class IndicateTroublePointFromParallelLines(AddParallelLines): vect = DOWN+RIGHT arrow = Arrow(circle.get_center()+2*vect, circle.get_boundary_point(vect)) arrow.highlight(circle.get_color()) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) class DrawAllThreeSquaresWithMoreTriangles(DrawAllThreeSquares): @@ -224,7 +224,7 @@ class IndicateBigRectangleTroublePoint(DrawAllThreeSquaresWithMoreTriangles): circle.shift(4*RIGHT) vect = DOWN+RIGHT arrow = Arrow(circle.get_center()+vect, circle.get_boundary_point(vect)) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) class ShowBigRectangleDimensions(DrawAllThreeSquaresWithMoreTriangles): args_list = [(10, False)] @@ -239,7 +239,7 @@ class ShowBigRectangleDimensions(DrawAllThreeSquaresWithMoreTriangles): b_plus_2a = TexMobject("b+2a").scale(TEX_MOB_SCALE_VAL) a_plus_2b.next_to(u_brace, DOWN) b_plus_2a.next_to(side_brace, LEFT) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) class FillInAreaOfBigRectangle(DrawAllThreeSquaresWithMoreTriangles): args_list = [(10, False)] @@ -267,7 +267,7 @@ class DrawOnlyABSquares(Scene): symobl.shift(mob.get_center()) self.add(symobl) triangle = Triangle() - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) class AddTriangleCopyToABSquares(DrawOnlyABSquares): def construct(self): @@ -388,7 +388,7 @@ class ZoomInOnTroublePoint(Scene): angle1_arc.filter_out(lambda (x, y, z) : not (x > 0 and y > 0 and y < x/3)) angle2_arc.filter_out(lambda (x, y, z) : not (x < 0 and y > 0 and y < -3*x)) - self.add_local_mobjects() + self.add_mobjects_among(locals().values()) self.add_elbow() if rotate: for mob in self.mobjects: diff --git a/scene/scene.py b/scene/scene.py index e781b29e..a52dece3 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -76,19 +76,20 @@ class Scene(object): self.mobjects = old_mobjects + list(mobjects) return self - def add_local_mobjects(self): + def add_mobjects_among(self, values): """ So a scene can just add all mobjects it's defined up to that point + by calling add_mobjects_among(locals().values()) """ - name_to_mob = get_caller_locals(lambda x : isinstance(x, Mobject)) - self.add(*name_to_mob.values()) + mobjects = filter(lambda x : isinstance(x, Mobject), values) + self.add(*mobjects) return self def remove(self, *mobjects): if not all_elements_are_instances(mobjects, Mobject): raise Exception("Removing something which is not a mobject") mobjects = filter(lambda m : m in self.mobjects, mobjects) - if len(mobjects): + if len(mobjects) == 0: return self.mobjects = filter(lambda m : m not in mobjects, self.mobjects) self.repaint_mojects() @@ -158,7 +159,11 @@ class Scene(object): run_time = animations[0].run_time for animation in animations: animation.set_run_time(run_time) - moving_mobjects = [anim.mobject for anim in animations] + moving_mobjects = [ + mobject + for anim in animations + for mobject in anim.mobject.get_full_submobject_family() + ] bundle = Mobject(*self.mobjects) static_mobjects = filter( diff --git a/topics/arithmetic.py b/topics/arithmetic.py index 83574a2e..061dff44 100644 --- a/topics/arithmetic.py +++ b/topics/arithmetic.py @@ -77,7 +77,7 @@ class RearrangeEquation(Scene): if sum(matches) > 1: base_mob = all_mobs[list(all_terms).index(term)] all_mobs[matches] = [ - deepcopy(base_mob).replace(target_mob) + base_mob.copy().replace(target_mob) for target_mob in all_mobs[matches] ] return all_mobs[:num_start_terms], all_mobs[num_start_terms:] diff --git a/topics/characters.py b/topics/characters.py index 1e7bbdeb..09d8a095 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -1,5 +1,3 @@ -from copy import deepcopy - from helpers import * from mobject import Mobject, Mobject, ImageMobject, TexMobject @@ -43,7 +41,7 @@ class PiCreature(Mobject): mouth_center = self.get_mouth_center() self.mouth.center() self.smile = self.mouth - self.frown = deepcopy(self.mouth).rotate(np.pi, RIGHT) + self.frown = self.mouth.copy().rotate(np.pi, RIGHT) self.straight_mouth = TexMobject("-").scale(0.5) for mouth in self.smile, self.frown, self.straight_mouth: mouth.sort_points(lambda p : p[0]) @@ -118,7 +116,7 @@ class PiCreature(Mobject): def get_step_intermediate(self, pi_creature): vect = pi_creature.get_center() - self.get_center() - result = deepcopy(self).shift(vect / 2.0) + result = self.copy().shift(vect / 2.0) left_forward = vect[0] > 0 if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]: #For Mortimer's case diff --git a/topics/combinatorics.py b/topics/combinatorics.py index f7ee56a3..cbace7fe 100644 --- a/topics/combinatorics.py +++ b/topics/combinatorics.py @@ -113,7 +113,7 @@ class PascalsTriangleScene(Scene): center = self.coords_to_center(n, k) if num not in num_to_num_mob: num_to_num_mob[num] = TexMobject(str(num)) - num_mob = deepcopy(num_to_num_mob[num]) + num_mob = num_to_num_mob[num].copy() scale_factor = min( 1, self.portion_to_fill * self.cell_height / num_mob.get_height(), @@ -154,7 +154,7 @@ class PascalsTriangleScene(Scene): for a in range((self.nrows - n)/2 + 1): for k in (n + a + 1, -a -1): self.coords.append((n, k)) - mob = deepcopy(zero) + mob = zero.copy() mob.shift(self.coords_to_center(n, k)) self.coords_to_mobs[n][k] = mob self.add(mob) diff --git a/topics/complex_numbers.py b/topics/complex_numbers.py index 360d8b93..a45f6514 100644 --- a/topics/complex_numbers.py +++ b/topics/complex_numbers.py @@ -21,7 +21,7 @@ class ComplexPlane(NumberPlane): digest_config(self, kwargs) kwargs.update({ "x_unit_to_spatial_width" : self.unit_to_spatial_width, - "y_uint_to_spatial_height" : self.unit_to_spatial_width, + "y_unit_to_spatial_height" : self.unit_to_spatial_width, "x_line_frequency" : self.line_frequency, "x_faded_line_frequency" : self.faded_line_frequency, "y_line_frequency" : self.line_frequency, diff --git a/topics/geometry.py b/topics/geometry.py index bc4146a4..3c001722 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -53,9 +53,11 @@ class Line(Mobject1D): for arg in start, end ] start_to_end = preliminary_end - preliminary_start + vect = np.zeros(len(start_to_end)) longer_dim = np.argmax(map(abs, start_to_end)) + vect[longer_dim] = start_to_end[longer_dim] self.start, self.end = [ - arg.get_edge_center(unit*start_to_end) + arg.get_edge_center(unit*vect) if isinstance(arg, Mobject) else np.array(arg) for arg, unit in zip([start, end], [1, -1]) diff --git a/topics/graph_theory.py b/topics/graph_theory.py index f8aa8688..c6d1ff3e 100644 --- a/topics/graph_theory.py +++ b/topics/graph_theory.py @@ -246,7 +246,7 @@ class GraphScene(Scene): self.play(*[ CounterclockwiseTransform( vertex, - deepcopy(mobject).shift(vertex.get_center()) + mobject.copy().shift(vertex.get_center()) ) for vertex in self.vertices ] + [ @@ -260,7 +260,7 @@ class GraphScene(Scene): def annotate_edges(self, mobject, fade_in = True, **kwargs): angles = map(np.arctan, map(Line.get_slope, self.edges)) self.edge_annotations = [ - deepcopy(mobject).rotate(angle).shift(edge.get_center()) + mobject.copy().rotate(angle).shift(edge.get_center()) for angle, edge in zip(angles, self.edges) ] if fade_in: diff --git a/topics/number_line.py b/topics/number_line.py index 677dac45..5599c551 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -18,7 +18,7 @@ class NumberLine(Mobject1D): def __init__(self, **kwargs): digest_config(self, kwargs) if self.leftmost_tick is None: - self.leftmost_tick = -int(self.numerical_radius) + self.leftmost_tick = -int(self.numerical_radius-self.number_at_center) self.left_num = self.number_at_center - self.numerical_radius self.right_num = self.number_at_center + self.numerical_radius Mobject1D.__init__(self, **kwargs) @@ -66,7 +66,8 @@ class NumberLine(Mobject1D): ) def point_to_number(self, point): - return self.number_at_center + point[0]/self.unit_length_to_spatial_width + new_point = point-self.get_center() + return self.number_at_center + new_point[0]/self.unit_length_to_spatial_width def default_numbers_to_display(self): return self.get_tick_numbers()[::2] @@ -115,7 +116,7 @@ class NumberPlane(Mobject1D): "x_radius" : SPACE_WIDTH, "y_radius" : SPACE_HEIGHT, "x_unit_to_spatial_width" : 1, - "y_uint_to_spatial_height" : 1, + "y_unit_to_spatial_height" : 1, "x_line_frequency" : 1, "x_faded_line_frequency" : 0.5, "y_line_frequency" : 1, @@ -145,7 +146,7 @@ class NumberPlane(Mobject1D): ))) y_vals = np.array(filter(lambda y : y not in y_vals, np.arange( 0, self.y_radius, - self.y_uint_to_spatial_height*y_freq + self.y_unit_to_spatial_height*y_freq ))) x_cont_vals = np.arange( 0, self.x_radius, @@ -153,7 +154,7 @@ class NumberPlane(Mobject1D): ) y_cont_vals = np.arange( 0, self.y_radius, - self.epsilon/self.y_uint_to_spatial_height + self.epsilon/self.y_unit_to_spatial_height ) for x_sgn, y_sgn in it.product([-1, 1], [-1, 1]): self.add_points( @@ -170,11 +171,17 @@ class NumberPlane(Mobject1D): def num_pair_to_point(self, pair): pair = pair + self.num_pair_at_center - return np.array([ - pair[0]*self.x_unit_to_spatial_width, - pair[1]*self.y_uint_to_spatial_height, - 0 - ]) + result = self.get_center() + result[0] += pair[0]*self.x_unit_to_spatial_width + result[1] += pair[1]*self.y_unit_to_spatial_height + return result + + def point_to_num_pair(self, point): + new_point = point-self.get_center() + center_x, center_y = self.num_pair_at_center + x = center_x + point[0]/self.x_unit_to_spatial_width + y = center_y + point[1]/self.y_unit_to_spatial_height + return x, y def get_coordinate_labels(self, x_vals = None, y_vals = None): result = [] @@ -207,6 +214,41 @@ class NumberPlane(Mobject1D): return arrow +class XYZAxes(Mobject1D): + DEFAULT_CONFIG = { + "color" : TEAL, + "radius" : SPACE_HEIGHT, + "tick_frequency" : 1, + } + def generate_points(self): + self.x_axis = NumberLine( + numerical_radius = self.radius, + tick_frequency = self.tick_frequency + ) + self.y_axis = self.x_axis.copy().rotate(np.pi/2, OUT) + self.z_axis = self.x_axis.copy().rotate(np.pi/2, DOWN) + + self.digest_mobject_attrs() + + +class SpaceGrid(Mobject1D): + DEFAULT_CONFIG = { + "color" : GREEN, + "radius" : SPACE_HEIGHT, + "unit_to_spatial_length" : 1, + "line_frequency" : 2, + } + def generate_points(self): + line_range = range(-int(self.radius), int(self.radius)+1, self.line_frequency) + for i in range(3): + perm = np.arange(i, i+3) % 3 + for a, b in it.product(line_range, line_range): + start = np.array([a, b, -self.radius])[perm] + end = np.array([a, b, self.radius])[perm] + self.add_line(start, end) + + + class NumberLineScene(Scene): def construct(self, **number_line_config): self.number_line = NumberLine(**number_line_config) @@ -231,7 +273,7 @@ class NumberLineScene(Scene): transforms = [] additional_mobjects = [] - squished_new_line = deepcopy(new_number_line) + squished_new_line = new_number_line.copy() squished_new_line.scale(1.0/zoom_factor) squished_new_line.shift(self.number_line.number_to_point(number)) squished_new_line.points[:,1] = self.number_line.number_to_point(0)[1] @@ -242,7 +284,7 @@ class NumberLineScene(Scene): transforms.append(Transform(point, mob)) for mob in self.mobjects: if mob == self.number_line: - new_mob = deepcopy(mob) + new_mob = mob.copy() new_mob.shift(-self.number_line.number_to_point(number)) new_mob.stretch(zoom_factor, 0) transforms.append(Transform(mob, new_mob)) diff --git a/transform_article.py b/transform_article.py new file mode 100644 index 00000000..7811fc4a --- /dev/null +++ b/transform_article.py @@ -0,0 +1,217 @@ +from topics import * +from animation import * + + +def half_plane(): + plane = NumberPlane( + x_radius = SPACE_WIDTH/2, + x_unit_to_spatial_width = 0.5, + y_unit_to_spatial_height = 0.5, + density = 2*DEFAULT_POINT_DENSITY_1D, + ) + plane.add_coordinates( + x_vals = range(-6, 7, 2), + y_vals = range(-6, 7, 2) + ) + return plane + +class SingleVariableFunction(Scene): + args_list = [ + (lambda x : x**2 - 3, "ShiftedSquare", True), + (lambda x : x**2 - 3, "ShiftedSquare", False), + ] + + @staticmethod + def args_to_string(func, name, separate_lines): + return name + ("SeparateLines" if separate_lines else "") + + def construct(self, func, name, separate_lines): + base_line = NumberLine(color = "grey") + moving_line = NumberLine( + tick_frequency = 1, + density = 3*DEFAULT_POINT_DENSITY_1D + ) + base_line.add_numbers() + def point_function((x, y, z)): + return (func(x), y, z) + target = moving_line.copy().apply_function(point_function) + + transform_config = { + "run_time" : 3, + "interpolation_function" : path_along_arc(np.pi/4) + } + + if separate_lines: + numbers = moving_line.get_number_mobjects(*range(-7, 7)) + negative_numbers = [] + for number in numbers: + number.highlight(GREEN_E) + number.shift(-2*moving_line.get_vertical_number_offset()) + center = number.get_center() + target_num = number.copy() + target_num.shift(point_function(center) - center) + target.add(target_num) + if center[0] < -0.5: + negative_numbers.append(number) + moving_line.add(*numbers) + base_line.shift(DOWN) + target.shift(DOWN) + moving_line.shift(UP) + + self.add(base_line, moving_line) + self.dither(3) + self.play(Transform(moving_line, target, **transform_config)) + if separate_lines: + self.play(*[ + ApplyMethod(mob.shift, 0.4*UP) + for mob in negative_numbers + ]) + self.dither(3) + + +class LineToPlaneFunction(Scene): + args_list = [ + (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", {}), + (lambda x : (np.cos(x), 0.5*x*np.sin(x)), "Swirl", { + "0" : 0, + "\\frac{\\pi}{2}" : np.pi/2, + "\\pi" : np.pi + }) + ] + + @staticmethod + def args_to_string(func, name, numbers_to_follow): + return name + ("FollowingNumbers" if numbers_to_follow else "") + + def construct(self, func, name, numbers_to_follow): + line = NumberLine( + unit_length_to_spatial_width = 0.5, + tick_frequency = 1, + number_at_center = 6, + numerical_radius = 6, + numbers_with_elongated_ticks = [0, 12], + density = 3*DEFAULT_POINT_DENSITY_1D + ) + line.to_edge(LEFT) + line_copy = line.copy() + line.add_numbers(*range(0, 14, 2)) + divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) + plane = half_plane() + plane.shift(0.5*SPACE_WIDTH*RIGHT) + self.add(line, divider, plane) + + def point_function(point): + x, y = func(line.point_to_number(point)) + return plane.num_pair_to_point((x, y)) + + target = line_copy.copy().apply_function(point_function) + target.highlight() + anim_config = {"run_time" : 3} + anims = [Transform(line_copy, target, **anim_config)] + + colors = iter([BLUE_B, GREEN_D, RED_D]) + for tex, number in numbers_to_follow.items(): + center = line.number_to_point(number) + dot = Dot(center, color = colors.next()) + anims.append(ApplyMethod( + dot.shift, + point_function(center) - center, + **anim_config + )) + label = TexMobject(tex) + label.shift(center + 2*UP) + arrow = Arrow(label, dot) + self.add(label) + self.play(ShowCreation(arrow), ShowCreation(dot)) + self.dither() + self.remove(arrow, label) + + + self.dither(2) + self.play(*anims) + self.dither() + +class PlaneToPlaneFunctionSeparatePlanes(Scene): + args_list = [ + (lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic") + ] + @staticmethod + def args_to_string(func, name): + return name + + def construct(self, func, name): + shift_factor = 0.55 + in_plane = half_plane().shift(shift_factor*SPACE_WIDTH*LEFT) + out_plane = half_plane().shift(shift_factor*SPACE_WIDTH*RIGHT) + divider = Line(SPACE_HEIGHT*UP, SPACE_HEIGHT*DOWN) + self.add(in_plane, out_plane, divider) + + plane_copy = in_plane.copy() + plane_copy.sub_mobjects = [] + + def point_function(point): + result = np.array(func((point*2 + 2*shift_factor*SPACE_WIDTH*RIGHT)[:2])) + result = np.append(result/2, [0]) + return result + shift_factor*SPACE_WIDTH*RIGHT + + target = plane_copy.copy().apply_function(point_function) + target.highlight(GREEN_B) + + anim_config = {"run_time" : 5} + + self.dither() + self.play(Transform(plane_copy, target, **anim_config)) + self.dither() + +class PlaneToPlaneFunction(Scene): + args_list = [ + (lambda (x, y) : (x**2+y**2, x**2-y**2), "Quadratic") + ] + @staticmethod + def args_to_string(func, name): + return name + + def construct(self, func, name): + plane = NumberPlane() + background = NumberPlane(color = "grey") + background.add_coordinates() + anim_config = {"run_time" : 3} + + def point_function(point): + return np.append(func(point[:2]), [0]) + + self.add(background, plane) + self.dither(2) + self.play(ApplyPointwiseFunction(point_function, plane, **anim_config)) + self.dither(3) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +