mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 04:53:34 +08:00
932 lines
26 KiB
Python
932 lines
26 KiB
Python
from manimlib.imports import *
|
|
# import scipy
|
|
|
|
|
|
class FourierCirclesScene(Scene):
|
|
CONFIG = {
|
|
"n_vectors": 10,
|
|
"big_radius": 2,
|
|
"colors": [
|
|
BLUE_D,
|
|
BLUE_C,
|
|
BLUE_E,
|
|
GREY_BROWN,
|
|
],
|
|
"circle_style": {
|
|
"stroke_width": 2,
|
|
},
|
|
"vector_config": {
|
|
"buff": 0,
|
|
"max_tip_length_to_length_ratio": 0.35,
|
|
"tip_length": 0.15,
|
|
"max_stroke_width_to_length_ratio": 10,
|
|
"stroke_width": 2,
|
|
},
|
|
"circle_config": {
|
|
"stroke_width": 1,
|
|
},
|
|
"base_frequency": 1,
|
|
"slow_factor": 0.25,
|
|
"center_point": ORIGIN,
|
|
"parametric_function_step_size": 0.001,
|
|
"drawn_path_color": YELLOW,
|
|
"drawn_path_stroke_width": 2,
|
|
}
|
|
|
|
def setup(self):
|
|
self.slow_factor_tracker = ValueTracker(
|
|
self.slow_factor
|
|
)
|
|
self.vector_clock = ValueTracker(0)
|
|
self.vector_clock.add_updater(
|
|
lambda m, dt: m.increment_value(
|
|
self.get_slow_factor() * dt
|
|
)
|
|
)
|
|
self.add(self.vector_clock)
|
|
|
|
def get_slow_factor(self):
|
|
return self.slow_factor_tracker.get_value()
|
|
|
|
def get_vector_time(self):
|
|
return self.vector_clock.get_value()
|
|
|
|
#
|
|
def get_freqs(self):
|
|
n = self.n_vectors
|
|
all_freqs = list(range(n // 2, -n // 2, -1))
|
|
all_freqs.sort(key=abs)
|
|
return all_freqs
|
|
|
|
def get_coefficients(self):
|
|
return [complex(0) for x in range(self.n_vectors)]
|
|
|
|
def get_color_iterator(self):
|
|
return it.cycle(self.colors)
|
|
|
|
def get_rotating_vectors(self, freqs=None, coefficients=None):
|
|
vectors = VGroup()
|
|
self.center_tracker = VectorizedPoint(self.center_point)
|
|
|
|
if freqs is None:
|
|
freqs = self.get_freqs()
|
|
if coefficients is None:
|
|
coefficients = self.get_coefficients()
|
|
|
|
last_vector = None
|
|
for freq, coefficient in zip(freqs, coefficients):
|
|
if last_vector:
|
|
center_func = last_vector.get_end
|
|
else:
|
|
center_func = self.center_tracker.get_location
|
|
vector = self.get_rotating_vector(
|
|
coefficient=coefficient,
|
|
freq=freq,
|
|
center_func=center_func,
|
|
)
|
|
vectors.add(vector)
|
|
last_vector = vector
|
|
return vectors
|
|
|
|
def get_rotating_vector(self, coefficient, freq, center_func):
|
|
vector = Vector(RIGHT, **self.vector_config)
|
|
vector.scale(abs(coefficient))
|
|
if abs(coefficient) == 0:
|
|
phase = 0
|
|
else:
|
|
phase = np.log(coefficient).imag
|
|
vector.rotate(phase, about_point=ORIGIN)
|
|
vector.freq = freq
|
|
vector.coefficient = coefficient
|
|
vector.center_func = center_func
|
|
vector.add_updater(self.update_vector)
|
|
return vector
|
|
|
|
def update_vector(self, vector, dt):
|
|
time = self.get_vector_time()
|
|
coef = vector.coefficient
|
|
freq = vector.freq
|
|
phase = np.log(coef).imag
|
|
|
|
vector.set_length(abs(coef))
|
|
vector.set_angle(phase + time * freq * TAU)
|
|
vector.shift(vector.center_func() - vector.get_start())
|
|
return vector
|
|
|
|
def get_circles(self, vectors):
|
|
return VGroup(*[
|
|
self.get_circle(
|
|
vector,
|
|
color=color
|
|
)
|
|
for vector, color in zip(
|
|
vectors,
|
|
self.get_color_iterator()
|
|
)
|
|
])
|
|
|
|
def get_circle(self, vector, color=BLUE):
|
|
circle = Circle(color=color, **self.circle_config)
|
|
circle.center_func = vector.get_start
|
|
circle.radius_func = vector.get_length
|
|
circle.add_updater(self.update_circle)
|
|
return circle
|
|
|
|
def update_circle(self, circle):
|
|
circle.set_width(2 * circle.radius_func())
|
|
circle.move_to(circle.center_func())
|
|
return circle
|
|
|
|
def get_vector_sum_path(self, vectors, color=YELLOW):
|
|
coefs = [v.coefficient for v in vectors]
|
|
freqs = [v.freq for v in vectors]
|
|
center = vectors[0].get_start()
|
|
|
|
path = ParametricFunction(
|
|
lambda t: center + reduce(op.add, [
|
|
complex_to_R3(
|
|
coef * np.exp(TAU * 1j * freq * t)
|
|
)
|
|
for coef, freq in zip(coefs, freqs)
|
|
]),
|
|
t_min=0,
|
|
t_max=1,
|
|
color=color,
|
|
step_size=self.parametric_function_step_size,
|
|
)
|
|
return path
|
|
|
|
# TODO, this should be a general animated mobect
|
|
def get_drawn_path_alpha(self):
|
|
return self.get_vector_time()
|
|
|
|
def get_drawn_path(self, vectors, stroke_width=None, **kwargs):
|
|
if stroke_width is None:
|
|
stroke_width = self.drawn_path_stroke_width
|
|
path = self.get_vector_sum_path(vectors, **kwargs)
|
|
broken_path = CurvesAsSubmobjects(path)
|
|
broken_path.curr_time = 0
|
|
|
|
def update_path(path, dt):
|
|
# alpha = path.curr_time * self.get_slow_factor()
|
|
alpha = self.get_drawn_path_alpha()
|
|
n_curves = len(path)
|
|
for a, sp in zip(np.linspace(0, 1, n_curves), path):
|
|
b = alpha - a
|
|
if b < 0:
|
|
width = 0
|
|
else:
|
|
width = stroke_width * (1 - (b % 1))
|
|
sp.set_stroke(width=width)
|
|
path.curr_time += dt
|
|
return path
|
|
|
|
broken_path.set_color(self.drawn_path_color)
|
|
broken_path.add_updater(update_path)
|
|
return broken_path
|
|
|
|
def get_y_component_wave(self,
|
|
vectors,
|
|
left_x=1,
|
|
color=PINK,
|
|
n_copies=2,
|
|
right_shift_rate=5):
|
|
path = self.get_vector_sum_path(vectors)
|
|
wave = ParametricFunction(
|
|
lambda t: op.add(
|
|
right_shift_rate * t * LEFT,
|
|
path.function(t)[1] * UP
|
|
),
|
|
t_min=path.t_min,
|
|
t_max=path.t_max,
|
|
color=color,
|
|
)
|
|
wave_copies = VGroup(*[
|
|
wave.copy()
|
|
for x in range(n_copies)
|
|
])
|
|
wave_copies.arrange(RIGHT, buff=0)
|
|
top_point = wave_copies.get_top()
|
|
wave.creation = ShowCreation(
|
|
wave,
|
|
run_time=(1 / self.get_slow_factor()),
|
|
rate_func=linear,
|
|
)
|
|
cycle_animation(wave.creation)
|
|
wave.add_updater(lambda m: m.shift(
|
|
(m.get_left()[0] - left_x) * LEFT
|
|
))
|
|
|
|
def update_wave_copies(wcs):
|
|
index = int(
|
|
wave.creation.total_time * self.get_slow_factor()
|
|
)
|
|
wcs[:index].match_style(wave)
|
|
wcs[index:].set_stroke(width=0)
|
|
wcs.next_to(wave, RIGHT, buff=0)
|
|
wcs.align_to(top_point, UP)
|
|
wave_copies.add_updater(update_wave_copies)
|
|
|
|
return VGroup(wave, wave_copies)
|
|
|
|
def get_wave_y_line(self, vectors, wave):
|
|
return DashedLine(
|
|
vectors[-1].get_end(),
|
|
wave[0].get_end(),
|
|
stroke_width=1,
|
|
dash_length=DEFAULT_DASH_LENGTH * 0.5,
|
|
)
|
|
|
|
# Computing Fourier series
|
|
# i.e. where all the math happens
|
|
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
|
|
if freqs is None:
|
|
freqs = self.get_freqs()
|
|
dt = 1 / n_samples
|
|
ts = np.arange(0, 1, dt)
|
|
samples = np.array([
|
|
path.point_from_proportion(t)
|
|
for t in ts
|
|
])
|
|
samples -= self.center_point
|
|
complex_samples = samples[:, 0] + 1j * samples[:, 1]
|
|
|
|
result = []
|
|
for freq in freqs:
|
|
riemann_sum = np.array([
|
|
np.exp(-TAU * 1j * freq * t) * cs
|
|
for t, cs in zip(ts, complex_samples)
|
|
]).sum() * dt
|
|
result.append(riemann_sum)
|
|
|
|
return result
|
|
|
|
|
|
class FourierSeriesIntroBackground4(FourierCirclesScene):
|
|
CONFIG = {
|
|
"n_vectors": 4,
|
|
"center_point": 4 * LEFT,
|
|
"run_time": 30,
|
|
"big_radius": 1.5,
|
|
}
|
|
|
|
def construct(self):
|
|
circles = self.get_circles()
|
|
path = self.get_drawn_path(circles)
|
|
wave = self.get_y_component_wave(circles)
|
|
h_line = always_redraw(
|
|
lambda: self.get_wave_y_line(circles, wave)
|
|
)
|
|
|
|
# Why?
|
|
circles.update(-1 / self.camera.frame_rate)
|
|
#
|
|
self.add(circles, path, wave, h_line)
|
|
self.wait(self.run_time)
|
|
|
|
def get_ks(self):
|
|
return np.arange(1, 2 * self.n_vectors + 1, 2)
|
|
|
|
def get_freqs(self):
|
|
return self.base_frequency * self.get_ks()
|
|
|
|
def get_coefficients(self):
|
|
return self.big_radius / self.get_ks()
|
|
|
|
|
|
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
|
|
CONFIG = {
|
|
"n_vectors": 8,
|
|
}
|
|
|
|
|
|
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
|
|
CONFIG = {
|
|
"n_vectors": 12,
|
|
}
|
|
|
|
|
|
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
|
|
CONFIG = {
|
|
"n_vectors": 20,
|
|
}
|
|
|
|
|
|
class FourierOfPiSymbol(FourierCirclesScene):
|
|
CONFIG = {
|
|
"n_vectors": 51,
|
|
"center_point": ORIGIN,
|
|
"slow_factor": 0.1,
|
|
"n_cycles": 1,
|
|
"tex": "\\pi",
|
|
"start_drawn": False,
|
|
"max_circle_stroke_width": 1,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_vectors_circles_path()
|
|
for n in range(self.n_cycles):
|
|
self.run_one_cycle()
|
|
|
|
def add_vectors_circles_path(self):
|
|
path = self.get_path()
|
|
coefs = self.get_coefficients_of_path(path)
|
|
vectors = self.get_rotating_vectors(coefficients=coefs)
|
|
circles = self.get_circles(vectors)
|
|
self.set_decreasing_stroke_widths(circles)
|
|
# approx_path = self.get_vector_sum_path(circles)
|
|
drawn_path = self.get_drawn_path(vectors)
|
|
if self.start_drawn:
|
|
self.vector_clock.increment_value(1)
|
|
|
|
self.add(path)
|
|
self.add(vectors)
|
|
self.add(circles)
|
|
self.add(drawn_path)
|
|
|
|
self.vectors = vectors
|
|
self.circles = circles
|
|
self.path = path
|
|
self.drawn_path = drawn_path
|
|
|
|
def run_one_cycle(self):
|
|
time = 1 / self.slow_factor
|
|
self.wait(time)
|
|
|
|
def set_decreasing_stroke_widths(self, circles):
|
|
mcsw = self.max_circle_stroke_width
|
|
for k, circle in zip(it.count(1), circles):
|
|
circle.set_stroke(width=max(
|
|
# mcsw / np.sqrt(k),
|
|
mcsw / k,
|
|
mcsw,
|
|
))
|
|
return circles
|
|
|
|
def get_path(self):
|
|
tex_mob = TexMobject(self.tex)
|
|
tex_mob.set_height(6)
|
|
path = tex_mob.family_members_with_points()[0]
|
|
path.set_fill(opacity=0)
|
|
path.set_stroke(WHITE, 1)
|
|
return path
|
|
|
|
|
|
class FourierOfName(FourierOfPiSymbol, MovingCameraScene):
|
|
CONFIG = {
|
|
"n_vectors": 100,
|
|
"name_color": WHITE,
|
|
"name_text": "Abc",
|
|
"time_per_symbol": 5,
|
|
"slow_factor": 1 / 5,
|
|
"parametric_function_step_size": 0.01,
|
|
}
|
|
|
|
def construct(self):
|
|
name = TextMobject(self.name_text)
|
|
max_width = FRAME_WIDTH - 2
|
|
max_height = FRAME_HEIGHT - 2
|
|
name.set_width(max_width)
|
|
if name.get_height() > max_height:
|
|
name.set_height(max_height)
|
|
|
|
frame = self.camera.frame
|
|
frame.save_state()
|
|
|
|
vectors = VGroup(VectorizedPoint())
|
|
circles = VGroup(VectorizedPoint())
|
|
for path in name.family_members_with_points():
|
|
for subpath in path.get_subpaths():
|
|
sp_mob = VMobject()
|
|
sp_mob.set_points(subpath)
|
|
coefs = self.get_coefficients_of_path(sp_mob)
|
|
new_vectors = self.get_rotating_vectors(
|
|
coefficients=coefs
|
|
)
|
|
new_circles = self.get_circles(new_vectors)
|
|
self.set_decreasing_stroke_widths(new_circles)
|
|
|
|
drawn_path = self.get_drawn_path(new_vectors)
|
|
drawn_path.clear_updaters()
|
|
drawn_path.set_stroke(self.name_color, 3)
|
|
|
|
static_vectors = VMobject().become(new_vectors)
|
|
static_circles = VMobject().become(new_circles)
|
|
# static_circles = new_circles.deepcopy()
|
|
# static_vectors.clear_updaters()
|
|
# static_circles.clear_updaters()
|
|
|
|
self.play(
|
|
Transform(vectors, static_vectors, remover=True),
|
|
Transform(circles, static_circles, remover=True),
|
|
frame.set_height, 1.5 * name.get_height(),
|
|
frame.move_to, path,
|
|
)
|
|
|
|
self.add(new_vectors, new_circles)
|
|
self.vector_clock.set_value(0)
|
|
self.play(
|
|
ShowCreation(drawn_path),
|
|
rate_func=linear,
|
|
run_time=self.time_per_symbol
|
|
)
|
|
self.remove(new_vectors, new_circles)
|
|
self.add(static_vectors, static_circles)
|
|
|
|
vectors = static_vectors
|
|
circles = static_circles
|
|
self.play(
|
|
FadeOut(vectors),
|
|
Restore(frame),
|
|
run_time=2
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class FourierOfPiSymbol5(FourierOfPiSymbol):
|
|
CONFIG = {
|
|
"n_vectors": 5,
|
|
"run_time": 10,
|
|
}
|
|
|
|
|
|
class FourierOfTrebleClef(FourierOfPiSymbol):
|
|
CONFIG = {
|
|
"n_vectors": 101,
|
|
"run_time": 10,
|
|
"start_drawn": True,
|
|
"file_name": "TrebleClef",
|
|
"height": 7.5,
|
|
}
|
|
|
|
def get_shape(self):
|
|
shape = SVGMobject(self.file_name)
|
|
return shape
|
|
|
|
def get_path(self):
|
|
shape = self.get_shape()
|
|
path = shape.family_members_with_points()[0]
|
|
path.set_height(self.height)
|
|
path.set_fill(opacity=0)
|
|
path.set_stroke(WHITE, 0)
|
|
return path
|
|
|
|
|
|
class FourierOfIP(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"file_name": "IP_logo2",
|
|
"height": 6,
|
|
"n_vectors": 100,
|
|
}
|
|
|
|
# def construct(self):
|
|
# path = self.get_path()
|
|
# self.add(path)
|
|
|
|
def get_shape(self):
|
|
shape = SVGMobject(self.file_name)
|
|
return shape
|
|
|
|
def get_path(self):
|
|
shape = self.get_shape()
|
|
path = shape.family_members_with_points()[0]
|
|
path.add_line_to(path.get_start())
|
|
# path.make_smooth()
|
|
|
|
path.set_height(self.height)
|
|
path.set_fill(opacity=0)
|
|
path.set_stroke(WHITE, 0)
|
|
return path
|
|
|
|
|
|
class FourierOfEighthNote(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"file_name": "EighthNote"
|
|
}
|
|
|
|
|
|
class FourierOfN(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"height": 6,
|
|
"n_vectors": 1000,
|
|
}
|
|
|
|
def get_shape(self):
|
|
return TexMobject("N")
|
|
|
|
|
|
class FourierNailAndGear(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"height": 6,
|
|
"n_vectors": 200,
|
|
"run_time": 100,
|
|
"slow_factor": 0.01,
|
|
"parametric_function_step_size": 0.0001,
|
|
"arrow_config": {
|
|
"tip_length": 0.1,
|
|
"stroke_width": 2,
|
|
}
|
|
}
|
|
|
|
def get_shape(self):
|
|
shape = SVGMobject("Nail_And_Gear")[1]
|
|
return shape
|
|
|
|
|
|
class FourierBatman(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"height": 4,
|
|
"n_vectors": 100,
|
|
"run_time": 10,
|
|
"arrow_config": {
|
|
"tip_length": 0.1,
|
|
"stroke_width": 2,
|
|
}
|
|
}
|
|
|
|
def get_shape(self):
|
|
shape = SVGMobject("BatmanLogo")[1]
|
|
return shape
|
|
|
|
|
|
class FourierHeart(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"height": 4,
|
|
"n_vectors": 100,
|
|
"run_time": 10,
|
|
"arrow_config": {
|
|
"tip_length": 0.1,
|
|
"stroke_width": 2,
|
|
}
|
|
}
|
|
|
|
def get_shape(self):
|
|
shape = SuitSymbol("hearts")
|
|
return shape
|
|
|
|
def get_drawn_path(self, *args, **kwargs):
|
|
kwargs["stroke_width"] = 5
|
|
path = super().get_drawn_path(*args, **kwargs)
|
|
path.set_color(PINK)
|
|
return path
|
|
|
|
|
|
class FourierNDQ(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"height": 4,
|
|
"n_vectors": 1000,
|
|
"run_time": 10,
|
|
"arrow_config": {
|
|
"tip_length": 0.1,
|
|
"stroke_width": 2,
|
|
}
|
|
}
|
|
|
|
def get_shape(self):
|
|
path = VMobject()
|
|
shape = TexMobject("NDQ")
|
|
for sp in shape.family_members_with_points():
|
|
path.append_points(sp.points)
|
|
return path
|
|
|
|
|
|
class FourierGoogleG(FourierOfTrebleClef):
|
|
CONFIG = {
|
|
"n_vectors": 10,
|
|
"height": 5,
|
|
"g_colors": [
|
|
"#4285F4",
|
|
"#DB4437",
|
|
"#F4B400",
|
|
"#0F9D58",
|
|
]
|
|
}
|
|
|
|
def get_shape(self):
|
|
g = SVGMobject("google_logo")[5]
|
|
g.center()
|
|
self.add(g)
|
|
return g
|
|
|
|
def get_drawn_path(self, *args, **kwargs):
|
|
kwargs["stroke_width"] = 7
|
|
path = super().get_drawn_path(*args, **kwargs)
|
|
|
|
blue, red, yellow, green = self.g_colors
|
|
|
|
path[:250].set_color(blue)
|
|
path[250:333].set_color(green)
|
|
path[333:370].set_color(yellow)
|
|
path[370:755].set_color(red)
|
|
path[755:780].set_color(yellow)
|
|
path[780:860].set_color(green)
|
|
path[860:].set_color(blue)
|
|
|
|
return path
|
|
|
|
|
|
class ExplainCircleAnimations(FourierCirclesScene):
|
|
CONFIG = {
|
|
"n_vectors": 100,
|
|
"center_point": 2 * DOWN,
|
|
"n_top_circles": 9,
|
|
"path_height": 3,
|
|
}
|
|
|
|
def construct(self):
|
|
self.add_path()
|
|
self.add_circles()
|
|
self.wait(8)
|
|
self.organize_circles_in_a_row()
|
|
self.show_frequencies()
|
|
self.show_examples_for_frequencies()
|
|
self.show_as_vectors()
|
|
self.show_vector_sum()
|
|
self.tweak_starting_vectors()
|
|
|
|
def add_path(self):
|
|
self.path = self.get_path()
|
|
self.add(self.path)
|
|
|
|
def add_circles(self):
|
|
coefs = self.get_coefficients_of_path(self.path)
|
|
self.circles = self.get_circles(coefficients=coefs)
|
|
|
|
self.add(self.circles)
|
|
self.drawn_path = self.get_drawn_path(self.circles)
|
|
self.add(self.drawn_path)
|
|
|
|
def organize_circles_in_a_row(self):
|
|
circles = self.circles
|
|
top_circles = circles[:self.n_top_circles].copy()
|
|
|
|
center_trackers = VGroup()
|
|
for circle in top_circles:
|
|
tracker = VectorizedPoint(circle.center_func())
|
|
circle.center_func = tracker.get_location
|
|
center_trackers.add(tracker)
|
|
tracker.freq = circle.freq
|
|
tracker.circle = circle
|
|
|
|
center_trackers.submobjects.sort(
|
|
key=lambda m: m.freq
|
|
)
|
|
center_trackers.generate_target()
|
|
right_buff = 1.45
|
|
center_trackers.target.arrange(RIGHT, buff=right_buff)
|
|
center_trackers.target.to_edge(UP, buff=1.25)
|
|
|
|
self.add(top_circles)
|
|
self.play(
|
|
MoveToTarget(center_trackers),
|
|
run_time=2
|
|
)
|
|
self.wait(4)
|
|
|
|
self.top_circles = top_circles
|
|
self.center_trackers = center_trackers
|
|
|
|
def show_frequencies(self):
|
|
center_trackers = self.center_trackers
|
|
|
|
freq_numbers = VGroup()
|
|
for ct in center_trackers:
|
|
number = Integer(ct.freq)
|
|
number.next_to(ct, DOWN, buff=1)
|
|
freq_numbers.add(number)
|
|
ct.circle.number = number
|
|
|
|
ld, rd = [
|
|
TexMobject("\\dots")
|
|
for x in range(2)
|
|
]
|
|
ld.next_to(freq_numbers, LEFT, MED_LARGE_BUFF)
|
|
rd.next_to(freq_numbers, RIGHT, MED_LARGE_BUFF)
|
|
freq_numbers.add_to_back(ld)
|
|
freq_numbers.add(rd)
|
|
|
|
freq_word = TextMobject("Frequencies")
|
|
freq_word.scale(1.5)
|
|
freq_word.set_color(YELLOW)
|
|
freq_word.next_to(freq_numbers, DOWN, MED_LARGE_BUFF)
|
|
|
|
self.play(
|
|
LaggedStartMap(
|
|
FadeInFromDown, freq_numbers
|
|
)
|
|
)
|
|
self.play(
|
|
Write(freq_word),
|
|
LaggedStartMap(
|
|
ShowCreationThenFadeAround, freq_numbers,
|
|
)
|
|
)
|
|
self.wait(2)
|
|
|
|
self.freq_numbers = freq_numbers
|
|
self.freq_word = freq_word
|
|
|
|
def show_examples_for_frequencies(self):
|
|
top_circles = self.top_circles
|
|
c1, c2, c3 = [
|
|
list(filter(
|
|
lambda c: c.freq == k,
|
|
top_circles
|
|
))[0]
|
|
for k in (1, 2, 3)
|
|
]
|
|
|
|
neg_circles = VGroup(*filter(
|
|
lambda c: c.freq < 0,
|
|
top_circles
|
|
))
|
|
|
|
for c in [c1, c2, c3, *neg_circles]:
|
|
c.rect = SurroundingRectangle(c)
|
|
|
|
self.play(
|
|
ShowCreation(c2.rect),
|
|
WiggleOutThenIn(c2.number),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
ReplacementTransform(c2.rect, c1.rect),
|
|
)
|
|
self.play(FadeOut(c1.rect))
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(c3.rect),
|
|
WiggleOutThenIn(c3.number),
|
|
)
|
|
self.play(
|
|
FadeOut(c3.rect),
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
LaggedStart(*[
|
|
ShowCreationThenFadeOut(c.rect)
|
|
for c in neg_circles
|
|
])
|
|
)
|
|
self.wait(3)
|
|
self.play(FadeOut(self.freq_word))
|
|
|
|
def show_as_vectors(self):
|
|
top_circles = self.top_circles
|
|
top_vectors = self.get_rotating_vectors(top_circles)
|
|
top_vectors.set_color(WHITE)
|
|
|
|
original_circles = top_circles.copy()
|
|
self.play(
|
|
FadeIn(top_vectors),
|
|
top_circles.set_opacity, 0,
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
top_circles.match_style, original_circles
|
|
)
|
|
self.remove(top_vectors)
|
|
|
|
self.top_vectors = top_vectors
|
|
|
|
def show_vector_sum(self):
|
|
trackers = self.center_trackers.copy()
|
|
trackers.sort(
|
|
submob_func=lambda t: abs(t.circle.freq - 0.1)
|
|
)
|
|
plane = self.plane = NumberPlane(
|
|
x_min=-3,
|
|
x_max=3,
|
|
y_min=-2,
|
|
y_max=2,
|
|
axis_config={
|
|
"stroke_color": LIGHT_GREY,
|
|
}
|
|
)
|
|
plane.set_stroke(width=1)
|
|
plane.fade(0.5)
|
|
plane.move_to(self.center_point)
|
|
|
|
self.play(
|
|
FadeOut(self.drawn_path),
|
|
FadeOut(self.circles),
|
|
self.slow_factor_tracker.set_value, 0.05,
|
|
)
|
|
self.add(plane, self.path)
|
|
self.play(FadeIn(plane))
|
|
|
|
new_circles = VGroup()
|
|
last_tracker = None
|
|
for tracker in trackers:
|
|
if last_tracker:
|
|
tracker.new_location_func = last_tracker.circle.get_start
|
|
else:
|
|
tracker.new_location_func = lambda: self.center_point
|
|
|
|
original_circle = tracker.circle
|
|
tracker.circle = original_circle.copy()
|
|
tracker.circle.center_func = tracker.get_location
|
|
new_circles.add(tracker.circle)
|
|
|
|
self.add(tracker, tracker.circle)
|
|
start_point = tracker.get_location()
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
tracker, lambda t, a: t.move_to(
|
|
interpolate(
|
|
start_point,
|
|
tracker.new_location_func(),
|
|
a,
|
|
)
|
|
),
|
|
run_time=2
|
|
)
|
|
)
|
|
tracker.add_updater(lambda t: t.move_to(
|
|
t.new_location_func()
|
|
))
|
|
self.wait(2)
|
|
last_tracker = tracker
|
|
|
|
self.wait(3)
|
|
|
|
self.clear()
|
|
self.slow_factor_tracker.set_value(0.1)
|
|
self.add(
|
|
self.top_circles,
|
|
self.freq_numbers,
|
|
self.path,
|
|
)
|
|
self.add_circles()
|
|
for tc in self.top_circles:
|
|
for c in self.circles:
|
|
if c.freq == tc.freq:
|
|
tc.rotate(
|
|
angle_of_vector(c.get_start() - c.get_center()) -
|
|
angle_of_vector(tc.get_start() - tc.get_center())
|
|
)
|
|
self.wait(10)
|
|
|
|
def tweak_starting_vectors(self):
|
|
top_circles = self.top_circles
|
|
circles = self.circles
|
|
path = self.path
|
|
drawn_path = self.drawn_path
|
|
|
|
new_path = self.get_new_path()
|
|
new_coefs = self.get_coefficients_of_path(new_path)
|
|
new_circles = self.get_circles(coefficients=new_coefs)
|
|
|
|
new_top_circles = VGroup()
|
|
new_top_vectors = VGroup()
|
|
for top_circle in top_circles:
|
|
for circle in new_circles:
|
|
if circle.freq == top_circle.freq:
|
|
new_top_circle = circle.copy()
|
|
new_top_circle.center_func = top_circle.get_center
|
|
new_top_vector = self.get_rotating_vector(
|
|
new_top_circle
|
|
)
|
|
new_top_circles.add(new_top_circle)
|
|
new_top_vectors.add(new_top_vector)
|
|
|
|
self.play(
|
|
self.slow_factor_tracker.set_value, 0,
|
|
FadeOut(drawn_path)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(top_circles, new_top_circles),
|
|
ReplacementTransform(circles, new_circles),
|
|
FadeOut(path),
|
|
run_time=3,
|
|
)
|
|
new_drawn_path = self.get_drawn_path(
|
|
new_circles, stroke_width=4,
|
|
)
|
|
self.add(new_drawn_path)
|
|
self.slow_factor_tracker.set_value(0.1)
|
|
self.wait(20)
|
|
|
|
#
|
|
def configure_path(self, path):
|
|
path.set_stroke(WHITE, 1)
|
|
path.set_fill(BLACK, opacity=1)
|
|
path.set_height(self.path_height)
|
|
path.move_to(self.center_point)
|
|
return path
|
|
|
|
def get_path(self):
|
|
tex = TexMobject("f")
|
|
path = tex.family_members_with_points()[0]
|
|
self.configure_path(path)
|
|
return path
|
|
# return Square().set_height(3)
|
|
|
|
def get_new_path(self):
|
|
shape = SVGMobject("TrebleClef")
|
|
path = shape.family_members_with_points()[0]
|
|
self.configure_path(path)
|
|
path.scale(1.5, about_edge=DOWN)
|
|
return path
|