Merge remote-tracking branch 'origin/lighthouse' into lighthouse

# Conflicts:
#	topics/geometry.py
This commit is contained in:
bhbr
2018-01-26 19:32:55 +01:00
10 changed files with 975 additions and 148 deletions

View File

@@ -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])
)

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):

View File

@@ -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