diff --git a/active_projects/basel.py b/active_projects/basel.py index 298b34aa..b6263cd0 100644 --- a/active_projects/basel.py +++ b/active_projects/basel.py @@ -42,11 +42,38 @@ INDICATOR_TEXT_COLOR = WHITE INDICATOR_UPDATE_TIME = 0.2 FAST_INDICATOR_UPDATE_TIME = 0.1 OPACITY_FOR_UNIT_INTENSITY = 0.2 -SWITCH_ON_RUN_TIME = 2.0 +SWITCH_ON_RUN_TIME = 2.5 FAST_SWITCH_ON_RUN_TIME = 0.1 -LIGHT_CONE_NUM_SECTORS = 10 -NUM_CONES = 10 -NUM_VISIBLE_CONES = 6 +LIGHT_CONE_NUM_SECTORS = 30 +NUM_CONES = 50 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem +ARC_TIP_LENGTH = 0.2 +DIM_OPACITY = 0.2 + + +def show_line_length(line): + v = line.points[1] - line.points[0] + print v[0]**2 + v[1]**2 + + +class AngleUpdater(ContinualAnimation): + def __init__(self, angle_arc, lc, **kwargs): + self.angle_arc = angle_arc + self.source_point = angle_arc.get_arc_center() + self.lc = lc + #self.angle_decimal = angle_decimal + ContinualAnimation.__init__(self, self.angle_arc, **kwargs) + + def update_mobject(self, dt): + # angle arc + new_arc = self.angle_arc.copy().set_bound_angles( + start = self.lc.start_angle, + stop = self.lc.stop_angle() + ) + new_arc.generate_points() + new_arc.move_arc_center_to(self.source_point) + self.angle_arc.points = new_arc.points + self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, at_start = True, at_end = True) @@ -69,6 +96,7 @@ class LightScreen(VMobject): def update_light_cone(self,lc): lower_angle, upper_angle = self.viewing_angles() + #print lower_angle, upper_angle self.light_cone.update_opening(start_angle = lower_angle, stop_angle = upper_angle) return self @@ -99,14 +127,18 @@ class LightScreen(VMobject): ray1 = self.screen.points[0] - self.light_source ray2 = self.screen.points[-1] - self.light_source ray1 = ray1/np.linalg.norm(ray1) * 100 - ray1 = rotate_vector(ray1,TAU/16) + ray1 = rotate_vector(ray1,-TAU/16) ray2 = ray2/np.linalg.norm(ray2) * 100 - ray2 = rotate_vector(ray2,-TAU/16) + ray2 = rotate_vector(ray2,TAU/16) outpoint1 = self.screen.points[0] + ray1 outpoint2 = self.screen.points[-1] + ray2 self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]]) self.shadow.mark_paths_closed = True - + + def move_source_to(self,new_point): + self.light_source = new_point + #self.update_light_cone(self.light_cone) + class LightCone(VGroup): @@ -114,6 +146,7 @@ class LightCone(VGroup): "start_angle": 0, "angle" : TAU/8, "radius" : 10, + "brightness" : 1, "opacity_function" : lambda r : 1./max(r, 0.01), "num_sectors" : 10, "color": LIGHT_COLOR, @@ -130,7 +163,7 @@ class LightCone(VGroup): stroke_width = 0, stroke_color = self.color, fill_color = self.color, - fill_opacity = self.opacity_function(r1), + fill_opacity = self.brightness * self.opacity_function(r1), ) for r1, r2 in zip(radii, radii[1:]) ] @@ -147,6 +180,7 @@ class LightCone(VGroup): return source = self.submobjects[0].get_arc_center() self.shift(point - source) + self.generate_points() def update_opening(self, start_angle, stop_angle): self.start_angle = start_angle @@ -160,6 +194,15 @@ class LightCone(VGroup): submob.generate_points() submob.shift(source_point - submob.get_arc_center()) + def set_brightness(self,new_brightness): + self.brightness = new_brightness + radii = np.linspace(0, self.radius, self.num_sectors+1) + for (r1,sector) in zip(radii,self.submobjects): + sector.set_fill(opacity = self.brightness * self.opacity_function(r1)) + + def stop_angle(self): + return self.start_angle + self.angle + @@ -169,13 +212,14 @@ class LightCone(VGroup): class Candle(VGroup): CONFIG = { "radius" : 5, + "brightness" : 1.0, "opacity_function" : lambda r : 1./max(r, 0.01), - "num_sectors" : 10, + "num_annuli" : 10, "color": LIGHT_COLOR, } def generate_points(self): - radii = np.linspace(0, self.radius, self.num_sectors+1) + radii = np.linspace(0, self.radius, self.num_annuli+1) annuli = [ Annulus( inner_radius = r1, @@ -183,7 +227,7 @@ class Candle(VGroup): stroke_width = 0, stroke_color = self.color, fill_color = self.color, - fill_opacity = self.opacity_function(r1), + fill_opacity = self.brightness * self.opacity_function(r1), ) for r1, r2 in zip(radii, radii[1:]) ] @@ -201,6 +245,14 @@ class Candle(VGroup): source = self.submobjects[0].get_center() self.shift(point - source) + def set_brightness(self,new_brightness): + self.brightness = new_brightness + radii = np.linspace(0, self.radius, self.num_annuli+1) + for (r1,annulus) in zip(radii,self.submobjects): + annulus.set_fill(opacity = self.brightness * self.opacity_function(r1)) + + + class SwitchOn(LaggedStart): CONFIG = { @@ -558,8 +610,7 @@ class FirstLightHouseScene(PiCreatureScene): euler_sum_above = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25 }", "+", "{1\over 36}") - euler_sum_above.fill_color = YELLOW + "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above): #horizontal alignment with tick marks @@ -568,7 +619,7 @@ class FirstLightHouseScene(PiCreatureScene): old_y = term.get_center()[1] new_y = light_indicator.get_center()[1] term.shift([0,new_y - old_y,0]) - + for i in range(1,NUM_CONES+1): @@ -576,8 +627,8 @@ class FirstLightHouseScene(PiCreatureScene): point = self.number_line.number_to_point(i) light_cone = Candle( opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, - radius = 10) + num_annuli = LIGHT_CONE_NUM_SECTORS, + radius = 12) light_cone.move_source_to(point) lighthouse.next_to(point,DOWN,0) @@ -599,10 +650,9 @@ class FirstLightHouseScene(PiCreatureScene): # slowly switch on visible light cones and increment indicator for (i,lc) in zip(range(NUM_VISIBLE_CONES),light_cones[:NUM_VISIBLE_CONES]): - print i - indicator_start_time = 0.5 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size + indicator_start_time = 0.4 * (i+1) * SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) + indicator_rate_func = squish_rate_func( smooth,indicator_start_time,indicator_stop_time) self.play( SwitchOn(lc), @@ -624,11 +674,8 @@ class FirstLightHouseScene(PiCreatureScene): light_indicator_copy.shift,[0, new_y - old_y,0] ) - print "fast now" - # quickly switch on off-screen light cones and increment indicator for (i,lc) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_cones[NUM_VISIBLE_CONES:NUM_CONES]): - print i indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/lc.radius * self.number_line.unit_size indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) @@ -644,10 +691,17 @@ class FirstLightHouseScene(PiCreatureScene): # show limit value in light indicator and an equals sign limit_reading = TexMobject("{\pi^2 \over 6}") limit_reading.move_to(light_indicator.reading) + + equals_sign = TexMobject("=") + equals_sign.next_to(randy, UP) + old_y = equals_sign.get_center()[1] + new_y = euler_sum_above.get_center()[1] + equals_sign.shift([0,new_y - old_y,0]) + self.play( - FadeOut(indicator.reading), - FadeIn(limit_reading) -# Transform(light_indicator.reading,limit_reading) + FadeOut(light_indicator.reading), + FadeIn(limit_reading), + FadeIn(equals_sign), ) @@ -674,52 +728,125 @@ class SingleLightHouseScene(PiCreatureScene): lighthouse = LightHouse() candle = Candle( opacity_function = inverse_quadratic(1,1), - num_sectors = LIGHT_CONE_NUM_SECTORS, - radius = 10 + num_annuli = LIGHT_CONE_NUM_SECTORS, + radius = 10, + brightness = 1, ) lighthouse.scale(2).next_to(source_point, DOWN, buff = 0) candle.move_to(source_point) morty = self.get_primary_pi_creature() morty.scale(0.5) morty.move_to(observer_point) - self.add(lighthouse, candle) - self.wait() + self.add(lighthouse) self.play( SwitchOn(candle) ) - light_cone = LightCone() + light_cone = LightCone( + opacity_function = inverse_quadratic(1,1), + num_sectors = LIGHT_CONE_NUM_SECTORS, + radius = 10, + brightness = 5, + ) light_cone.move_source_to(source_point) - screen = Arc(TAU/4).rotate_in_place(TAU/2).shift(3*RIGHT) - screen.radius = 4 - screen.start_angle = -TAU/5 - screen.next_to(morty, LEFT) + screen = Line([0,-1,0],[0,1,0]) + show_line_length(screen) + + screen.rotate_in_place(-TAU/6) + show_line_length(screen) + + screen.next_to(morty, LEFT, buff = 1) light_screen = LightScreen(light_source = source_point, screen = screen, light_cone = light_cone) light_screen.screen.color = WHITE light_screen.screen.fill_opacity = 1 light_screen.update_light_cone(light_cone) - self.add(light_screen) - # dim the light that misses the screen - self.play( - ApplyMethod(light_cone.set_intensity,0.3) - ) + # self.play( + # FadeIn(light_screen, run_time = 2), + # # dim the light that misses the screen + # ApplyMethod(candle.set_brightness,0.3), + # ApplyMethod(light_screen.update_shadow,light_screen.shadow), + # FadeIn(light_cone), + # ) + lc_updater = lambda lc: light_screen.update_light_cone(lc) sh_updater = lambda sh: light_screen.update_shadow(sh) ca1 = ContinualUpdateFromFunc(light_screen.light_cone, lc_updater) + ca15 = ContinualUpdateFromFunc(light_screen, + lc_updater) ca2 = ContinualUpdateFromFunc(light_screen.shadow, sh_updater) - self.add(ca1, ca2) + self.add(ca1, ca15, ca2) self.add_foreground_mobject(morty) - moving_screen = ApplyMethod(screen.move_to, [1,0,0], run_time=3) + pointing_screen_at_source = ApplyMethod(screen.rotate,TAU/6) + #self.play(pointing_screen_at_source) + #self.wait() - self.play(moving_screen) + arc_angle = light_cone.angle + # draw arc arrows to show the opening angle + angle_arc = Arc(radius = 5, start_angle = light_cone.start_angle, + angle = light_cone.angle, tip_length = ARC_TIP_LENGTH) + #angle_arc.add_tip(at_start = True, at_end = True) + angle_arc.move_arc_center_to(source_point) + + self.add(angle_arc) + + angle_indicator = DecimalNumber(arc_angle/TAU*360, + num_decimal_points = 0, + unit = "^\\circ") + angle_indicator.next_to(angle_arc,RIGHT) + self.add_foreground_mobject(angle_indicator) + + angle_update_func = lambda x: light_cone.angle/TAU * 360 + ca3 = ContinualChangingDecimal(angle_indicator,angle_update_func) + self.add(ca3) + + #ca4 = ContinualUpdateFromFunc(angle_arc,update_angle_arc) + ca4 = AngleUpdater(angle_arc, light_screen.light_cone) + self.add(ca4) + + rotating_screen = ApplyMethod(light_screen.screen.rotate, + TAU/8, run_time=1.5) + #self.wait(2) + rotating_screen_2 = ApplyMethod(light_screen.screen.rotate, + -TAU/4, run_time=3, rate_func = there_and_back) + #self.wait(2) + rotating_screen_3 = ApplyMethod(light_screen.screen.rotate, + TAU/8, run_time=1.5) + + #self.play(rotating_screen) + #self.play(rotating_screen_2) + #self.play(rotating_screen_3) + + #rotating_screen_back = ApplyMethod(light_screen.screen.rotate_in_place, -TAU/6) #, run_time=3, rate_func = wiggle) + #self.play(rotating_screen_back) + + self.wait() + + + + # morph into Earth scene + + globe = Circle(radius = 3) + globe.move_to([2,0,0]) + sun_position = [-100,0,0] + self.play( + #ApplyMethod(lighthouse.move_to,sun_position), + #ApplyMethod(candle.move_to,sun_position), + ApplyMethod(light_screen.move_source_to,sun_position), + #FadeOut(angle_arc), + #FadeOut(angle_indicator), + #FadeIn(globe), + #ApplyMethod(light_screen.move_to,[0,0,0]), + #ApplyMethod(morty.move_to,[1,0,0]) + + ) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index f51029f9..f7c1d745 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -2392,10 +2392,12 @@ class ApplyFourierToFourier(DrawFrequencyPlot): kwargs["scale_val"] = 1.0 return DrawFrequencyPlot.get_cosine_wave(self, freq, **kwargs) -class WhiteComplexExponentialExpression(DrawFrequencyPlot): +class WriteComplexExponentialExpression(DrawFrequencyPlot): CONFIG = { "signal_frequency" : 2.0, "default_num_v_lines_indicating_periods" : 0, + "time_axes_scale_val" : 0.7, + "initial_winding_frequency" : 0.1, } def construct(self): self.remove(self.pi_creature) @@ -2404,15 +2406,16 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.show_winding_with_both_coordinates() self.show_plane_as_complex_plane() self.show_eulers_formula() - self.reference_other_video() self.show_winding_graph_expression() + self.find_center_of_mass() def setup_plane(self): circle_plane = ComplexPlane( unit_size = 2, - y_radius = SPACE_HEIGHT+LARGE_BUFF + y_radius = SPACE_HEIGHT+LARGE_BUFF, + x_radius = SPACE_WIDTH+LARGE_BUFF ) - circle_plane.shift(DOWN) + circle_plane.shift(DOWN+LEFT) circle = DashedLine(ORIGIN, TAU*UP) circle.apply_complex_function( lambda z : R3_to_complex( @@ -2427,7 +2430,7 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): fill_opacity = 0.9, buff = MED_SMALL_BUFF, )) - time_axes.scale(0.7) + time_axes.scale(self.time_axes_scale_val) time_axes.to_corner(UP+LEFT, buff = 0) time_axes.set_stroke(color = WHITE, width = 1) @@ -2444,7 +2447,7 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): scale_val = 0.5, shift_val = 0.75, ) - freq = 0.1 + freq = self.initial_winding_frequency pol_graph = self.get_polarized_mobject(graph, freq = freq) wps_label = self.get_winding_frequency_label() ChangeDecimalToValue(wps_label[0], freq).update(1) @@ -2455,35 +2458,607 @@ class WhiteComplexExponentialExpression(DrawFrequencyPlot): self.generate_center_of_mass_dot_update_anim() self.add(graph, pol_graph, wps_label) + self.set_variables_as_attrs(pol_graph, wps_label) + self.time_axes_group = VGroup(self.time_axes, graph) def show_winding_with_both_coordinates(self): - #TODO, tie dashed lines to dot + com_dot = self.center_of_mass_dot + plane = self.circle_plane + v_line = Line(ORIGIN, UP) + h_line = Line(ORIGIN, RIGHT) + lines = VGroup(v_line, h_line) + lines.highlight(PINK) + def lines_update(lines): + point = com_dot.get_center() + x, y = plane.point_to_coords(point) + h_line.put_start_and_end_on( + plane.coords_to_point(0, y), point + ) + v_line.put_start_and_end_on( + plane.coords_to_point(x, 0), point + ) + lines_update_anim = ContinualUpdateFromFunc(lines, lines_update) + lines_update_anim.update(0) + self.add(lines_update_anim) self.change_frequency( - 2.0, run_time = 15, + 2.04, + added_anims = [ + self.center_of_mass_dot_anim, + ], + run_time = 15, rate_func = bezier([0, 0, 1, 1]) ) self.wait() + self.dot_component_anim = lines_update_anim + def show_plane_as_complex_plane(self): - pass + to_fade = VGroup( + self.time_axes_group, self.pol_graph, self.wps_label + ) + plane = self.circle_plane + dot = self.center_of_mass_dot + complex_plane_title = TextMobject("Complex plane") + complex_plane_title.add_background_rectangle() + complex_plane_title.to_edge(UP) + coordinate_labels = plane.get_coordinate_labels() + number_label = DecimalNumber( + 0, include_background_rectangle = True, + ) + number_label_update_anim = ContinualChangingDecimal( + number_label, + lambda a : plane.point_to_number(dot.get_center()), + position_update_func = lambda l : l.next_to( + dot, DOWN+RIGHT, + buff = SMALL_BUFF + ), + ) + number_label_update_anim.update(0) + flower_path = ParametricFunction( + lambda t : plane.coords_to_point( + np.sin(2*t)*np.cos(t), + np.sin(2*t)*np.sin(t), + ), + t_min = 0, t_max = TAU, + ) + flower_path.move_to(self.center_of_mass_dot) + + self.play(FadeOut(to_fade)) + self.play(Write(complex_plane_title)) + self.play(Write(coordinate_labels)) + self.wait() + self.play(FadeIn(number_label)) + self.add(number_label_update_anim) + self.play(MoveAlongPath( + dot, flower_path, + run_time = 10, + rate_func = bezier([0, 0, 1, 1]) + )) + self.wait() + self.play(ShowCreation( + self.pol_graph, run_time = 3, + )) + self.play(FadeOut(self.pol_graph)) + self.wait() + self.play(FadeOut(VGroup( + dot, self.dot_component_anim.mobject, number_label + ))) + self.remove(self.dot_component_anim) + self.remove(number_label_update_anim) + + self.set_variables_as_attrs( + number_label, + number_label_update_anim, + complex_plane_title, + ) def show_eulers_formula(self): - pass + plane = self.circle_plane - def reference_other_video(self): - pass + ghost_dot = Dot(ORIGIN, fill_opacity = 0) + def get_t(): + return ghost_dot.get_center()[0] + def get_circle_point(scalar = 1, t_shift = 0): + return plane.number_to_point( + scalar*np.exp(complex(0, get_t()+t_shift)) + ) + vector = Vector(plane.number_to_point(1), color = GREEN) + exp_base = TexMobject("e").scale(1.3) + exp_base.add_background_rectangle() + exp_decimal = DecimalNumber(0, unit = "i", include_background_rectangle = True) + exp_decimal.scale(0.75) + VGroup(exp_base, exp_decimal).match_color(vector) + exp_decimal_update = ContinualChangingDecimal( + exp_decimal, lambda a : get_t(), + position_update_func = lambda d : d.move_to( + exp_base.get_corner(UP+RIGHT), DOWN+LEFT + ) + ) + exp_base_update = ContinualUpdateFromFunc( + exp_base, lambda e : e.move_to(get_circle_point( + scalar = 1.1, t_shift = 0.01*TAU + )) + ) + vector_update = ContinualUpdateFromFunc( + vector, lambda v : v.put_start_and_end_on( + plane.number_to_point(0), get_circle_point() + ) + ) + updates = [exp_base_update, exp_decimal_update, vector_update] + for update in updates: + update.update(0) + + #Show initial vector + self.play( + GrowArrow(vector), + FadeIn(exp_base), + Write(exp_decimal) + ) + self.add(*updates) + self.play(ghost_dot.shift, 2*RIGHT, run_time = 3) + self.wait() + + #Show arc + arc, circle = [ + Line(ORIGIN, t*UP) + for t in get_t(), TAU + ] + for mob in arc, circle: + mob.insert_n_anchor_points(20) + mob.set_stroke(RED, 4) + mob.apply_function( + lambda p : plane.number_to_point( + np.exp(R3_to_complex(p)) + ) + ) + distance_label = DecimalNumber( + exp_decimal.number, + unit = "\\text{units}" + ) + distance_label[-1].shift(SMALL_BUFF*RIGHT) + distance_label.match_color(arc) + distance_label.add_background_rectangle() + distance_label.move_to( + plane.number_to_point( + 1.1*np.exp(complex(0, 0.4*get_t())), + ), + DOWN+LEFT + ) + + self.play(ShowCreation(arc)) + self.play(ReplacementTransform( + exp_decimal.copy(), distance_label + )) + self.wait() + self.play(FadeOut(distance_label)) + + #Show full cycle + self.remove(arc) + self.play( + ghost_dot.move_to, TAU*RIGHT, + ShowCreation( + circle, + rate_func = lambda a : interpolate( + 2.0/TAU, 1, smooth(a) + ), + ), + run_time = 6, + ) + self.wait() + + #Write exponential expression + exp_expression = TexMobject("e", "^{-", "2\\pi i", "f", "t}") + e, minus, two_pi_i, f, t = exp_expression + exp_expression.next_to( + plane.coords_to_point(1, 1), + UP+RIGHT + ) + f.highlight(RED) + t.highlight(YELLOW) + exp_expression.add_background_rectangle() + two_pi_i_f_t_group = VGroup(two_pi_i, f, t) + two_pi_i_f_t_group.save_state() + two_pi_i_f_t_group.move_to(minus, LEFT) + exp_expression[1].remove(minus) + t.save_state() + t.align_to(f, LEFT) + exp_expression[1].remove(f) + + labels = VGroup() + for sym, word in (t, "Time"), (f, "Frequency"): + label = TextMobject(word) + label.match_style(sym) + label.next_to(sym, UP, buff = MED_LARGE_BUFF) + label.add_background_rectangle() + label.arrow = Arrow(label, sym, buff = SMALL_BUFF) + label.arrow.match_style(sym) + labels.add(label) + time_label, frequency_label = labels + example_frequency = TexMobject("f = 1/10") + example_frequency.add_background_rectangle() + example_frequency.match_style(frequency_label) + example_frequency.move_to(frequency_label, DOWN) + + self.play(ReplacementTransform( + VGroup(exp_base[1], exp_decimal[1]).copy(), + exp_expression + )) + self.play(FadeOut(circle)) + self.wait() + + ghost_dot.move_to(ORIGIN) + ambient_ghost_dot_movement = AmbientMovement( + ghost_dot, rate = TAU + ) + self.add(ambient_ghost_dot_movement) + + self.play( + Write(time_label), + GrowArrow(time_label.arrow), + ) + self.wait(6.5) #Leave time to say let's slow down + self.remove(ambient_ghost_dot_movement) + self.play( + FadeOut(time_label), + FadeIn(frequency_label), + t.restore, + GrowFromPoint(f, frequency_label.get_center()), + ReplacementTransform( + time_label.arrow, + frequency_label.arrow, + ) + ) + ghost_dot.move_to(ORIGIN) + ambient_ghost_dot_movement = AmbientMovement( + ghost_dot, rate = 0.1*TAU + ) + self.add(ambient_ghost_dot_movement) + self.wait(3) + self.play( + FadeOut(frequency_label), + FadeIn(example_frequency) + ) + self.wait(15) #Give time to reference other video + #Reverse directions + ambient_ghost_dot_movement.rate *= -1 + self.play( + FadeOut(example_frequency), + FadeOut(frequency_label.arrow), + GrowFromCenter(minus), + two_pi_i_f_t_group.restore + ) + self.wait(4) + + ambient_ghost_dot_movement.rate = 0 + self.remove(*updates) + self.play(*map(FadeOut, [ + update.mobject + for update in updates + if update.mobject is not vector + ])) + self.play(ghost_dot.move_to, ORIGIN) + + exp_expression[1].add(minus, f) + exp_expression[1].sort_submobjects(lambda p : p[0]) + + self.set_variables_as_attrs( + ambient_ghost_dot_movement, ghost_dot, + vector, vector_update, exp_expression + ) def show_winding_graph_expression(self): + ambient_ghost_dot_movement = self.ambient_ghost_dot_movement + ghost_dot = self.ghost_dot + vector = self.vector + exp_expression = self.exp_expression + plane = self.circle_plane + time_axes_group = self.time_axes_group + graph = self.graph + pol_graph = self.get_polarized_mobject(graph, freq = 0.2) + g_label = TexMobject("g(t)") + g_label.match_color(graph) + g_label.next_to(graph, UP) + g_label.add_background_rectangle() + g_scalar = g_label.copy() + g_scalar.move_to(exp_expression, DOWN+LEFT) + + vector_animations = self.get_vector_animations(graph) + vector_animations[1].mobject = vector + graph_y_vector = vector_animations[0].mobject + + self.play( + FadeIn(time_axes_group), + FadeOut(self.complex_plane_title) + ) + self.play(Write(g_label)) + self.wait() + self.play( + ReplacementTransform(g_label.copy(), g_scalar), + exp_expression.next_to, g_scalar, RIGHT, SMALL_BUFF, + exp_expression.shift, 0.5*SMALL_BUFF*UP, + ) + self.play(*vector_animations, run_time = 15) + self.add(*self.mobjects_from_last_animation) + self.wait() + + integrand = VGroup(g_scalar, exp_expression) + rect = SurroundingRectangle(integrand) + morty = Mortimer() + morty.next_to(rect, DOWN+RIGHT) + morty.shift_onto_screen() + self.play( + ShowCreation(rect), + FadeIn(morty) + ) + self.play(morty.change, "raise_right_hand") + self.play(Blink(morty)) + self.play(morty.change, "hooray", rect) + self.wait(2) + self.play(*map(FadeOut, [ + morty, rect, graph_y_vector, vector + ])) + + self.integrand = integrand + + def find_center_of_mass(self): + integrand = self.integrand + integrand.generate_target() + integrand.target.to_edge(RIGHT, buff = LARGE_BUFF) + integrand.target.shift(MED_LARGE_BUFF*DOWN) + sum_expr = TexMobject( + "{1", "\\over", "N}", + "\\sum", "_{k = 1}", "^N", + ) + sum_expr.add_background_rectangle() + sum_expr.shift(SMALL_BUFF*(UP+5*RIGHT)) + sum_expr.next_to(integrand.target, LEFT) + + integral = TexMobject( + "{1", "\\over", "t_2 - t_1}", + "\\int", "_{t_1}", "^{t_2}" + ) + integral.move_to(sum_expr, RIGHT) + time_interval_indicator = SurroundingRectangle(integral[2]) + integral.add_background_rectangle() + axes = self.time_axes + time_interval = Line( + axes.coords_to_point(axes.x_min, 0), + axes.coords_to_point(axes.x_max, 0), + ) + time_interval.match_style(time_interval_indicator) + time_interval_indicator.add(time_interval) + dt_mob = TexMobject("dt") + dt_mob.next_to(integrand.target, RIGHT, SMALL_BUFF, DOWN) + dt_mob.add_background_rectangle() + + dots = self.show_center_of_mass_sampling(20) + self.wait() + self.play( + Write(sum_expr), + MoveToTarget(integrand), + ) + + #Add k subscript to t's + t1 = integrand[0][1][2] + t2 = integrand[1][1][-1] + t_mobs = VGroup(t1, t2) + t_mobs.save_state() + t_mobs.generate_target() + for i, t_mob in enumerate(t_mobs.target): + k = TexMobject("k") + k.match_style(t_mob) + k.match_height(t_mob) + k.scale(0.5) + k.move_to(t_mob.get_corner(DOWN+RIGHT), LEFT) + k.add_background_rectangle() + t_mob.add(k) + if i == 0: + t_mob.shift(0.5*SMALL_BUFF*LEFT) + + self.play(MoveToTarget(t_mobs)) + self.play(LaggedStart( + Indicate, dots[1], + rate_func = there_and_back, + color = TEAL, + )) + self.show_center_of_mass_sampling(100) + dots = self.show_center_of_mass_sampling(500) + self.wait() + self.play(FadeOut(dots)) + self.play( + ReplacementTransform(sum_expr, integral), + FadeIn(dt_mob), + t_mobs.restore, + ) + self.wait() + self.play(ShowCreation(time_interval_indicator)) + self.wait() + self.play(FadeOut(time_interval_indicator)) + self.wait() + + #Show confusion + randy = Randolph() + randy.flip() + randy.next_to(integrand, DOWN, LARGE_BUFF) + randy.to_edge(RIGHT) + full_expression_rect = SurroundingRectangle( + VGroup(integral, dt_mob), color = RED + ) + com_dot = self.center_of_mass_dot + self.center_of_mass_dot_anim.update(0) + com_arrow = Arrow( + full_expression_rect.get_left(), com_dot, + buff = SMALL_BUFF + ) + com_arrow.match_color(com_dot) + + + self.play(FadeIn(randy)) + self.play(randy.change, "confused", integral) + self.play(Blink(randy)) + self.wait(2) + self.play(ShowCreation(full_expression_rect)) + self.play( + randy.change, "thinking", self.pol_graph, + GrowArrow(com_arrow), + GrowFromCenter(com_dot), + ) + self.play(Blink(randy)) + self.wait(2) + + def show_center_of_mass_sampling(self, n_dots): + time_graph = self.graph + pol_graph = self.graph.polarized_mobject + axes = self.time_axes + + dot = Dot(radius = 0.05, color = PINK) + pre_dots = VGroup(*[ + dot.copy().move_to(axes.coords_to_point(t, 0)) + for t in np.linspace(axes.x_min, axes.x_max, n_dots) + ]) + pre_dots.set_fill(opacity = 0) + for graph in time_graph, pol_graph: + if hasattr(graph, "dots"): + graph.dot_fade_anims = [FadeOut(graph.dots)] + else: + graph.dot_fade_anims = [] + graph.save_state() + graph.generate_target() + if not hasattr(graph, "is_faded"): + graph.target.fade(0.7) + graph.is_faded = True + graph.dots = VGroup(*[ + dot.copy().move_to(graph.point_from_proportion(a)) + for a in np.linspace(0, 1, n_dots) + ]) + + self.play( + ReplacementTransform( + pre_dots, time_graph.dots, + submobject_mode = "lagged_start", + run_time = 2, + ), + MoveToTarget(time_graph), + *time_graph.dot_fade_anims + ) + self.play( + ReplacementTransform( + time_graph.copy(), pol_graph.target + ), + MoveToTarget(pol_graph), + ReplacementTransform( + time_graph.dots.copy(), + pol_graph.dots, + ), + *pol_graph.dot_fade_anims, + run_time = 2 + ) + return VGroup(time_graph.dots, pol_graph.dots) + + +class WhyAreYouTellingUsThis(TeacherStudentsScene): + def construct(self): + self.student_says("Why are you \\\\ telling us this?") + self.play(self.teacher.change, "happy") + self.wait(2) + +class BuildUpExpressionStepByStep(TeacherStudentsScene): + def construct(self): + expression = TexMobject( + "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", + "g(t)", "e", "^{2\\pi i", "f", "t}", "dt" + ) + frac, integral, g, e, two_pi_i, f, t, dt = expression + expression.next_to(self.teacher, UP+LEFT) + t.highlight(YELLOW) + g[2].highlight(YELLOW) + dt[1].highlight(YELLOW) + f.highlight(GREEN) + t.save_state() + t.move_to(f, LEFT) + + self.play( + self.teacher.change, "raise_right_hand", + FadeIn(e), + FadeIn(two_pi_i), + ) + self.play( + self.get_student_changes(*["pondering"]*3), + FadeIn(t), + ) + self.play( + FadeIn(f), + t.restore, + ) + self.wait() + self.play(FadeIn(g), Blink(self.students[1])) + self.wait() + self.play( + FadeIn(integral), + FadeIn(frac), + FadeIn(dt), + ) + self.wait(3) + self.teacher_says( + "Just one final \\\\ distinction.", + bubble_kwargs = {"height" : 2.5, "width" : 3.5}, + added_anims = [expression.to_corner, UP+RIGHT] + ) + self.wait(3) + +class ScaleUpCenterOfMass(WriteComplexExponentialExpression): + CONFIG = { + "time_axes_scale_val" : 0.6, + "initial_winding_frequency" : 1.95 + } + def construct(self): + self.remove(self.pi_creature) + self.setup_plane() + self.setup_graph() + self.add_expression() + self.add_center_of_mass_dot() + + self.cross_out_denominator() + self.scale_up_center_of_mass() + self.what_this_means_for_various_winding_frequencies() + + + def add_expression(self): + expression = TexMobject( + "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", + "g(t)", "e", "^{2\\pi i", "f", "t}", "dt" + ) + frac, integral, g, e, two_pi_i, f, t, dt = expression + expression.to_corner(UP+RIGHT) + t.highlight(YELLOW) + g[2].highlight(YELLOW) + dt[1].highlight(YELLOW) + f.highlight(GREEN) + expression.add_background_rectangle() + self.expression = expression + self.add(expression) + + self.winding_freq_label.to_edge(RIGHT) + self.winding_freq_label[1].match_color(f) + + def add_center_of_mass_dot(self): + self.center_of_mass_dot = self.get_center_of_mass_dot() + self.generate_center_of_mass_dot_update_anim() + self.add(self.center_of_mass_dot) + + + def cross_out_denominator(self): + frac = self.expression[0] + integral = VGroup(*self.expression[1:]) + + + def scale_up_center_of_mass(self): pass - - - - - - + def what_this_means_for_various_winding_frequencies(self): + pass class CloseWithAPuzzle(TeacherStudentsScene): diff --git a/animation/simple_animations.py b/animation/simple_animations.py index bb703443..853f218f 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -12,20 +12,19 @@ from transform import Transform class Rotating(Animation): CONFIG = { - "axes" : [], "axis" : OUT, "radians" : 2*np.pi, "run_time" : 5, "rate_func" : None, "in_place" : True, "about_point" : None, + "about_edge" : None, } def update_submobject(self, submobject, starting_submobject, alpha): submobject.points = np.array(starting_submobject.points) def update_mobject(self, alpha): Animation.update_mobject(self, alpha) - axes = self.axes if self.axes else [self.axis] about_point = None if self.about_point is not None: about_point = self.about_point @@ -33,8 +32,9 @@ class Rotating(Animation): self.about_point = self.mobject.get_center() self.mobject.rotate( alpha*self.radians, - axes = axes, - about_point = self.about_point + axis = self.axis, + about_point = self.about_point, + about_edge = self.about_edge, ) class ShowPartial(Animation): diff --git a/camera.py b/camera.py index 8737a54b..f5a6f09a 100644 --- a/camera.py +++ b/camera.py @@ -99,10 +99,7 @@ class Camera(object): ]) )) - def capture_mobject(self, mobject): - return self.capture_mobjects([mobject]) - - def capture_mobjects( + def get_mobjects_to_display( self, mobjects, include_submobjects = True, excluded_mobjects = None, @@ -116,6 +113,13 @@ class Camera(object): excluded_mobjects ) mobjects = list_difference_update(mobjects, all_excluded) + return mobjects + + def capture_mobject(self, mobject, **kwargs): + return self.capture_mobjects([mobject], **kwargs) + + def capture_mobjects(self, mobjects, **kwargs): + mobjects = self.get_mobjects_to_display(mobjects, **kwargs) vmobjects = [] for mobject in mobjects: if isinstance(mobject, VMobject): @@ -411,7 +415,7 @@ class MovingCamera(Camera): class MappingCamera(Camera): CONFIG = { "mapping_func" : lambda p : p, - "min_anchor_points" : 20, + "min_anchor_points" : 50, "allow_object_intrusion" : False } @@ -419,6 +423,7 @@ class MappingCamera(Camera): return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points)) def capture_mobjects(self, mobjects, **kwargs): + mobjects = self.get_mobjects_to_display(mobjects, **kwargs) if self.allow_object_intrusion: mobject_copies = mobjects else: @@ -427,7 +432,11 @@ class MappingCamera(Camera): if isinstance(mobject, VMobject) and \ 0 < mobject.get_num_anchor_points() < self.min_anchor_points: mobject.insert_n_anchor_points(self.min_anchor_points) - Camera.capture_mobjects(self, mobject_copies, **kwargs) + Camera.capture_mobjects( + self, mobject_copies, + include_submobjects = False, + excluded_mobjects = None, + ) # TODO: Put this in different utility/helpers file? Convenient for me (Sridhar); I like it. class DictAsObject(object): diff --git a/helpers.py b/helpers.py index 079b0b5e..85ad584e 100644 --- a/helpers.py +++ b/helpers.py @@ -252,14 +252,14 @@ def get_all_descendent_classes(Class): result.append(Child) return result -def filtered_locals(local_args): - result = local_args.copy() +def filtered_locals(caller_locals): + result = caller_locals.copy() ignored_local_args = ["self", "kwargs"] for arg in ignored_local_args: - result.pop(arg, local_args) + result.pop(arg, caller_locals) return result -def digest_config(obj, kwargs, local_args = {}): +def digest_config(obj, kwargs, caller_locals = {}): """ Sets init args and CONFIG values as local variables @@ -268,19 +268,25 @@ def digest_config(obj, kwargs, local_args = {}): be easily passed into instantiation, and is attached as an attribute of the object. """ - ### Assemble list of CONFIGs from all super classes + + # Assemble list of CONFIGs from all super classes classes_in_hierarchy = [obj.__class__] - configs = [] + static_configs = [] while len(classes_in_hierarchy) > 0: Class = classes_in_hierarchy.pop() classes_in_hierarchy += Class.__bases__ if hasattr(Class, "CONFIG"): - configs.append(Class.CONFIG) + static_configs.append(Class.CONFIG) #Order matters a lot here, first dicts have higher priority - all_dicts = [kwargs, filtered_locals(local_args), obj.__dict__] - all_dicts += configs + caller_locals = filtered_locals(caller_locals) + all_dicts = [kwargs, caller_locals, obj.__dict__] + all_dicts += static_configs + all_new_dicts = [kwargs, caller_locals] + static_configs obj.__dict__ = merge_config(all_dicts) + #Keep track of the configuration of objects upon + #instantiation + obj.initial_config = merge_config(all_new_dicts) def merge_config(all_dicts): all_config = reduce(op.add, [d.items() for d in all_dicts]) @@ -295,6 +301,15 @@ def merge_config(all_dicts): config[key] = merge_config([config[key], value]) return config +def soft_dict_update(d1, d2): + """ + Adds key values pairs of d2 to d1 only when d1 doesn't + already have that key + """ + for key, value in d2.items(): + if key not in d1: + d1[key] = value + def digest_locals(obj, keys = None): caller_locals = filtered_locals( inspect.currentframe().f_back.f_locals diff --git a/mobject/mobject.py b/mobject/mobject.py index 6e135c95..f079b330 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -15,11 +15,11 @@ class Mobject(object): Mathematical Object """ CONFIG = { - "color" : WHITE, + "color" : WHITE, "stroke_width" : DEFAULT_POINT_THICKNESS, - "name" : None, - "dim" : 3, - "target" : None, + "name" : None, + "dim" : 3, + "target" : None, } def __init__(self, *submobjects, **kwargs): digest_config(self, kwargs) @@ -316,14 +316,29 @@ class Mobject(object): self.shift(target_point - point_to_align + buff*direction) return self - def align_to(self, mobject_or_point, direction = UP): + def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): + """ + Examples: + mob1.align_to(mob2, UP) moves mob1 vertically so that its + top edge lines ups with mob2's top edge. + + mob1.align_to(mob2, alignment_vector = RIGHT) moves mob1 + horizontally so that it's center is directly above/below + the center of mob2 + """ if isinstance(mobject_or_point, Mobject): mob = mobject_or_point - point = mob.get_edge_center(direction) + target_point = mob.get_critical_point(direction) else: - point = mobject_or_point - diff = point - self.get_edge_center(direction) - self.shift(direction*np.dot(diff, direction)) + target_point = mobject_or_point + direction_norm = np.linalg.norm(direction) + if direction_norm > 0: + alignment_vect = np.array(direction)/direction_norm + reference_point = self.get_critical_point(direction) + else: + reference_point = self.get_center() + diff = target_point - reference_point + self.shift(alignment_vect*np.dot(diff, alignment_vect)) return self def shift_onto_screen(self, **kwargs): @@ -648,8 +663,12 @@ class Mobject(object): ## Family matters - def __getitem__(self, index): - return self.split()[index] + def __getitem__(self, value): + self_list = self.split() + if isinstance(value, slice): + GroupClass = self.get_group_class() + return GroupClass(*self_list.__getitem__(value)) + return self_list.__getitem__(value) def __iter__(self): return iter(self.split()) @@ -657,6 +676,9 @@ class Mobject(object): def __len__(self): return len(self.split()) + def get_group_class(self): + return Group + def split(self): result = [self] if len(self.points) > 0 else [] return result + self.submobjects diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index ec1da178..4b1a37e6 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -19,6 +19,9 @@ class VMobject(Mobject): "make_smooth_after_applying_functions" : False, } + def get_group_class(self): + return VGroup + ## Colors def init_colors(self): self.set_style_data( @@ -85,15 +88,23 @@ class VMobject(Mobject): return self def match_style(self, vmobject): - #TODO: Should this be smart about matching the - #style of the family members, if they happen to - #be different? self.set_style_data( stroke_color = vmobject.get_stroke_color(), stroke_width = vmobject.get_stroke_width(), fill_color = vmobject.get_fill_color(), fill_opacity = vmobject.get_fill_opacity(), + family = False ) + + #Does its best to match up submobject lists, and + #match styles accordingly + submobs1, submobs2 = self.submobjects, vmobject.submobjects + if len(submobs1) == 0: + return + elif len(submobs2) == 0: + submobs2 = [vmobject] + for sm1, sm2 in zip(*make_even(submobs1, submobs2)): + sm1.match_style(sm2) return def fade(self, darkness = 0.5): diff --git a/scene/scene.py b/scene/scene.py index fb4ba6ca..fbf2d2ff 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -482,7 +482,7 @@ class Scene(object): def preview(self): TkSceneRoot(self) - + def get_image_file_path(self, name = None, dont_update = False): folder = "images" if dont_update: @@ -540,7 +540,6 @@ class Scene(object): '-loglevel', 'error', temp_file_path, ] - # self.writing_process = sp.Popen(command, stdin=sp.PIPE, shell=True) self.writing_process = sp.Popen(command, stdin=sp.PIPE) diff --git a/topics/geometry.py b/topics/geometry.py index 343a9f70..b5113ee5 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -36,15 +36,39 @@ class Arc(VMobject): ) self.scale(self.radius, about_point = ORIGIN) - def add_tip(self, tip_length = 0.25): + def add_tip(self, tip_length = 0.25, at_start = False, at_end = True): + # clear out any old tips + for submob in self.submobjects: + if submob.mark_paths_closed == True: # is a tip + self.remove(submob) + #TODO, do this a better way - p1, p2 = self.points[-2:] - arrow = Arrow( - p1, 2*p2 - p1, - tip_length = tip_length, - max_tip_length_to_length_ratio = 2.0 - ) - self.add(arrow.split()[-1]) + p1 = p2 = p3 = p4 = None + start_arrow = end_arrow = None + if at_start: + p1, p2 = self.points[-3:-1] + # self.points[-2:] did overshoot + start_arrow = Arrow( + p1, 2*p2 - p1, + tip_length = tip_length, + max_tip_length_to_length_ratio = 2.0 + ) + self.add(start_arrow.split()[-1]) # just the tip + + if at_end: + p4, p3 = self.points[1:3] + # self.points[:2] did overshoot + end_arrow = Arrow( + p3, 2*p4 - p3, + tip_length = tip_length, + max_tip_length_to_length_ratio = 2.0 + ) + self.add(end_arrow.split()[-1]) + + + + + self.highlight(self.get_color()) return self @@ -66,6 +90,13 @@ class Arc(VMobject): def stop_angle(self): return self.start_angle + self.angle + def set_bound_angles(self,start=0,stop=np.pi): + self.start_angle = start + self.angle = stop - start + + return self + + @@ -141,18 +172,13 @@ class AnnularSector(VMobject): arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center -<<<<<<< HEAD def move_arc_center_to(self,point): v = point - self.get_arc_center() self.shift(v) return self - -======= ->>>>>>> master class Sector(AnnularSector): - CONFIG = { "outer_radius" : 1, "inner_radius" : 0 @@ -178,10 +204,12 @@ class Annulus(Circle): } def generate_points(self): + self.points = [] self.radius = self.outer_radius - Circle.generate_points(self) + outer_circle = Circle(radius = self.outer_radius) inner_circle = Circle(radius=self.inner_radius) inner_circle.flip() + self.points = outer_circle.points self.add_subpath(inner_circle.points) class Line(VMobject): diff --git a/topics/numerals.py b/topics/numerals.py index f8ee064f..0be11a49 100644 --- a/topics/numerals.py +++ b/topics/numerals.py @@ -3,6 +3,7 @@ from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint from mobject.tex_mobject import TexMobject from animation import Animation from animation.continual_animation import ContinualAnimation +from topics.geometry import BackgroundRectangle from scene import Scene from helpers import * @@ -10,26 +11,37 @@ class DecimalNumber(VMobject): CONFIG = { "num_decimal_points" : 2, "digit_to_digit_buff" : 0.05, - "show_ellipsis" : False + "show_ellipsis" : False, + "unit" : None, + "include_background_rectangle" : False, } def __init__(self, number, **kwargs): - digest_config(self, kwargs, locals()) - num_string = '%.*f'%(self.num_decimal_points, number) - negative_zero_string = "-%.*f"%(self.num_decimal_points, 0.) - if num_string == negative_zero_string: - num_string = num_string[1:] - VMobject.__init__(self, *[ - TexMobject(char) - for char in num_string - ], **kwargs) + VMobject.__init__(self, **kwargs) + self.number = number + ndp = self.num_decimal_points + #Build number string + if isinstance(number, complex): + num_string = '%.*f%s%.*fi'%( + ndp, number.real, + "-" if number.imag < 0 else "+", + ndp, abs(number.imag) + ) + else: + num_string = '%.*f'%(ndp, number) + negative_zero_string = "-%.*f"%(ndp, 0.) + if num_string == negative_zero_string: + num_string = num_string[1:] + self.add(*[ + TexMobject(char, **kwargs) + for char in num_string + ]) + + #Add non-numerical bits if self.show_ellipsis: self.add(TexMobject("\\dots")) - - self.arrange_submobjects( - buff = self.digit_to_digit_buff, - aligned_edge = DOWN - ) + + if num_string.startswith("-"): minus = self.submobjects[0] minus.next_to( @@ -37,38 +49,68 @@ class DecimalNumber(VMobject): buff = self.digit_to_digit_buff ) -class Integer(VGroup): - CONFIG = { - "digit_buff" : 0.8*SMALL_BUFF - } - def __init__(self, integer, **kwargs): - self.number = integer - num_str = str(integer) - VGroup.__init__(self, *map(TexMobject, num_str), **kwargs) + if self.unit != None: + unit_sign = TexMobject(self.unit) + unit_sign.next_to(self.submobjects[-1],RIGHT, + buff = self.digit_to_digit_buff) + + if self.unit == "^\\circ": + unit_sign.align_to(self,UP) + else: + unit_sign.align_to(self,DOWN) + self.add(unit_sign) + self.arrange_submobjects( - RIGHT, buff = self.digit_buff, aligned_edge = DOWN + buff = self.digit_to_digit_buff, + aligned_edge = DOWN ) - if num_str[0] == "-": - self[0].next_to(self[1], LEFT, buff = SMALL_BUFF) + + +class Integer(VGroup): + #Handle alignment of parts that should be aligned + #to the bottom + for i, c in enumerate(num_string): + if c == "-" and len(num_string) > i+1: + self[i].align_to(self[i+1], alignment_vect = UP) + if self.unit == "\\circ": + self[-1].align_to(self, UP) + # + if self.include_background_rectangle: + self.add_background_rectangle() + + def add_background_rectangle(self): + #TODO, is this the best way to handle + #background rectangles? + self.background_rectangle = BackgroundRectangle(self) + self.submobjects = [ + self.background_rectangle, + VGroup(*self.submobjects) + ] + return self + +class Integer(DecimalNumber): + CONFIG = { + "num_decimal_points" : 0, + } class ChangingDecimal(Animation): CONFIG = { "num_decimal_points" : None, "show_ellipsis" : None, - "spare_parts" : 2, "position_update_func" : None, "tracked_mobject" : None } def __init__(self, decimal_number_mobject, number_update_func, **kwargs): digest_config(self, kwargs, locals()) - if self.num_decimal_points is None: - self.num_decimal_points = decimal_number_mobject.num_decimal_points - if self.show_ellipsis is None: - self.show_ellipsis = decimal_number_mobject.show_ellipsis - decimal_number_mobject.add(*[ - VectorizedPoint(decimal_number_mobject.get_corner(DOWN+LEFT)) - for x in range(self.spare_parts)] + self.decimal_number_config = dict( + decimal_number_mobject.initial_config ) + for attr in "num_decimal_points", "show_ellipsis": + value = getattr(self, attr) + if value is not None: + self.decimal_number_config[attr] = value + if hasattr(self.decimal_number_mobject, "background_rectangle"): + self.decimal_number_config["include_background_rectangle"] = True if self.tracked_mobject: dmc = decimal_number_mobject.get_center() tmc = self.tracked_mobject.get_center() @@ -83,12 +125,11 @@ class ChangingDecimal(Animation): decimal = self.decimal_number_mobject new_number = self.number_update_func(alpha) new_decimal = DecimalNumber( - new_number, - num_decimal_points = self.num_decimal_points, - show_ellipsis = self.show_ellipsis + new_number, **self.decimal_number_config ) - new_decimal.replace(decimal, dim_to_match = 1) - new_decimal.highlight(decimal.get_color()) + new_decimal.match_height(decimal) + new_decimal.move_to(decimal) + new_decimal.match_style(decimal) decimal.submobjects = new_decimal.submobjects decimal.number = new_number