Skip to content

Commit 2680a9c

Browse files
committed
Change Fourier scene to center on vectors, not circles
1 parent e421d36 commit 2680a9c

File tree

1 file changed

+84
-90
lines changed

1 file changed

+84
-90
lines changed

active_projects/ode/part2/fourier_series.py

Lines changed: 84 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class FourierCirclesScene(Scene):
66
CONFIG = {
7-
"n_circles": 10,
7+
"n_vectors": 10,
88
"big_radius": 2,
99
"colors": [
1010
BLUE_D,
@@ -15,14 +15,16 @@ class FourierCirclesScene(Scene):
1515
"circle_style": {
1616
"stroke_width": 2,
1717
},
18-
"arrow_config": {
18+
"vector_config": {
1919
"buff": 0,
2020
"max_tip_length_to_length_ratio": 0.35,
2121
"tip_length": 0.15,
2222
"max_stroke_width_to_length_ratio": 10,
2323
"stroke_width": 2,
2424
},
25-
"use_vectors": True,
25+
"circle_config": {
26+
"stroke_width": 1,
27+
},
2628
"base_frequency": 1,
2729
"slow_factor": 0.25,
2830
"center_point": ORIGIN,
@@ -39,101 +41,90 @@ def get_slow_factor(self):
3941

4042
#
4143
def get_freqs(self):
42-
n = self.n_circles
44+
n = self.n_vectors
4345
all_freqs = list(range(n // 2, -n // 2, -1))
4446
all_freqs.sort(key=abs)
4547
return all_freqs
4648

4749
def get_coefficients(self):
48-
return [complex(0) for x in range(self.n_circles)]
50+
return [complex(0) for x in range(self.n_vectors)]
4951

5052
def get_color_iterator(self):
5153
return it.cycle(self.colors)
5254

53-
def get_circles(self, freqs=None, coefficients=None):
54-
circles = VGroup()
55-
color_iterator = self.get_color_iterator()
55+
def get_rotating_vectors(self, freqs=None, coefficients=None):
56+
vectors = VGroup()
5657
self.center_tracker = VectorizedPoint(self.center_point)
5758

5859
if freqs is None:
5960
freqs = self.get_freqs()
6061
if coefficients is None:
6162
coefficients = self.get_coefficients()
6263

63-
last_circle = None
64+
last_vector = None
6465
for freq, coefficient in zip(freqs, coefficients):
65-
if last_circle:
66-
center_func = last_circle.get_start
66+
if last_vector:
67+
center_func = last_vector.get_end
6768
else:
6869
center_func = self.center_tracker.get_location
69-
circle = self.get_circle(
70+
vector = self.get_rotating_vector(
7071
coefficient=coefficient,
7172
freq=freq,
72-
color=next(color_iterator),
7373
center_func=center_func,
7474
)
75-
circles.add(circle)
76-
last_circle = circle
77-
return circles
75+
vectors.add(vector)
76+
last_vector = vector
77+
return vectors
7878

79-
def get_circle(self, coefficient, freq, color, center_func):
80-
radius = abs(coefficient)
79+
def get_rotating_vector(self, coefficient, freq, center_func):
80+
vector = Vector(RIGHT, **self.vector_config)
81+
vector.scale(abs(coefficient))
8182
phase = np.log(coefficient).imag
82-
circle = Circle(
83-
radius=radius,
84-
color=color,
85-
**self.circle_style,
83+
vector.rotate(phase, about_point=ORIGIN)
84+
vector.freq = freq
85+
vector.phase = phase
86+
vector.coefficient = coefficient
87+
vector.center_func = center_func
88+
vector.add_updater(self.update_vector)
89+
return vector
90+
91+
def update_vector(self, vector, dt):
92+
vector.rotate(
93+
self.get_slow_factor() * vector.freq * dt * TAU
8694
)
87-
line_points = (
88-
circle.get_center(),
89-
circle.get_start(),
95+
vector.shift(
96+
vector.center_func() - vector.get_start()
9097
)
91-
if self.use_vectors:
92-
circle.radial_line = Arrow(
93-
*line_points,
94-
**self.arrow_config,
98+
return vector
99+
100+
def get_circles(self, vectors):
101+
return VGroup(*[
102+
self.get_circle(
103+
vector,
104+
color=color
95105
)
96-
else:
97-
circle.radial_line = Line(
98-
*line_points,
99-
color=WHITE,
100-
**self.circle_style,
106+
for vector, color in zip(
107+
vectors,
108+
self.get_color_iterator()
101109
)
102-
circle.add(circle.radial_line)
103-
circle.freq = freq
104-
circle.phase = phase
105-
circle.rotate(phase)
106-
circle.coefficient = coefficient
107-
circle.center_func = center_func
110+
])
111+
112+
def get_circle(self, vector, color=BLUE):
113+
circle = Circle(color=color, **self.circle_config)
114+
circle.vector = vector
115+
vector.circle = circle
108116
circle.add_updater(self.update_circle)
109117
return circle
110118

111-
def update_circle(self, circle, dt):
112-
circle.rotate(
113-
self.get_slow_factor() * circle.freq * dt * TAU
114-
)
115-
circle.move_to(circle.center_func())
119+
def update_circle(self, circle):
120+
circle.set_width(2 * circle.vector.get_length())
121+
circle.move_to(circle.vector.get_start())
116122
return circle
117123

118-
def get_rotating_vectors(self, circles):
119-
return VGroup(*[
120-
self.get_rotating_vector(circle)
121-
for circle in circles
122-
])
123-
124-
def get_rotating_vector(self, circle):
125-
vector = Vector(RIGHT, color=WHITE)
126-
vector.add_updater(lambda v, dt: v.put_start_and_end_on(
127-
circle.get_center(),
128-
circle.get_start(),
129-
))
130-
circle.vector = vector
131-
return vector
132-
133-
def get_circle_end_path(self, circles, color=YELLOW):
134-
coefs = [c.coefficient for c in circles]
135-
freqs = [c.freq for c in circles]
136-
center = circles[0].get_center()
124+
def get_vector_sum_path(self, vectors, color=YELLOW):
125+
coefs = [v.coefficient for v in vectors]
126+
freqs = [v.freq for v in vectors]
127+
center = vectors[0].get_start()
137128

138129
path = ParametricFunction(
139130
lambda t: center + reduce(op.add, [
@@ -150,8 +141,8 @@ def get_circle_end_path(self, circles, color=YELLOW):
150141
return path
151142

152143
# TODO, this should be a general animated mobect
153-
def get_drawn_path(self, circles, stroke_width=2, **kwargs):
154-
path = self.get_circle_end_path(circles, **kwargs)
144+
def get_drawn_path(self, vectors, stroke_width=2, **kwargs):
145+
path = self.get_vector_sum_path(vectors, **kwargs)
155146
broken_path = CurvesAsSubmobjects(path)
156147
broken_path.curr_time = 0
157148

@@ -173,12 +164,12 @@ def update_path(path, dt):
173164
return broken_path
174165

175166
def get_y_component_wave(self,
176-
circles,
167+
vectors,
177168
left_x=1,
178169
color=PINK,
179170
n_copies=2,
180171
right_shift_rate=5):
181-
path = self.get_circle_end_path(circles)
172+
path = self.get_vector_sum_path(vectors)
182173
wave = ParametricFunction(
183174
lambda t: op.add(
184175
right_shift_rate * t * LEFT,
@@ -216,15 +207,16 @@ def update_wave_copies(wcs):
216207

217208
return VGroup(wave, wave_copies)
218209

219-
def get_wave_y_line(self, circles, wave):
210+
def get_wave_y_line(self, vectors, wave):
220211
return DashedLine(
221-
circles[-1].get_start(),
212+
vectors[-1].get_end(),
222213
wave[0].get_end(),
223214
stroke_width=1,
224215
dash_length=DEFAULT_DASH_LENGTH * 0.5,
225216
)
226217

227218
# Computing Fourier series
219+
# i.e. where all the math happens
228220
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
229221
if freqs is None:
230222
freqs = self.get_freqs()
@@ -250,7 +242,7 @@ def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
250242

251243
class FourierSeriesIntroBackground4(FourierCirclesScene):
252244
CONFIG = {
253-
"n_circles": 4,
245+
"n_vectors": 4,
254246
"center_point": 4 * LEFT,
255247
"run_time": 30,
256248
"big_radius": 1.5,
@@ -271,7 +263,7 @@ def construct(self):
271263
self.wait(self.run_time)
272264

273265
def get_ks(self):
274-
return np.arange(1, 2 * self.n_circles + 1, 2)
266+
return np.arange(1, 2 * self.n_vectors + 1, 2)
275267

276268
def get_freqs(self):
277269
return self.base_frequency * self.get_ks()
@@ -282,25 +274,25 @@ def get_coefficients(self):
282274

283275
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
284276
CONFIG = {
285-
"n_circles": 8,
277+
"n_vectors": 8,
286278
}
287279

288280

289281
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
290282
CONFIG = {
291-
"n_circles": 12,
283+
"n_vectors": 12,
292284
}
293285

294286

295287
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
296288
CONFIG = {
297-
"n_circles": 20,
289+
"n_vectors": 20,
298290
}
299291

300292

301293
class FourierOfPiSymbol(FourierCirclesScene):
302294
CONFIG = {
303-
"n_circles": 50,
295+
"n_vectors": 51,
304296
"center_point": ORIGIN,
305297
"slow_factor": 0.1,
306298
"run_time": 30,
@@ -312,14 +304,16 @@ def construct(self):
312304
path = self.get_path()
313305
coefs = self.get_coefficients_of_path(path)
314306

315-
circles = self.get_circles(coefficients=coefs)
307+
vectors = self.get_rotating_vectors(coefficients=coefs)
308+
circles = self.get_circles(vectors)
316309
self.set_decreasing_stroke_widths(circles)
317-
# approx_path = self.get_circle_end_path(circles)
318-
drawn_path = self.get_drawn_path(circles)
310+
# approx_path = self.get_vector_sum_path(circles)
311+
drawn_path = self.get_drawn_path(vectors)
319312
if self.start_drawn:
320313
drawn_path.curr_time = 1 / self.slow_factor
321314

322315
self.add(path)
316+
self.add(vectors)
323317
self.add(circles)
324318
self.add(drawn_path)
325319
self.wait(self.run_time)
@@ -343,7 +337,7 @@ def get_path(self):
343337

344338
class FourierOfName(FourierOfPiSymbol):
345339
CONFIG = {
346-
"n_circles": 100,
340+
"n_vectors": 100,
347341
"name_color": WHITE,
348342
"name_text": "Abc",
349343
"time_per_symbol": 5,
@@ -388,14 +382,14 @@ def construct(self):
388382

389383
class FourierOfPiSymbol5(FourierOfPiSymbol):
390384
CONFIG = {
391-
"n_circles": 5,
385+
"n_vectors": 5,
392386
"run_time": 10,
393387
}
394388

395389

396390
class FourierOfTrebleClef(FourierOfPiSymbol):
397391
CONFIG = {
398-
"n_circles": 100,
392+
"n_vectors": 101,
399393
"run_time": 10,
400394
"start_drawn": True,
401395
"file_name": "TrebleClef",
@@ -419,7 +413,7 @@ class FourierOfIP(FourierOfTrebleClef):
419413
CONFIG = {
420414
"file_name": "IP_logo2",
421415
"height": 6,
422-
"n_circles": 100,
416+
"n_vectors": 100,
423417
}
424418

425419
# def construct(self):
@@ -451,7 +445,7 @@ class FourierOfEighthNote(FourierOfTrebleClef):
451445
class FourierOfN(FourierOfTrebleClef):
452446
CONFIG = {
453447
"height": 6,
454-
"n_circles": 1000,
448+
"n_vectors": 1000,
455449
}
456450

457451
def get_shape(self):
@@ -461,7 +455,7 @@ def get_shape(self):
461455
class FourierNailAndGear(FourierOfTrebleClef):
462456
CONFIG = {
463457
"height": 6,
464-
"n_circles": 200,
458+
"n_vectors": 200,
465459
"run_time": 100,
466460
"slow_factor": 0.01,
467461
"parametric_function_step_size": 0.0001,
@@ -479,7 +473,7 @@ def get_shape(self):
479473
class FourierBatman(FourierOfTrebleClef):
480474
CONFIG = {
481475
"height": 4,
482-
"n_circles": 100,
476+
"n_vectors": 100,
483477
"run_time": 10,
484478
"arrow_config": {
485479
"tip_length": 0.1,
@@ -495,7 +489,7 @@ def get_shape(self):
495489
class FourierHeart(FourierOfTrebleClef):
496490
CONFIG = {
497491
"height": 4,
498-
"n_circles": 100,
492+
"n_vectors": 100,
499493
"run_time": 10,
500494
"arrow_config": {
501495
"tip_length": 0.1,
@@ -517,7 +511,7 @@ def get_drawn_path(self, *args, **kwargs):
517511
class FourierNDQ(FourierOfTrebleClef):
518512
CONFIG = {
519513
"height": 4,
520-
"n_circles": 1000,
514+
"n_vectors": 1000,
521515
"run_time": 10,
522516
"arrow_config": {
523517
"tip_length": 0.1,
@@ -535,7 +529,7 @@ def get_shape(self):
535529

536530
class FourierGoogleG(FourierOfTrebleClef):
537531
CONFIG = {
538-
"n_circles": 10,
532+
"n_vectors": 10,
539533
"height": 5,
540534
"g_colors": [
541535
"#4285F4",
@@ -570,7 +564,7 @@ def get_drawn_path(self, *args, **kwargs):
570564

571565
class ExplainCircleAnimations(FourierCirclesScene):
572566
CONFIG = {
573-
"n_circles": 100,
567+
"n_vectors": 100,
574568
"center_point": 2 * DOWN,
575569
"n_top_circles": 9,
576570
"path_height": 3,

0 commit comments

Comments
 (0)