Files

846 lines
26 KiB
Python

from helpers import *
import numbers
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from scene import Scene
from mobject.svg_mobject import *
from mobject.vectorized_mobject import *
from mobject.tex_mobject import *
OPERATION_COLORS = [YELLOW, GREEN, BLUE_B]
def get_equation(index, x = 2, y = 3, z = 8, expression_only = False):
assert(index in [0, 1, 2])
if index == 0:
tex1 = "\\sqrt[%d]{%d}"%(y, z),
tex2 = " = %d"%x
elif index == 1:
tex1 = "\\log_%d(%d)"%(x, z),
tex2 = " = %d"%y
elif index == 2:
tex1 = "%d^%d"%(x, y),
tex2 = " = %d"%z
if expression_only:
tex = tex1
else:
tex = tex1+tex2
return TexMobject(tex).highlight(OPERATION_COLORS[index])
def get_inverse_rules():
return map(TexMobject, [
"x^{\\log_x(z)} = z",
"\\log_x\\left(x^y \\right) = y",
"\\sqrt[y]{x^y} = x",
"\\left(\\sqrt[y]{z}\\right)^y = z",
"\\sqrt[\\log_x(z)]{z} = x",
"\\log_{\\sqrt[y]{z}}(z) = y",
])
def get_top_inverse_rules():
result = []
pairs = [#Careful of order here!
(0, 2),
(0, 1),
(1, 0),
(1, 2),
(2, 0),
(2, 1),
]
for i, j in pairs:
top = get_top_inverse(i, j)
char = ["x", "y", "z"][j]
eq = TexMobject("= %s"%char)
eq.scale(2)
eq.next_to(top, RIGHT)
diff = eq.get_center() - top.triangle.get_center()
eq.shift(diff[1]*UP)
result.append(VMobject(top, eq))
return result
def get_top_inverse(i, j):
args = [None]*3
k = set([0, 1, 2]).difference([i, j]).pop()
args[i] = ["x", "y", "z"][i]
big_top = TOP(*args)
args[j] = ["x", "y", "z"][j]
lil_top = TOP(*args, triangle_height_to_number_height = 1.5)
big_top.set_value(k, lil_top)
return big_top
class TOP(VMobject):
CONFIG = {
"triangle_height_to_number_height" : 3,
"offset_multiple" : 1.5,
"radius" : 1.5,
"propagate_style_to_family" : False,
}
def __init__(self, x = None, y = None, z = None, **kwargs):
digest_config(self, kwargs, locals())
VMobject.__init__(self, **kwargs)
def generate_points(self):
vertices = [
self.radius*rotate_vector(RIGHT, 7*np.pi/6 - i*2*np.pi/3)
for i in range(3)
]
self.triangle = Polygon(
*vertices,
color = WHITE,
stroke_width = 5
)
self.values = [VMobject()]*3
self.set_values(self.x, self.y, self.z)
def set_values(self, x, y, z):
for i, mob in enumerate([x, y, z]):
self.set_value(i, mob)
def set_value(self, index, value):
self.values[index] = self.put_on_vertex(index, value)
self.reset_submobjects()
def put_on_vertex(self, index, value):
assert(index in [0, 1, 2])
if value is None:
value = VectorizedPoint()
if isinstance(value, numbers.Number):
value = str(value)
if isinstance(value, str):
value = TexMobject(value)
if isinstance(value, TOP):
return self.put_top_on_vertix(index, value)
self.rescale_corner_mobject(value)
value.center()
if index == 0:
offset = -value.get_corner(UP+RIGHT)
elif index == 1:
offset = -value.get_bottom()
elif index == 2:
offset = -value.get_corner(UP+LEFT)
value.shift(self.offset_multiple*offset)
anchors = self.triangle.get_anchors_and_handles()[0]
value.shift(anchors[index])
return value
def put_top_on_vertix(self, index, top):
top.scale_to_fit_height(2*self.get_value_height())
vertices = np.array(top.get_vertices())
vertices[index] = 0
start = reduce(op.add, vertices)/2
end = self.triangle.get_anchors_and_handles()[0][index]
top.shift(end-start)
return top
def put_in_vertex(self, index, mobject):
self.rescale_corner_mobject(mobject)
mobject.center()
mobject.shift(interpolate(
self.get_center(),
self.get_vertices()[index],
0.7
))
return mobject
def get_surrounding_circle(self, color = YELLOW):
return Circle(
radius = 1.7*self.radius,
color = color
).shift(
self.triangle.get_center(),
(self.triangle.get_height()/6)*DOWN
)
def rescale_corner_mobject(self, mobject):
mobject.scale_to_fit_height(self.get_value_height())
return self
def get_value_height(self):
return self.triangle.get_height()/self.triangle_height_to_number_height
def get_center(self):
return center_of_mass(self.get_vertices())
def get_vertices(self):
return self.triangle.get_anchors_and_handles()[0][:3]
def reset_submobjects(self):
self.submobjects = [self.triangle] + self.values
return self
class IntroduceNotation(Scene):
def construct(self):
top = TOP()
equation = TexMobject("2^3 = 8")
equation.to_corner(UP+LEFT)
two, three, eight = [
top.put_on_vertex(i, num)
for i, num in enumerate([2, 3, 8])
]
self.play(FadeIn(equation))
self.wait()
self.play(ShowCreation(top))
for num in two, three, eight:
self.play(ShowCreation(num), run_time=2)
self.wait()
class ShowRule(Scene):
args_list = [(0,), (1,), (2,)]
@staticmethod
def args_to_string(index):
return str(index)
@staticmethod
def string_to_args(index_string):
result = int(index_string)
assert(result in [0, 1, 2])
return result
def construct(self, index):
equation = get_equation(index)
equation.to_corner(UP+LEFT)
top = TOP(2, 3, 8)
new_top = top.copy()
equals = TexMobject("=").scale(1.5)
new_top.next_to(equals, LEFT, buff = 1)
new_top.values[index].next_to(equals, RIGHT, buff = 1)
circle = Circle(
radius = 1.7*top.radius,
color = OPERATION_COLORS[index]
)
self.add(equation, top)
self.wait()
self.play(
Transform(top, new_top),
ShowCreation(equals)
)
circle.shift(new_top.triangle.get_center_of_mass())
new_circle = circle.copy()
new_top.put_on_vertex(index, new_circle)
self.wait()
self.play(ShowCreation(circle))
self.wait()
self.play(
Transform(circle, new_circle),
ApplyMethod(new_top.values[index].highlight, circle.color)
)
self.wait()
class AllThree(Scene):
def construct(self):
tops = []
equations = []
args = (2, 3, 8)
for i in 2, 1, 0:
new_args = list(args)
new_args[i] = None
top = TOP(*new_args, triangle_height_to_number_height = 2)
# top.highlight(OPERATION_COLORS[i])
top.shift(i*4.5*LEFT)
equation = get_equation(i, expression_only = True)
equation.scale(3)
equation.next_to(top, DOWN, buff = 0.7)
tops.append(top)
equations.append(equation)
VMobject(*tops+equations).center()
# name = TextMobject("Triangle of Power")
# name.to_edge(UP)
for top, eq in zip(tops, equations):
self.play(FadeIn(top), FadeIn(eq))
self.wait(3)
# self.play(Write(name))
self.wait()
class SixDifferentInverses(Scene):
def construct(self):
rules = get_inverse_rules()
vects = it.starmap(op.add, it.product(
[3*UP, 0.5*UP, 2*DOWN], [2*LEFT, 2*RIGHT]
))
for rule, vect in zip(rules, vects):
rule.shift(vect)
general_idea = TexMobject("f(f^{-1}(a)) = a")
self.play(Write(VMobject(*rules)))
self.wait()
for s, color in (rules[:4], GREEN), (rules[4:], RED):
mob = VMobject(*s)
self.play(ApplyMethod(mob.highlight, color))
self.wait()
self.play(ApplyMethod(mob.highlight, WHITE))
self.play(
ApplyMethod(VMobject(*rules[::2]).to_edge, LEFT),
ApplyMethod(VMobject(*rules[1::2]).to_edge, RIGHT),
GrowFromCenter(general_idea)
)
self.wait()
top_rules = get_top_inverse_rules()
for rule, top_rule in zip(rules, top_rules):
top_rule.scale_to_fit_height(1.5)
top_rule.center()
top_rule.shift(rule.get_center())
self.play(*map(FadeOut, rules))
self.remove(*rules)
self.play(*map(GrowFromCenter, top_rules))
self.wait()
self.remove(general_idea)
rules = get_inverse_rules()
original = None
for i, (top_rule, rule) in enumerate(zip(top_rules, rules)):
rule.center().to_edge(UP)
rule.highlight(GREEN if i < 4 else RED)
self.add(rule)
new_top_rule = top_rule.copy().center().scale(1.5)
anims = [Transform(top_rule, new_top_rule)]
if original is not None:
anims.append(FadeIn(original))
original = top_rule.copy()
self.play(*anims)
self.wait()
self.animate_top_rule(top_rule)
self.remove(rule)
def animate_top_rule(self, top_rule):
lil_top, lil_symbol, symbol_index = None, None, None
big_top = top_rule.submobjects[0]
equals, right_symbol = top_rule.submobjects[1].split()
for i, value in enumerate(big_top.values):
if isinstance(value, TOP):
lil_top = value
elif isinstance(value, TexMobject):
symbol_index = i
else:
lil_symbol_index = i
lil_symbol = lil_top.values[lil_symbol_index]
assert(lil_top is not None and lil_symbol is not None)
cancel_parts = [
VMobject(top.triangle, top.values[symbol_index])
for top in lil_top, big_top
]
new_symbol = lil_symbol.copy()
new_symbol.replace(right_symbol)
vect = equals.get_center() - right_symbol.get_center()
new_symbol.shift(2*vect[0]*RIGHT)
self.play(
Transform(*cancel_parts, rate_func = rush_into)
)
self.play(
FadeOut(VMobject(*cancel_parts)),
Transform(lil_symbol, new_symbol, rate_func = rush_from)
)
self.wait()
self.remove(lil_symbol, top_rule, VMobject(*cancel_parts))
class SixSixSix(Scene):
def construct(self):
randy = Randolph(mode = "pondering").to_corner()
bubble = ThoughtBubble().pin_to(randy)
rules = get_inverse_rules()
sixes = TexMobject(["6", "6", "6"], next_to_buff = 1)
sixes.to_corner(UP+RIGHT)
sixes = sixes.split()
speech_bubble = SpeechBubble()
speech_bubble.pin_to(randy)
speech_bubble.write("I'll just study art!")
self.add(randy)
self.play(ShowCreation(bubble))
bubble.add_content(VectorizedPoint())
for i, rule in enumerate(rules):
if i%2 == 0:
anim = ShowCreation(sixes[i/2])
else:
anim = Blink(randy)
self.play(
ApplyMethod(bubble.add_content, rule),
anim
)
self.wait()
self.wait()
words = speech_bubble.content
equation = bubble.content
speech_bubble.clear()
bubble.clear()
self.play(
ApplyMethod(randy.change_mode, "angry"),
Transform(bubble, speech_bubble),
Transform(equation, words),
FadeOut(VMobject(*sixes))
)
self.wait()
class AdditiveProperty(Scene):
def construct(self):
exp_rule, log_rule = self.write_old_style_rules()
t_exp_rule, t_log_rule = self.get_new_style_rules()
self.play(
ApplyMethod(exp_rule.to_edge, UP),
ApplyMethod(log_rule.to_edge, DOWN, 1.5)
)
t_exp_rule.next_to(exp_rule, DOWN)
t_exp_rule.highlight(GREEN)
t_log_rule.next_to(log_rule, UP)
t_log_rule.highlight(RED)
self.play(
FadeIn(t_exp_rule),
FadeIn(t_log_rule),
ApplyMethod(exp_rule.highlight, GREEN),
ApplyMethod(log_rule.highlight, RED),
)
self.wait()
all_tops = filter(
lambda m : isinstance(m, TOP),
t_exp_rule.split()+t_log_rule.split()
)
self.put_in_circles(all_tops)
self.highlight_appropriate_parts(t_exp_rule, t_log_rule)
def write_old_style_rules(self):
start = TexMobject("a^x a^y = a^{x+y}")
end = TexMobject("\\log_a(xy) = \\log_a(x) + \\log_a(y)")
start.shift(UP)
end.shift(DOWN)
a1, x1, a2, y1, eq1, a3, p1, x2, y2 = start.split()
a4, x3, y3, eq2, a5, x4, p2, a6, y4 = np.array(end.split())[
[3, 5, 6, 8, 12, 14, 16, 20, 22]
]
start_copy = start.copy()
self.play(Write(start_copy))
self.wait()
self.play(Transform(
VMobject(a1, x1, a2, y1, eq1, a3, p1, x2, a3.copy(), y2),
VMobject(a4, x3, a4.copy(), y3, eq2, a5, p2, x4, a6, y4)
))
self.play(Write(end))
self.clear()
self.add(start_copy, end)
self.wait()
return start_copy, end
def get_new_style_rules(self):
upper_mobs = [
TOP("a", "x", "R"), Dot(),
TOP("a", "y", "R"), TexMobject("="),
TOP("a", "x+y")
]
lower_mobs = [
TOP("a", None, "xy"), TexMobject("="),
TOP("a", None, "x"), TexMobject("+"),
TOP("a", None, "y"),
]
for mob in upper_mobs + lower_mobs:
if isinstance(mob, TOP):
mob.scale(0.5)
for group in upper_mobs, lower_mobs:
for m1, m2 in zip(group, group[1:]):
m2.next_to(m1)
for top in upper_mobs[0], upper_mobs[2]:
top.set_value(2, None)
upper_mobs = VMobject(*upper_mobs).center().shift(2*UP)
lower_mobs = VMobject(*lower_mobs).center().shift(2*DOWN)
return upper_mobs, lower_mobs
def put_in_circles(self, tops):
anims = []
for top in tops:
for i, value in enumerate(top.values):
if isinstance(value, VectorizedPoint):
index = i
circle = top.put_on_vertex(index, Circle(color = WHITE))
anims.append(
Transform(top.copy().highlight(YELLOW), circle)
)
self.add(*[anim.mobject for anim in anims])
self.wait()
self.play(*anims)
self.wait()
def highlight_appropriate_parts(self, t_exp_rule, t_log_rule):
#Horribly hacky
circle1 = t_exp_rule.split()[0].put_on_vertex(
2, Circle()
)
top_dot = t_exp_rule.split()[1]
circle2 = t_exp_rule.split()[2].put_on_vertex(
2, Circle()
)
top_plus = t_exp_rule.split()[4].values[1]
bottom_times = t_log_rule.split()[0].values[2]
circle3 = t_log_rule.split()[2].put_on_vertex(
1, Circle()
)
bottom_plus = t_log_rule.split()[3]
circle4 = t_log_rule.split()[4].put_on_vertex(
1, Circle()
)
mob_lists = [
[circle1, top_dot, circle2],
[top_plus],
[bottom_times],
[circle3, bottom_plus, circle4]
]
for mobs in mob_lists:
copies = VMobject(*mobs).copy()
self.play(ApplyMethod(
copies.highlight, YELLOW,
run_time = 0.5
))
self.play(ApplyMethod(
copies.scale_in_place, 1.2,
rate_func = there_and_back
))
self.wait()
self.remove(copies)
class DrawInsideTriangle(Scene):
def construct(self):
top = TOP()
top.scale(2)
dot = top.put_in_vertex(0, Dot())
plus = top.put_in_vertex(1, TexMobject("+"))
times = top.put_in_vertex(2, TexMobject("\\times"))
plus.highlight(GREEN)
times.highlight(YELLOW)
self.add(top)
self.wait()
for mob in dot, plus, times:
self.play(Write(mob, run_time = 1))
self.wait()
class ConstantOnTop(Scene):
def construct(self):
top = TOP()
dot = top.put_in_vertex(1, Dot())
times1 = top.put_in_vertex(0, TexMobject("\\times"))
times2 = top.put_in_vertex(2, TexMobject("\\times"))
times1.highlight(YELLOW)
times2.highlight(YELLOW)
three = top.put_on_vertex(1, "3")
lower_left_x = top.put_on_vertex(0, "x")
lower_right_x = top.put_on_vertex(2, "x")
x_cubed = TexMobject("x^3").to_edge(UP)
x_cubed.submobjects.reverse() #To align better
cube_root_x = TexMobject("\\sqrt[3]{x}").to_edge(UP)
self.add(top)
self.play(ShowCreation(three))
self.play(
FadeIn(lower_left_x),
Write(x_cubed),
run_time = 1
)
self.wait()
self.play(*[
Transform(*pair, path_arc = np.pi)
for pair in [
(lower_left_x, lower_right_x),
(x_cubed, cube_root_x),
]
])
self.wait(2)
for mob in dot, times1, times2:
self.play(ShowCreation(mob))
self.wait()
def get_const_top_TOP(*args):
top = TOP(*args)
dot = top.put_in_vertex(1, Dot())
times1 = top.put_in_vertex(0, TexMobject("\\times"))
times2 = top.put_in_vertex(2, TexMobject("\\times"))
times1.highlight(YELLOW)
times2.highlight(YELLOW)
top.add(dot, times1, times2)
return top
class MultiplyWithConstantTop(Scene):
def construct(self):
top1 = get_const_top_TOP("x", "3")
top2 = get_const_top_TOP("y", "3")
top3 = get_const_top_TOP("xy", "3")
times = TexMobject("\\times")
equals = TexMobject("=")
top_exp_equation = VMobject(
top1, times, top2, equals, top3
)
top_exp_equation.arrange_submobjects()
old_style_exp = TexMobject("(x^3)(y^3) = (xy)^3")
old_style_exp.to_edge(UP)
old_style_exp.highlight(GREEN)
old_style_rad = TexMobject("\\sqrt[3]{x} \\sqrt[3]{y} = \\sqrt[3]{xy}")
old_style_rad.to_edge(UP)
old_style_rad.highlight(RED)
self.add(top_exp_equation, old_style_exp)
self.wait(3)
old_tops = [top1, top2, top3]
new_tops = []
for top in old_tops:
new_top = top.copy()
new_top.put_on_vertex(2, new_top.values[0])
new_top.shift(0.5*LEFT)
new_tops.append(new_top)
self.play(
Transform(old_style_exp, old_style_rad),
Transform(
VMobject(*old_tops),
VMobject(*new_tops),
path_arc = np.pi/2
)
)
self.wait(3)
class RightStaysConstantQ(Scene):
def construct(self):
top1, top2, top3 = old_tops = [
TOP(None, s, "8")
for s in "x", "y", TexMobject("x?y")
]
q_mark = TexMobject("?").scale(2)
equation = VMobject(
top1, q_mark, top2, TexMobject("="), top3
)
equation.arrange_submobjects(buff = 0.7)
symbols_at_top = VMobject(*[
top.values[1]
for top in top1, top2, top3
])
symbols_at_lower_right = VMobject(*[
top.put_on_vertex(0, top.values[1].copy())
for top in top1, top2, top3
])
old_style_eq1 = TexMobject("\\sqrt[x]{8} ? \\sqrt[y]{8} = \\sqrt[x?y]{8}")
old_style_eq1.highlight(BLUE)
old_style_eq2 = TexMobject("\\log_x(8) ? \\log_y(8) = \\log_{x?y}(8)")
old_style_eq2.highlight(YELLOW)
for eq in old_style_eq1, old_style_eq2:
eq.to_edge(UP)
randy = Randolph()
randy.to_corner()
bubble = ThoughtBubble().pin_to(randy)
bubble.add_content(TOP(None, None, "8"))
self.add(randy, bubble)
self.play(ApplyMethod(randy.change_mode, "pondering"))
self.wait(3)
triangle = bubble.content.triangle
eight = bubble.content.values[2]
bubble.clear()
self.play(
Transform(triangle, equation),
FadeOut(eight),
ApplyPointwiseFunction(
lambda p : (p+2*DOWN)*15/np.linalg.norm(p+2*DOWN),
bubble
),
FadeIn(old_style_eq1),
ApplyMethod(randy.shift, 3*DOWN + 3*LEFT),
run_time = 2
)
self.remove(triangle)
self.add(equation)
self.wait(4)
self.play(
Transform(
symbols_at_top, symbols_at_lower_right,
path_arc = np.pi/2
),
Transform(old_style_eq1, old_style_eq2)
)
self.wait(2)
class AOplusB(Scene):
def construct(self):
self.add(TexMobject(
"a \\oplus b = \\dfrac{1}{\\frac{1}{a} + \\frac{1}{b}}"
).scale(2))
self.wait()
class ConstantLowerRight(Scene):
def construct(self):
top = TOP()
times = top.put_in_vertex(0, TexMobject("\\times"))
times.highlight(YELLOW)
oplus = top.put_in_vertex(1, TexMobject("\\oplus"))
oplus.highlight(BLUE)
dot = top.put_in_vertex(2, Dot())
eight = top.put_on_vertex(2, TexMobject("8"))
self.add(top)
self.play(ShowCreation(eight))
for mob in dot, oplus, times:
self.play(ShowCreation(mob))
self.wait()
top.add(eight)
top.add(times, oplus, dot)
top1, top2, top3 = tops = [
top.copy() for i in range(3)
]
big_oplus = TexMobject("\\oplus").scale(2).highlight(BLUE)
equals = TexMobject("=")
equation = VMobject(
top1, big_oplus, top2, equals, top3
)
equation.arrange_submobjects()
top3.shift(0.5*RIGHT)
x, y, xy = [
t.put_on_vertex(0, s)
for t, s in zip(tops, ["x", "y", "xy"])
]
old_style_eq = TexMobject(
"\\dfrac{1}{\\frac{1}{\\log_x(8)} + \\frac{1}{\\log_y(8)}} = \\log_{xy}(8)"
)
old_style_eq.to_edge(UP).highlight(RED)
triple_top_copy = VMobject(*[
top.copy() for i in range(3)
])
self.clear()
self.play(
Transform(triple_top_copy, VMobject(*tops)),
FadeIn(VMobject(x, y, xy, big_oplus, equals))
)
self.remove(triple_top_copy)
self.add(*tops)
self.play(Write(old_style_eq))
self.wait(3)
syms = VMobject(x, y, xy)
new_syms = VMobject(*[
t.put_on_vertex(1, s)
for t, s in zip(tops, ["x", "y", "x \\oplus y"])
])
new_old_style_eq = TexMobject(
"\\sqrt[x]{8} \\sqrt[y]{8} = \\sqrt[X]{8}"
)
X = new_old_style_eq.split()[-4]
frac = TexMobject("\\frac{1}{\\frac{1}{x} + \\frac{1}{y}}")
frac.replace(X)
frac_lower_right = frac.get_corner(DOWN+RIGHT)
frac.scale(2)
frac.shift(frac_lower_right - frac.get_corner(DOWN+RIGHT))
new_old_style_eq.submobjects[-4] = frac
new_old_style_eq.to_edge(UP)
new_old_style_eq.highlight(RED)
big_times = TexMobject("\\times").highlight(YELLOW)
big_times.shift(big_oplus.get_center())
self.play(
Transform(old_style_eq, new_old_style_eq),
Transform(syms, new_syms, path_arc = np.pi/2),
Transform(big_oplus, big_times)
)
self.wait(4)
class TowerExponentFrame(Scene):
def construct(self):
words = TextMobject("""
Consider an expression like $3^{3^3}$. It's
ambiguous whether this means $27^3$ or $3^{27}$,
which is the difference between $19{,}683$ and
$7{,}625{,}597{,}484{,}987$. But with the triangle
of power, the difference is crystal clear:
""")
words.scale_to_fit_width(2*SPACE_WIDTH-1)
words.to_edge(UP)
top1 = TOP(TOP(3, 3), 3)
top2 = TOP(3, (TOP(3, 3)))
for top in top1, top2:
top.next_to(words, DOWN)
top1.shift(3*LEFT)
top2.shift(3*RIGHT)
self.add(words, top1, top2)
self.wait()
class ExponentialGrowth(Scene):
def construct(self):
words = TextMobject("""
Let's say you are studying a certain growth rate,
and you come across an expression like $T^a$. It
matters a lot whether you consider $T$ or $a$
to be the variable, since exponential growth and
polynomial growth have very different flavors. The
nice thing about having a triangle that you can write
inside is that you can clarify this kind of ambiguity
by writing a little dot next to the constant and
a ``$\\sim$'' next to the variable.
""")
words.scale(0.75)
words.to_edge(UP)
top = TOP("T", "a")
top.next_to(words, DOWN)
dot = top.put_in_vertex(0, TexMobject("\\cdot"))
sim = top.put_in_vertex(1, TexMobject("\\sim"))
self.add(words, top, dot, sim)
self.show_frame()
self.wait()
class GoExplore(Scene):
def construct(self):
explore = TextMobject("Go explore!")
by_the_way = TextMobject("by the way \\dots")
by_the_way.shift(20*RIGHT)
self.play(Write(explore))
self.wait(4)
self.play(
ApplyMethod(
VMobject(explore, by_the_way).shift,
20*LEFT
)
)
self.wait(3)