mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 13:17:44 +00:00
Move all contents of once_useful_constructs out of this repo
And into 3b1b/videos
This commit is contained in:
parent
e4aebaf791
commit
9ad370a04b
9 changed files with 0 additions and 2205 deletions
|
|
@ -1 +0,0 @@
|
|||
This folder contains a collection of various things that were built for a video at some point, but were really one-off and should be given more careful consideration before being brought into the main library. In particular, there is really no guarantee of these being fully functional.
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.animation.animation import Animation
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class RearrangeEquation(Scene):
|
||||
def construct(
|
||||
self,
|
||||
start_terms,
|
||||
end_terms,
|
||||
index_map,
|
||||
path_arc=np.pi,
|
||||
start_transform=None,
|
||||
end_transform=None,
|
||||
leave_start_terms=False,
|
||||
transform_kwargs={},
|
||||
):
|
||||
transform_kwargs["path_func"] = path
|
||||
start_mobs, end_mobs = self.get_mobs_from_terms(
|
||||
start_terms, end_terms
|
||||
)
|
||||
if start_transform:
|
||||
start_mobs = start_transform(Mobject(*start_mobs)).split()
|
||||
if end_transform:
|
||||
end_mobs = end_transform(Mobject(*end_mobs)).split()
|
||||
unmatched_start_indices = set(range(len(start_mobs)))
|
||||
unmatched_end_indices = set(range(len(end_mobs)))
|
||||
unmatched_start_indices.difference_update(
|
||||
[n % len(start_mobs) for n in index_map]
|
||||
)
|
||||
unmatched_end_indices.difference_update(
|
||||
[n % len(end_mobs) for n in list(index_map.values())]
|
||||
)
|
||||
mobject_pairs = [
|
||||
(start_mobs[a], end_mobs[b])
|
||||
for a, b in index_map.items()
|
||||
] + [
|
||||
(Point(end_mobs[b].get_center()), end_mobs[b])
|
||||
for b in unmatched_end_indices
|
||||
]
|
||||
if not leave_start_terms:
|
||||
mobject_pairs += [
|
||||
(start_mobs[a], Point(start_mobs[a].get_center()))
|
||||
for a in unmatched_start_indices
|
||||
]
|
||||
|
||||
self.add(*start_mobs)
|
||||
if leave_start_terms:
|
||||
self.add(Mobject(*start_mobs))
|
||||
self.wait()
|
||||
self.play(*[
|
||||
Transform(*pair, **transform_kwargs)
|
||||
for pair in mobject_pairs
|
||||
])
|
||||
self.wait()
|
||||
|
||||
def get_mobs_from_terms(self, start_terms, end_terms):
|
||||
"""
|
||||
Need to ensure that all image mobjects for a tex expression
|
||||
stemming from the same string are point-for-point copies of one
|
||||
and other. This makes transitions much smoother, and not look
|
||||
like point-clouds.
|
||||
"""
|
||||
num_start_terms = len(start_terms)
|
||||
all_mobs = np.array(
|
||||
Tex(start_terms).split() + Tex(end_terms).split())
|
||||
all_terms = np.array(start_terms + end_terms)
|
||||
for term in set(all_terms):
|
||||
matches = all_terms == term
|
||||
if sum(matches) > 1:
|
||||
base_mob = all_mobs[list(all_terms).index(term)]
|
||||
all_mobs[matches] = [
|
||||
base_mob.copy().replace(target_mob)
|
||||
for target_mob in all_mobs[matches]
|
||||
]
|
||||
return all_mobs[:num_start_terms], all_mobs[num_start_terms:]
|
||||
|
||||
|
||||
class FlipThroughSymbols(Animation):
|
||||
CONFIG = {
|
||||
"start_center": ORIGIN,
|
||||
"end_center": ORIGIN,
|
||||
}
|
||||
|
||||
def __init__(self, tex_list, **kwargs):
|
||||
mobject = Tex(self.curr_tex).shift(start_center)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def interpolate_mobject(self, alpha):
|
||||
new_tex = self.tex_list[np.ceil(alpha * len(self.tex_list)) - 1]
|
||||
|
||||
if new_tex != self.curr_tex:
|
||||
self.curr_tex = new_tex
|
||||
self.mobject = Tex(new_tex).shift(self.start_center)
|
||||
if not all(self.start_center == self.end_center):
|
||||
self.mobject.center().shift(
|
||||
(1 - alpha) * self.start_center + alpha * self.end_center
|
||||
)
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.numbers import Integer
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject, VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.simple_functions import choose
|
||||
|
||||
|
||||
DEFAULT_COUNT_NUM_OFFSET = (FRAME_X_RADIUS - 1, FRAME_Y_RADIUS - 1, 0)
|
||||
DEFAULT_COUNT_RUN_TIME = 5.0
|
||||
|
||||
|
||||
class CountingScene(Scene):
|
||||
def count(self, items, item_type="mobject", *args, **kwargs):
|
||||
if item_type == "mobject":
|
||||
self.count_mobjects(items, *args, **kwargs)
|
||||
elif item_type == "region":
|
||||
self.count_regions(items, *args, **kwargs)
|
||||
else:
|
||||
raise Exception("Unknown item_type, should be mobject or region")
|
||||
return self
|
||||
|
||||
def count_mobjects(
|
||||
self, mobjects, mode="highlight",
|
||||
color="red",
|
||||
display_numbers=True,
|
||||
num_offset=DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time=DEFAULT_COUNT_RUN_TIME,
|
||||
):
|
||||
"""
|
||||
Note, leaves final number mobject as "number" attribute
|
||||
|
||||
mode can be "highlight", "show_creation" or "show", otherwise
|
||||
a warning is given and nothing is animating during the count
|
||||
"""
|
||||
if len(mobjects) > 50: # TODO
|
||||
raise Exception("I don't know if you should be counting \
|
||||
too many mobjects...")
|
||||
if len(mobjects) == 0:
|
||||
raise Exception("Counting mobject list of length 0")
|
||||
if mode not in ["highlight", "show_creation", "show"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / len(mobjects)
|
||||
if mode == "highlight":
|
||||
self.add(*mobjects)
|
||||
for mob, num in zip(mobjects, it.count(1)):
|
||||
if display_numbers:
|
||||
num_mob = Tex(str(num))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
if mode == "highlight":
|
||||
original_color = mob.color
|
||||
mob.set_color(color)
|
||||
self.wait(frame_time)
|
||||
mob.set_color(original_color)
|
||||
if mode == "show_creation":
|
||||
self.play(ShowCreation(mob, run_time=frame_time))
|
||||
if mode == "show":
|
||||
self.add(mob)
|
||||
self.wait(frame_time)
|
||||
if display_numbers:
|
||||
self.remove(num_mob)
|
||||
if display_numbers:
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
return self
|
||||
|
||||
def count_regions(self, regions,
|
||||
mode="one_at_a_time",
|
||||
num_offset=DEFAULT_COUNT_NUM_OFFSET,
|
||||
run_time=DEFAULT_COUNT_RUN_TIME,
|
||||
**unused_kwargsn):
|
||||
if mode not in ["one_at_a_time", "show_all"]:
|
||||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / (len(regions))
|
||||
for region, count in zip(regions, it.count(1)):
|
||||
num_mob = Tex(str(count))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
self.set_color_region(region)
|
||||
self.wait(frame_time)
|
||||
if mode == "one_at_a_time":
|
||||
self.reset_background()
|
||||
self.remove(num_mob)
|
||||
self.add(num_mob)
|
||||
self.number = num_mob
|
||||
return self
|
||||
|
||||
|
||||
def combinationMobject(n, k):
|
||||
return Integer(choose(n, k))
|
||||
|
||||
|
||||
class GeneralizedPascalsTriangle(VMobject):
|
||||
CONFIG = {
|
||||
"nrows": 7,
|
||||
"height": FRAME_HEIGHT - 1,
|
||||
"width": 1.5 * FRAME_X_RADIUS,
|
||||
"portion_to_fill": 0.7,
|
||||
"submob_class": combinationMobject,
|
||||
}
|
||||
|
||||
def init_points(self):
|
||||
self.cell_height = float(self.height) / self.nrows
|
||||
self.cell_width = float(self.width) / self.nrows
|
||||
self.bottom_left = (self.cell_width * self.nrows / 2.0) * LEFT + \
|
||||
(self.cell_height * self.nrows / 2.0) * DOWN
|
||||
self.coords_to_mobs = {}
|
||||
self.coords = [
|
||||
(n, k)
|
||||
for n in range(self.nrows)
|
||||
for k in range(n + 1)
|
||||
]
|
||||
for n, k in self.coords:
|
||||
center = self.coords_to_center(n, k)
|
||||
num_mob = self.submob_class(n, k) # Tex(str(num))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / num_mob.get_height(),
|
||||
self.portion_to_fill * self.cell_width / num_mob.get_width(),
|
||||
)
|
||||
num_mob.center().scale(scale_factor).shift(center)
|
||||
if n not in self.coords_to_mobs:
|
||||
self.coords_to_mobs[n] = {}
|
||||
self.coords_to_mobs[n][k] = num_mob
|
||||
self.add(*[
|
||||
self.coords_to_mobs[n][k]
|
||||
for n, k in self.coords
|
||||
])
|
||||
return self
|
||||
|
||||
def coords_to_center(self, n, k):
|
||||
x_offset = self.cell_width * (k + self.nrows / 2.0 - n / 2.0)
|
||||
y_offset = self.cell_height * (self.nrows - n)
|
||||
return self.bottom_left + x_offset * RIGHT + y_offset * UP
|
||||
|
||||
def generate_n_choose_k_mobs(self):
|
||||
self.coords_to_n_choose_k = {}
|
||||
for n, k in self.coords:
|
||||
nck_mob = Tex(r"{%d \choose %d}" % (n, k))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / nck_mob.get_height(),
|
||||
self.portion_to_fill * self.cell_width / nck_mob.get_width(),
|
||||
)
|
||||
center = self.coords_to_mobs[n][k].get_center()
|
||||
nck_mob.center().scale(scale_factor).shift(center)
|
||||
if n not in self.coords_to_n_choose_k:
|
||||
self.coords_to_n_choose_k[n] = {}
|
||||
self.coords_to_n_choose_k[n][k] = nck_mob
|
||||
return self
|
||||
|
||||
def fill_with_n_choose_k(self):
|
||||
if not hasattr(self, "coords_to_n_choose_k"):
|
||||
self.generate_n_choose_k_mobs()
|
||||
self.set_submobjects([])
|
||||
self.add(*[
|
||||
self.coords_to_n_choose_k[n][k]
|
||||
for n, k in self.coords
|
||||
])
|
||||
return self
|
||||
|
||||
def generate_sea_of_zeros(self):
|
||||
zero = Tex("0")
|
||||
self.sea_of_zeros = []
|
||||
for n in range(self.nrows):
|
||||
for a in range((self.nrows - n) / 2 + 1):
|
||||
for k in (n + a + 1, -a - 1):
|
||||
self.coords.append((n, k))
|
||||
mob = zero.copy()
|
||||
mob.shift(self.coords_to_center(n, k))
|
||||
self.coords_to_mobs[n][k] = mob
|
||||
self.add(mob)
|
||||
return self
|
||||
|
||||
def get_lowest_row(self):
|
||||
n = self.nrows - 1
|
||||
lowest_row = VGroup(*[
|
||||
self.coords_to_mobs[n][k]
|
||||
for k in range(n + 1)
|
||||
])
|
||||
return lowest_row
|
||||
|
||||
|
||||
class PascalsTriangle(GeneralizedPascalsTriangle):
|
||||
CONFIG = {
|
||||
"submob_class": combinationMobject,
|
||||
}
|
||||
|
|
@ -1,677 +0,0 @@
|
|||
from functools import reduce
|
||||
import random
|
||||
|
||||
from manimlib.constants import *
|
||||
# from manimlib.for_3b1b_videos.pi_creature import PiCreature
|
||||
# from manimlib.for_3b1b_videos.pi_creature import Randolph
|
||||
# from manimlib.for_3b1b_videos.pi_creature import get_all_pi_creature_modes
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Polygon
|
||||
from manimlib.mobject.geometry import RegularPolygon
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.color import color_gradient
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.space_ops import center_of_mass
|
||||
from manimlib.utils.space_ops import compass_directions
|
||||
from manimlib.utils.space_ops import rotate_vector
|
||||
from manimlib.utils.space_ops import rotation_matrix
|
||||
|
||||
|
||||
def rotate(points, angle=np.pi, axis=OUT):
|
||||
if axis is None:
|
||||
return points
|
||||
matrix = rotation_matrix(angle, axis)
|
||||
points = np.dot(points, np.transpose(matrix))
|
||||
return points
|
||||
|
||||
|
||||
def fractalify(vmobject, order=3, *args, **kwargs):
|
||||
for x in range(order):
|
||||
fractalification_iteration(vmobject)
|
||||
return vmobject
|
||||
|
||||
|
||||
def fractalification_iteration(vmobject, dimension=1.05, num_inserted_anchors_range=list(range(1, 4))):
|
||||
num_points = vmobject.get_num_points()
|
||||
if num_points > 0:
|
||||
# original_anchors = vmobject.get_anchors()
|
||||
original_anchors = [
|
||||
vmobject.point_from_proportion(x)
|
||||
for x in np.linspace(0, 1 - 1. / num_points, num_points)
|
||||
]
|
||||
new_anchors = []
|
||||
for p1, p2, in zip(original_anchors, original_anchors[1:]):
|
||||
num_inserts = random.choice(num_inserted_anchors_range)
|
||||
inserted_points = [
|
||||
interpolate(p1, p2, alpha)
|
||||
for alpha in np.linspace(0, 1, num_inserts + 2)[1:-1]
|
||||
]
|
||||
mass_scaling_factor = 1. / (num_inserts + 1)
|
||||
length_scaling_factor = mass_scaling_factor**(1. / dimension)
|
||||
target_length = get_norm(p1 - p2) * length_scaling_factor
|
||||
curr_length = get_norm(p1 - p2) * mass_scaling_factor
|
||||
# offset^2 + curr_length^2 = target_length^2
|
||||
offset_len = np.sqrt(target_length**2 - curr_length**2)
|
||||
unit_vect = (p1 - p2) / get_norm(p1 - p2)
|
||||
offset_unit_vect = rotate_vector(unit_vect, np.pi / 2)
|
||||
inserted_points = [
|
||||
point + u * offset_len * offset_unit_vect
|
||||
for u, point in zip(it.cycle([-1, 1]), inserted_points)
|
||||
]
|
||||
new_anchors += [p1] + inserted_points
|
||||
new_anchors.append(original_anchors[-1])
|
||||
vmobject.set_points_as_corners(new_anchors)
|
||||
vmobject.set_submobjects([
|
||||
fractalification_iteration(
|
||||
submob, dimension, num_inserted_anchors_range)
|
||||
for submob in vmobject.submobjects
|
||||
])
|
||||
return vmobject
|
||||
|
||||
|
||||
class SelfSimilarFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order": 5,
|
||||
"num_subparts": 3,
|
||||
"height": 4,
|
||||
"colors": [RED, WHITE],
|
||||
"stroke_width": 1,
|
||||
"fill_opacity": 1,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
|
||||
def init_points(self):
|
||||
order_n_self = self.get_order_n_self(self.order)
|
||||
if self.order == 0:
|
||||
self.set_submobjects([order_n_self])
|
||||
else:
|
||||
self.set_submobjects(order_n_self.submobjects)
|
||||
return self
|
||||
|
||||
def get_order_n_self(self, order):
|
||||
if order == 0:
|
||||
result = self.get_seed_shape()
|
||||
else:
|
||||
lower_order = self.get_order_n_self(order - 1)
|
||||
subparts = [
|
||||
lower_order.copy()
|
||||
for x in range(self.num_subparts)
|
||||
]
|
||||
self.arrange_subparts(*subparts)
|
||||
result = VGroup(*subparts)
|
||||
|
||||
result.set_height(self.height)
|
||||
result.center()
|
||||
return result
|
||||
|
||||
def get_seed_shape(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class Sierpinski(SelfSimilarFractal):
|
||||
def get_seed_shape(self):
|
||||
return Polygon(
|
||||
RIGHT, np.sqrt(3) * UP, LEFT,
|
||||
)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
tri1, tri2, tri3 = subparts
|
||||
tri1.move_to(tri2.get_corner(DOWN + LEFT), UP)
|
||||
tri3.move_to(tri2.get_corner(DOWN + RIGHT), UP)
|
||||
|
||||
|
||||
class DiamondFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 4,
|
||||
"height": 4,
|
||||
"colors": [GREEN_E, YELLOW],
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=4)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
# VGroup(*subparts).rotate(np.pi/4)
|
||||
for part, vect in zip(subparts, compass_directions(start_vect=UP + RIGHT)):
|
||||
part.next_to(ORIGIN, vect, buff=0)
|
||||
VGroup(*subparts).rotate(np.pi / 4, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 5,
|
||||
"colors": [MAROON_B, YELLOW, RED],
|
||||
"height": 6,
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=5, start_angle=np.pi / 2)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for x, part in enumerate(subparts):
|
||||
part.shift(0.95 * part.get_height() * UP)
|
||||
part.rotate(2 * np.pi * x / 5, about_point=ORIGIN)
|
||||
|
||||
|
||||
class PentagonalPiCreatureFractal(PentagonalFractal):
|
||||
def init_colors(self):
|
||||
SelfSimilarFractal.init_colors(self)
|
||||
internal_pis = [
|
||||
pi
|
||||
for pi in self.get_family()
|
||||
if isinstance(pi, PiCreature)
|
||||
]
|
||||
colors = color_gradient(self.colors, len(internal_pis))
|
||||
for pi, color in zip(internal_pis, colors):
|
||||
pi.init_colors()
|
||||
pi.body.set_stroke(color, width=0.5)
|
||||
pi.set_color(color)
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Randolph(mode="shruggie")
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for part in subparts:
|
||||
part.rotate(2 * np.pi / 5, about_point=ORIGIN)
|
||||
PentagonalFractal.arrange_subparts(self, *subparts)
|
||||
|
||||
|
||||
class PiCreatureFractal(VMobject):
|
||||
CONFIG = {
|
||||
"order": 7,
|
||||
"scale_val": 2.5,
|
||||
"start_mode": "hooray",
|
||||
"height": 6,
|
||||
"colors": [
|
||||
BLUE_D, BLUE_B, MAROON_B, MAROON_D, GREY,
|
||||
YELLOW, RED, GREY_BROWN, RED, RED_E,
|
||||
],
|
||||
"random_seed": 0,
|
||||
"stroke_width": 0,
|
||||
}
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
internal_pis = [
|
||||
pi
|
||||
for pi in self.get_family()
|
||||
if isinstance(pi, PiCreature)
|
||||
]
|
||||
random.seed(self.random_seed)
|
||||
for pi in reversed(internal_pis):
|
||||
color = random.choice(self.colors)
|
||||
pi.set_color(color)
|
||||
pi.set_stroke(color, width=0)
|
||||
|
||||
def init_points(self):
|
||||
random.seed(self.random_seed)
|
||||
modes = get_all_pi_creature_modes()
|
||||
seed = PiCreature(mode=self.start_mode)
|
||||
seed.set_height(self.height)
|
||||
seed.to_edge(DOWN)
|
||||
creatures = [seed]
|
||||
self.add(VGroup(seed))
|
||||
for x in range(self.order):
|
||||
new_creatures = []
|
||||
for creature in creatures:
|
||||
for eye, vect in zip(creature.eyes, [LEFT, RIGHT]):
|
||||
new_creature = PiCreature(
|
||||
mode=random.choice(modes)
|
||||
)
|
||||
new_creature.set_height(
|
||||
self.scale_val * eye.get_height()
|
||||
)
|
||||
new_creature.next_to(
|
||||
eye, vect,
|
||||
buff=0,
|
||||
aligned_edge=DOWN
|
||||
)
|
||||
new_creatures.append(new_creature)
|
||||
creature.look_at(random.choice(new_creatures))
|
||||
self.add_to_back(VGroup(*new_creatures))
|
||||
creatures = new_creatures
|
||||
|
||||
# def init_colors(self):
|
||||
# VMobject.init_colors(self)
|
||||
# self.set_color_by_gradient(*self.colors)
|
||||
|
||||
|
||||
class WonkyHexagonFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 7
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return RegularPolygon(n=6)
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
for i, piece in enumerate(subparts):
|
||||
piece.rotate(i * np.pi / 12, about_point=ORIGIN)
|
||||
p1, p2, p3, p4, p5, p6, p7 = subparts
|
||||
center_row = VGroup(p1, p4, p7)
|
||||
center_row.arrange(RIGHT, buff=0)
|
||||
for p in p2, p3, p5, p6:
|
||||
p.set_width(p1.get_width())
|
||||
p2.move_to(p1.get_top(), DOWN + LEFT)
|
||||
p3.move_to(p1.get_bottom(), UP + LEFT)
|
||||
p5.move_to(p4.get_top(), DOWN + LEFT)
|
||||
p6.move_to(p4.get_bottom(), UP + LEFT)
|
||||
|
||||
|
||||
class CircularFractal(SelfSimilarFractal):
|
||||
CONFIG = {
|
||||
"num_subparts": 3,
|
||||
"colors": [GREEN, BLUE, GREY]
|
||||
}
|
||||
|
||||
def get_seed_shape(self):
|
||||
return Circle()
|
||||
|
||||
def arrange_subparts(self, *subparts):
|
||||
if not hasattr(self, "been_here"):
|
||||
self.num_subparts = 3 + self.order
|
||||
self.been_here = True
|
||||
for i, part in enumerate(subparts):
|
||||
theta = np.pi / self.num_subparts
|
||||
part.next_to(
|
||||
ORIGIN, UP,
|
||||
buff=self.height / (2 * np.tan(theta))
|
||||
)
|
||||
part.rotate(i * 2 * np.pi / self.num_subparts, about_point=ORIGIN)
|
||||
self.num_subparts -= 1
|
||||
|
||||
######## Space filling curves ############
|
||||
|
||||
|
||||
class JaggedCurvePiece(VMobject):
|
||||
def insert_n_curves(self, n):
|
||||
if self.get_num_curves() == 0:
|
||||
self.set_points(np.zeros((1, 3)))
|
||||
anchors = self.get_anchors()
|
||||
indices = np.linspace(
|
||||
0, len(anchors) - 1, n + len(anchors)
|
||||
).astype('int')
|
||||
self.set_points_as_corners(anchors[indices])
|
||||
|
||||
|
||||
class FractalCurve(VMobject):
|
||||
CONFIG = {
|
||||
"radius": 3,
|
||||
"order": 5,
|
||||
"colors": [RED, GREEN],
|
||||
"num_submobjects": 20,
|
||||
"monochromatic": False,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
4: 2,
|
||||
5: 1,
|
||||
},
|
||||
}
|
||||
|
||||
def init_points(self):
|
||||
points = self.get_anchor_points()
|
||||
self.set_points_as_corners(points)
|
||||
if not self.monochromatic:
|
||||
alphas = np.linspace(0, 1, self.num_submobjects)
|
||||
for alpha_pair in zip(alphas, alphas[1:]):
|
||||
submobject = JaggedCurvePiece()
|
||||
submobject.pointwise_become_partial(
|
||||
self, *alpha_pair
|
||||
)
|
||||
self.add(submobject)
|
||||
self.set_points(np.zeros((0, 3)))
|
||||
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
self.set_color_by_gradient(*self.colors)
|
||||
for order in sorted(self.order_to_stroke_width_map.keys()):
|
||||
if self.order >= order:
|
||||
self.set_stroke(width=self.order_to_stroke_width_map[order])
|
||||
|
||||
def get_anchor_points(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class LindenmayerCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"axiom": "A",
|
||||
"rule": {},
|
||||
"scale_factor": 2,
|
||||
"radius": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2,
|
||||
}
|
||||
|
||||
def expand_command_string(self, command):
|
||||
result = ""
|
||||
for letter in command:
|
||||
if letter in self.rule:
|
||||
result += self.rule[letter]
|
||||
else:
|
||||
result += letter
|
||||
return result
|
||||
|
||||
def get_command_string(self):
|
||||
result = self.axiom
|
||||
for x in range(self.order):
|
||||
result = self.expand_command_string(result)
|
||||
return result
|
||||
|
||||
def get_anchor_points(self):
|
||||
step = float(self.radius) * self.start_step
|
||||
step /= (self.scale_factor**self.order)
|
||||
curr = np.zeros(3)
|
||||
result = [curr]
|
||||
for letter in self.get_command_string():
|
||||
if letter == "+":
|
||||
step = rotate(step, self.angle)
|
||||
elif letter == "-":
|
||||
step = rotate(step, -self.angle)
|
||||
else:
|
||||
curr = curr + step
|
||||
result.append(curr)
|
||||
return np.array(result) - center_of_mass(result)
|
||||
|
||||
|
||||
class SelfSimilarSpaceFillingCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"offsets": [],
|
||||
# keys must awkwardly be in string form...
|
||||
"offset_to_rotation_axis": {},
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 0.5,
|
||||
}
|
||||
|
||||
def transform(self, points, offset):
|
||||
"""
|
||||
How to transform the copy of points shifted by
|
||||
offset. Generally meant to be extended in subclasses
|
||||
"""
|
||||
copy = np.array(points)
|
||||
if str(offset) in self.offset_to_rotation_axis:
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis[str(offset)]
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
def refine_into_subparts(self, points):
|
||||
transformed_copies = [
|
||||
self.transform(points, offset)
|
||||
for offset in self.offsets
|
||||
]
|
||||
return reduce(
|
||||
lambda a, b: np.append(a, b, axis=0),
|
||||
transformed_copies
|
||||
)
|
||||
|
||||
def get_anchor_points(self):
|
||||
points = np.zeros((1, 3))
|
||||
for count in range(self.order):
|
||||
points = self.refine_into_subparts(points)
|
||||
return points
|
||||
|
||||
def generate_grid(self):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
class HilbertCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT + UP,
|
||||
RIGHT + UP,
|
||||
RIGHT + DOWN,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT + DOWN): RIGHT + UP,
|
||||
str(RIGHT + DOWN): RIGHT + DOWN,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"offsets": [
|
||||
RIGHT + DOWN + IN,
|
||||
LEFT + DOWN + IN,
|
||||
LEFT + DOWN + OUT,
|
||||
RIGHT + DOWN + OUT,
|
||||
RIGHT + UP + OUT,
|
||||
LEFT + UP + OUT,
|
||||
LEFT + UP + IN,
|
||||
RIGHT + UP + IN,
|
||||
],
|
||||
"offset_to_rotation_axis_and_angle": {
|
||||
str(RIGHT + DOWN + IN): (LEFT + UP + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + IN): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(LEFT + DOWN + OUT): (RIGHT + DOWN + IN, 2 * np.pi / 3),
|
||||
str(RIGHT + DOWN + OUT): (UP, np.pi),
|
||||
str(RIGHT + UP + OUT): (UP, np.pi),
|
||||
str(LEFT + UP + OUT): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(LEFT + UP + IN): (LEFT + DOWN + OUT, 2 * np.pi / 3),
|
||||
str(RIGHT + UP + IN): (RIGHT + UP + IN, 2 * np.pi / 3),
|
||||
},
|
||||
}
|
||||
# Rewrote transform method to include the rotation angle
|
||||
|
||||
def transform(self, points, offset):
|
||||
copy = np.array(points)
|
||||
copy = rotate(
|
||||
copy,
|
||||
axis=self.offset_to_rotation_axis_and_angle[str(offset)][0],
|
||||
angle=self.offset_to_rotation_axis_and_angle[str(offset)][1],
|
||||
)
|
||||
copy /= self.scale_factor,
|
||||
copy += offset * self.radius * self.radius_scale_factor
|
||||
return copy
|
||||
|
||||
|
||||
class PeanoCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [PURPLE, TEAL],
|
||||
"offsets": [
|
||||
LEFT + DOWN,
|
||||
LEFT,
|
||||
LEFT + UP,
|
||||
UP,
|
||||
ORIGIN,
|
||||
DOWN,
|
||||
RIGHT + DOWN,
|
||||
RIGHT,
|
||||
RIGHT + UP,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(LEFT): UP,
|
||||
str(UP): RIGHT,
|
||||
str(ORIGIN): LEFT + UP,
|
||||
str(DOWN): RIGHT,
|
||||
str(RIGHT): UP,
|
||||
},
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2.0 / 3,
|
||||
}
|
||||
|
||||
|
||||
class TriangleFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [MAROON, YELLOW],
|
||||
"offsets": [
|
||||
LEFT / 4. + DOWN / 6.,
|
||||
ORIGIN,
|
||||
RIGHT / 4. + DOWN / 6.,
|
||||
UP / 3.,
|
||||
],
|
||||
"offset_to_rotation_axis": {
|
||||
str(ORIGIN): RIGHT,
|
||||
str(UP / 3.): UP,
|
||||
},
|
||||
"scale_factor": 2,
|
||||
"radius_scale_factor": 1.5,
|
||||
}
|
||||
|
||||
# class HexagonFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
# CONFIG = {
|
||||
# "start_color" : WHITE,
|
||||
# "end_color" : BLUE_D,
|
||||
# "axis_offset_pairs" : [
|
||||
# (None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT),
|
||||
# (UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT),
|
||||
# (np.sqrt(3)*UP+RIGHT, ORIGIN),
|
||||
# ((UP, RIGHT), np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*LEFT),
|
||||
# (None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT),
|
||||
# (RIGHT, np.sqrt(3)*RIGHT),
|
||||
# ],
|
||||
# "scale_factor" : 3,
|
||||
# "radius_scale_factor" : 2/(3*np.sqrt(3)),
|
||||
# }
|
||||
|
||||
# def refine_into_subparts(self, points):
|
||||
# return SelfSimilarSpaceFillingCurve.refine_into_subparts(
|
||||
# self,
|
||||
# rotate(points, np.pi/6, IN)
|
||||
# )
|
||||
|
||||
|
||||
class UtahFillingCurve(SelfSimilarSpaceFillingCurve):
|
||||
CONFIG = {
|
||||
"colors": [WHITE, BLUE_D],
|
||||
"axis_offset_pairs": [
|
||||
|
||||
],
|
||||
"scale_factor": 3,
|
||||
"radius_scale_factor": 2 / (3 * np.sqrt(3)),
|
||||
}
|
||||
|
||||
|
||||
class FlowSnake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [YELLOW, GREEN],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A-B--B+A++AA+B-",
|
||||
"B": "+A-BB--B-A++A+B",
|
||||
},
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": np.sqrt(7),
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
self.rotate(-self.order * np.pi / 9, about_point=ORIGIN)
|
||||
|
||||
|
||||
class SierpinskiCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [RED, WHITE],
|
||||
"axiom": "B",
|
||||
"rule": {
|
||||
"A": "+B-A-B+",
|
||||
"B": "-A+B+A-",
|
||||
},
|
||||
"radius": 6, # TODO, this is innaccurate
|
||||
"scale_factor": 2,
|
||||
"start_step": RIGHT,
|
||||
"angle": -np.pi / 3,
|
||||
}
|
||||
|
||||
|
||||
class KochSnowFlake(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [BLUE_D, WHITE, BLUE_D],
|
||||
"axiom": "A--A--A--",
|
||||
"rule": {
|
||||
"A": "A+A--A+A"
|
||||
},
|
||||
"radius": 4,
|
||||
"scale_factor": 3,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 3,
|
||||
"order_to_stroke_width_map": {
|
||||
3: 3,
|
||||
5: 2,
|
||||
6: 1,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.scale_factor = 2 * (1 + np.cos(self.angle))
|
||||
LindenmayerCurve.__init__(self, **kwargs)
|
||||
|
||||
|
||||
class KochCurve(KochSnowFlake):
|
||||
CONFIG = {
|
||||
"axiom": "A--"
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKoch(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"colors": [YELLOW, WHITE, MAROON_B],
|
||||
"axiom": "A",
|
||||
"rule": {
|
||||
"A": "A+A-A-AA+A+A-A"
|
||||
},
|
||||
"radius": 4,
|
||||
"scale_factor": 4,
|
||||
"start_step": RIGHT,
|
||||
"angle": np.pi / 2
|
||||
}
|
||||
|
||||
|
||||
class QuadraticKochIsland(QuadraticKoch):
|
||||
CONFIG = {
|
||||
"axiom": "A+A+A+A"
|
||||
}
|
||||
|
||||
|
||||
class StellarCurve(LindenmayerCurve):
|
||||
CONFIG = {
|
||||
"start_color": RED,
|
||||
"end_color": BLUE_E,
|
||||
"rule": {
|
||||
"A": "+B-A-B+A-B+",
|
||||
"B": "-A+B+A-B+A-",
|
||||
},
|
||||
"scale_factor": 3,
|
||||
"angle": 2 * np.pi / 5,
|
||||
}
|
||||
|
||||
|
||||
class SnakeCurve(FractalCurve):
|
||||
CONFIG = {
|
||||
"start_color": BLUE,
|
||||
"end_color": YELLOW,
|
||||
}
|
||||
|
||||
def get_anchor_points(self):
|
||||
result = []
|
||||
resolution = 2**self.order
|
||||
step = 2.0 * self.radius / resolution
|
||||
lower_left = ORIGIN + \
|
||||
LEFT * (self.radius - step / 2) + \
|
||||
DOWN * (self.radius - step / 2)
|
||||
|
||||
for y in range(resolution):
|
||||
x_range = list(range(resolution))
|
||||
if y % 2 == 0:
|
||||
x_range.reverse()
|
||||
for x in x_range:
|
||||
result.append(
|
||||
lower_left + x * step * RIGHT + y * step * UP
|
||||
)
|
||||
return result
|
||||
|
|
@ -1,566 +0,0 @@
|
|||
import itertools as it
|
||||
|
||||
from manimlib.animation.creation import Write, DrawBorderThenFill, ShowCreation
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.animation.update import UpdateFromAlphaFunc
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.functions import ParametricCurve
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.geometry import Rectangle
|
||||
from manimlib.mobject.geometry import RegularPolygon
|
||||
from manimlib.mobject.number_line import NumberLine
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.color import color_gradient
|
||||
from manimlib.utils.color import invert_color
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
|
||||
# TODO, this class should be deprecated, with all its
|
||||
# functionality moved to Axes and handled at the mobject
|
||||
# level rather than the scene level
|
||||
|
||||
|
||||
class GraphScene(Scene):
|
||||
CONFIG = {
|
||||
"x_min": -1,
|
||||
"x_max": 10,
|
||||
"x_axis_width": 9,
|
||||
"x_tick_frequency": 1,
|
||||
"x_leftmost_tick": None, # Change if different from x_min
|
||||
"x_labeled_nums": None,
|
||||
"x_axis_label": "$x$",
|
||||
"y_min": -1,
|
||||
"y_max": 10,
|
||||
"y_axis_height": 6,
|
||||
"y_tick_frequency": 1,
|
||||
"y_bottom_tick": None, # Change if different from y_min
|
||||
"y_labeled_nums": None,
|
||||
"y_axis_label": "$y$",
|
||||
"axes_color": GREY,
|
||||
"graph_origin": 2.5 * DOWN + 4 * LEFT,
|
||||
"exclude_zero_label": True,
|
||||
"default_graph_colors": [BLUE, GREEN, YELLOW],
|
||||
"default_derivative_color": GREEN,
|
||||
"default_input_color": YELLOW,
|
||||
"default_riemann_start_color": BLUE,
|
||||
"default_riemann_end_color": GREEN,
|
||||
"area_opacity": 0.8,
|
||||
"num_rects": 50,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
self.default_graph_colors_cycle = it.cycle(self.default_graph_colors)
|
||||
|
||||
self.left_T_label = VGroup()
|
||||
self.left_v_line = VGroup()
|
||||
self.right_T_label = VGroup()
|
||||
self.right_v_line = VGroup()
|
||||
|
||||
def setup_axes(self, animate=False):
|
||||
# TODO, once eoc is done, refactor this to be less redundant.
|
||||
x_num_range = float(self.x_max - self.x_min)
|
||||
self.space_unit_to_x = self.x_axis_width / x_num_range
|
||||
if self.x_labeled_nums is None:
|
||||
self.x_labeled_nums = []
|
||||
if self.x_leftmost_tick is None:
|
||||
self.x_leftmost_tick = self.x_min
|
||||
x_axis = NumberLine(
|
||||
x_min=self.x_min,
|
||||
x_max=self.x_max,
|
||||
unit_size=self.space_unit_to_x,
|
||||
tick_frequency=self.x_tick_frequency,
|
||||
leftmost_tick=self.x_leftmost_tick,
|
||||
numbers_with_elongated_ticks=self.x_labeled_nums,
|
||||
color=self.axes_color
|
||||
)
|
||||
x_axis.shift(self.graph_origin - x_axis.number_to_point(0))
|
||||
if len(self.x_labeled_nums) > 0:
|
||||
if self.exclude_zero_label:
|
||||
self.x_labeled_nums = [x for x in self.x_labeled_nums if x != 0]
|
||||
x_axis.add_numbers(self.x_labeled_nums)
|
||||
if self.x_axis_label:
|
||||
x_label = TexText(self.x_axis_label)
|
||||
x_label.next_to(
|
||||
x_axis.get_tick_marks(), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
)
|
||||
x_label.shift_onto_screen()
|
||||
x_axis.add(x_label)
|
||||
self.x_axis_label_mob = x_label
|
||||
|
||||
y_num_range = float(self.y_max - self.y_min)
|
||||
self.space_unit_to_y = self.y_axis_height / y_num_range
|
||||
|
||||
if self.y_labeled_nums is None:
|
||||
self.y_labeled_nums = []
|
||||
if self.y_bottom_tick is None:
|
||||
self.y_bottom_tick = self.y_min
|
||||
y_axis = NumberLine(
|
||||
x_min=self.y_min,
|
||||
x_max=self.y_max,
|
||||
unit_size=self.space_unit_to_y,
|
||||
tick_frequency=self.y_tick_frequency,
|
||||
leftmost_tick=self.y_bottom_tick,
|
||||
numbers_with_elongated_ticks=self.y_labeled_nums,
|
||||
color=self.axes_color,
|
||||
line_to_number_vect=LEFT,
|
||||
label_direction=LEFT,
|
||||
)
|
||||
y_axis.shift(self.graph_origin - y_axis.number_to_point(0))
|
||||
y_axis.rotate(np.pi / 2, about_point=y_axis.number_to_point(0))
|
||||
if len(self.y_labeled_nums) > 0:
|
||||
if self.exclude_zero_label:
|
||||
self.y_labeled_nums = [y for y in self.y_labeled_nums if y != 0]
|
||||
y_axis.add_numbers(self.y_labeled_nums)
|
||||
if self.y_axis_label:
|
||||
y_label = TexText(self.y_axis_label)
|
||||
y_label.next_to(
|
||||
y_axis.get_corner(UP + RIGHT), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
)
|
||||
y_label.shift_onto_screen()
|
||||
y_axis.add(y_label)
|
||||
self.y_axis_label_mob = y_label
|
||||
|
||||
if animate:
|
||||
self.play(Write(VGroup(x_axis, y_axis)))
|
||||
else:
|
||||
self.add(x_axis, y_axis)
|
||||
self.x_axis, self.y_axis = self.axes = VGroup(x_axis, y_axis)
|
||||
self.default_graph_colors = it.cycle(self.default_graph_colors)
|
||||
|
||||
def coords_to_point(self, x, y):
|
||||
assert(hasattr(self, "x_axis") and hasattr(self, "y_axis"))
|
||||
result = self.x_axis.number_to_point(x)[0] * RIGHT
|
||||
result += self.y_axis.number_to_point(y)[1] * UP
|
||||
return result
|
||||
|
||||
def point_to_coords(self, point):
|
||||
return (self.x_axis.point_to_number(point),
|
||||
self.y_axis.point_to_number(point))
|
||||
|
||||
def get_graph(
|
||||
self, func,
|
||||
color=None,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
**kwargs
|
||||
):
|
||||
if color is None:
|
||||
color = next(self.default_graph_colors_cycle)
|
||||
if x_min is None:
|
||||
x_min = self.x_min
|
||||
if x_max is None:
|
||||
x_max = self.x_max
|
||||
|
||||
def parameterized_function(alpha):
|
||||
x = interpolate(x_min, x_max, alpha)
|
||||
y = func(x)
|
||||
if not np.isfinite(y):
|
||||
y = self.y_max
|
||||
return self.coords_to_point(x, y)
|
||||
|
||||
graph = ParametricCurve(
|
||||
parameterized_function,
|
||||
color=color,
|
||||
**kwargs
|
||||
)
|
||||
graph.underlying_function = func
|
||||
return graph
|
||||
|
||||
def input_to_graph_point(self, x, graph):
|
||||
return self.coords_to_point(x, graph.underlying_function(x))
|
||||
|
||||
def angle_of_tangent(self, x, graph, dx=0.01):
|
||||
vect = self.input_to_graph_point(
|
||||
x + dx, graph) - self.input_to_graph_point(x, graph)
|
||||
return angle_of_vector(vect)
|
||||
|
||||
def slope_of_tangent(self, *args, **kwargs):
|
||||
return np.tan(self.angle_of_tangent(*args, **kwargs))
|
||||
|
||||
def get_derivative_graph(self, graph, dx=0.01, **kwargs):
|
||||
if "color" not in kwargs:
|
||||
kwargs["color"] = self.default_derivative_color
|
||||
|
||||
def deriv(x):
|
||||
return self.slope_of_tangent(x, graph, dx) / self.space_unit_to_y
|
||||
return self.get_graph(deriv, **kwargs)
|
||||
|
||||
def get_graph_label(
|
||||
self,
|
||||
graph,
|
||||
label="f(x)",
|
||||
x_val=None,
|
||||
direction=RIGHT,
|
||||
buff=MED_SMALL_BUFF,
|
||||
color=None,
|
||||
):
|
||||
label = Tex(label)
|
||||
color = color or graph.get_color()
|
||||
label.set_color(color)
|
||||
if x_val is None:
|
||||
# Search from right to left
|
||||
for x in np.linspace(self.x_max, self.x_min, 100):
|
||||
point = self.input_to_graph_point(x, graph)
|
||||
if point[1] < FRAME_Y_RADIUS:
|
||||
break
|
||||
x_val = x
|
||||
label.next_to(
|
||||
self.input_to_graph_point(x_val, graph),
|
||||
direction,
|
||||
buff=buff
|
||||
)
|
||||
label.shift_onto_screen()
|
||||
return label
|
||||
|
||||
def get_riemann_rectangles(
|
||||
self,
|
||||
graph,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
dx=0.1,
|
||||
input_sample_type="left",
|
||||
stroke_width=1,
|
||||
stroke_color=BLACK,
|
||||
fill_opacity=1,
|
||||
start_color=None,
|
||||
end_color=None,
|
||||
show_signed_area=True,
|
||||
width_scale_factor=1.001
|
||||
):
|
||||
x_min = x_min if x_min is not None else self.x_min
|
||||
x_max = x_max if x_max is not None else self.x_max
|
||||
if start_color is None:
|
||||
start_color = self.default_riemann_start_color
|
||||
if end_color is None:
|
||||
end_color = self.default_riemann_end_color
|
||||
rectangles = VGroup()
|
||||
x_range = np.arange(x_min, x_max, dx)
|
||||
colors = color_gradient([start_color, end_color], len(x_range))
|
||||
for x, color in zip(x_range, colors):
|
||||
if input_sample_type == "left":
|
||||
sample_input = x
|
||||
elif input_sample_type == "right":
|
||||
sample_input = x + dx
|
||||
elif input_sample_type == "center":
|
||||
sample_input = x + 0.5 * dx
|
||||
else:
|
||||
raise Exception("Invalid input sample type")
|
||||
graph_point = self.input_to_graph_point(sample_input, graph)
|
||||
points = VGroup(*list(map(VectorizedPoint, [
|
||||
self.coords_to_point(x, 0),
|
||||
self.coords_to_point(x + width_scale_factor * dx, 0),
|
||||
graph_point
|
||||
])))
|
||||
|
||||
rect = Rectangle()
|
||||
rect.replace(points, stretch=True)
|
||||
if graph_point[1] < self.graph_origin[1] and show_signed_area:
|
||||
fill_color = invert_color(color)
|
||||
else:
|
||||
fill_color = color
|
||||
rect.set_fill(fill_color, opacity=fill_opacity)
|
||||
rect.set_stroke(stroke_color, width=stroke_width)
|
||||
rectangles.add(rect)
|
||||
return rectangles
|
||||
|
||||
def get_riemann_rectangles_list(
|
||||
self,
|
||||
graph,
|
||||
n_iterations,
|
||||
max_dx=0.5,
|
||||
power_base=2,
|
||||
stroke_width=1,
|
||||
**kwargs
|
||||
):
|
||||
return [
|
||||
self.get_riemann_rectangles(
|
||||
graph=graph,
|
||||
dx=float(max_dx) / (power_base**n),
|
||||
stroke_width=float(stroke_width) / (power_base**n),
|
||||
**kwargs
|
||||
)
|
||||
for n in range(n_iterations)
|
||||
]
|
||||
|
||||
def get_area(self, graph, t_min, t_max):
|
||||
numerator = max(t_max - t_min, 0.0001)
|
||||
dx = float(numerator) / self.num_rects
|
||||
return self.get_riemann_rectangles(
|
||||
graph,
|
||||
x_min=t_min,
|
||||
x_max=t_max,
|
||||
dx=dx,
|
||||
stroke_width=0,
|
||||
).set_fill(opacity=self.area_opacity)
|
||||
|
||||
def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs):
|
||||
transform_kwargs = {
|
||||
"run_time": 2,
|
||||
"lag_ratio": 0.5
|
||||
}
|
||||
added_anims = kwargs.get("added_anims", [])
|
||||
transform_kwargs.update(kwargs)
|
||||
curr_rects.align_family(new_rects)
|
||||
x_coords = set() # Keep track of new repetitions
|
||||
for rect in curr_rects:
|
||||
x = rect.get_center()[0]
|
||||
if x in x_coords:
|
||||
rect.set_fill(opacity=0)
|
||||
else:
|
||||
x_coords.add(x)
|
||||
self.play(
|
||||
Transform(curr_rects, new_rects, **transform_kwargs),
|
||||
*added_anims
|
||||
)
|
||||
|
||||
def get_vertical_line_to_graph(
|
||||
self,
|
||||
x, graph,
|
||||
line_class=Line,
|
||||
**line_kwargs
|
||||
):
|
||||
if "color" not in line_kwargs:
|
||||
line_kwargs["color"] = graph.get_color()
|
||||
return line_class(
|
||||
self.coords_to_point(x, 0),
|
||||
self.input_to_graph_point(x, graph),
|
||||
**line_kwargs
|
||||
)
|
||||
|
||||
def get_vertical_lines_to_graph(
|
||||
self, graph,
|
||||
x_min=None,
|
||||
x_max=None,
|
||||
num_lines=20,
|
||||
**kwargs
|
||||
):
|
||||
x_min = x_min or self.x_min
|
||||
x_max = x_max or self.x_max
|
||||
return VGroup(*[
|
||||
self.get_vertical_line_to_graph(x, graph, **kwargs)
|
||||
for x in np.linspace(x_min, x_max, num_lines)
|
||||
])
|
||||
|
||||
def get_secant_slope_group(
|
||||
self,
|
||||
x, graph,
|
||||
dx=None,
|
||||
dx_line_color=None,
|
||||
df_line_color=None,
|
||||
dx_label=None,
|
||||
df_label=None,
|
||||
include_secant_line=True,
|
||||
secant_line_color=None,
|
||||
secant_line_length=10,
|
||||
):
|
||||
"""
|
||||
Resulting group is of the form VGroup(
|
||||
dx_line,
|
||||
df_line,
|
||||
dx_label, (if applicable)
|
||||
df_label, (if applicable)
|
||||
secant_line, (if applicable)
|
||||
)
|
||||
with attributes of those names.
|
||||
"""
|
||||
kwargs = locals()
|
||||
kwargs.pop("self")
|
||||
group = VGroup()
|
||||
group.kwargs = kwargs
|
||||
|
||||
dx = dx or float(self.x_max - self.x_min) / 10
|
||||
dx_line_color = dx_line_color or self.default_input_color
|
||||
df_line_color = df_line_color or graph.get_color()
|
||||
|
||||
p1 = self.input_to_graph_point(x, graph)
|
||||
p2 = self.input_to_graph_point(x + dx, graph)
|
||||
interim_point = p2[0] * RIGHT + p1[1] * UP
|
||||
|
||||
group.dx_line = Line(
|
||||
p1, interim_point,
|
||||
color=dx_line_color
|
||||
)
|
||||
group.df_line = Line(
|
||||
interim_point, p2,
|
||||
color=df_line_color
|
||||
)
|
||||
group.add(group.dx_line, group.df_line)
|
||||
|
||||
labels = VGroup()
|
||||
if dx_label is not None:
|
||||
group.dx_label = Tex(dx_label)
|
||||
labels.add(group.dx_label)
|
||||
group.add(group.dx_label)
|
||||
if df_label is not None:
|
||||
group.df_label = Tex(df_label)
|
||||
labels.add(group.df_label)
|
||||
group.add(group.df_label)
|
||||
|
||||
if len(labels) > 0:
|
||||
max_width = 0.8 * group.dx_line.get_width()
|
||||
max_height = 0.8 * group.df_line.get_height()
|
||||
if labels.get_width() > max_width:
|
||||
labels.set_width(max_width)
|
||||
if labels.get_height() > max_height:
|
||||
labels.set_height(max_height)
|
||||
|
||||
if dx_label is not None:
|
||||
group.dx_label.next_to(
|
||||
group.dx_line,
|
||||
np.sign(dx) * DOWN,
|
||||
buff=group.dx_label.get_height() / 2
|
||||
)
|
||||
group.dx_label.set_color(group.dx_line.get_color())
|
||||
|
||||
if df_label is not None:
|
||||
group.df_label.next_to(
|
||||
group.df_line,
|
||||
np.sign(dx) * RIGHT,
|
||||
buff=group.df_label.get_height() / 2
|
||||
)
|
||||
group.df_label.set_color(group.df_line.get_color())
|
||||
|
||||
if include_secant_line:
|
||||
secant_line_color = secant_line_color or self.default_derivative_color
|
||||
group.secant_line = Line(p1, p2, color=secant_line_color)
|
||||
group.secant_line.scale(
|
||||
secant_line_length / group.secant_line.get_length()
|
||||
)
|
||||
group.add(group.secant_line)
|
||||
|
||||
return group
|
||||
|
||||
def add_T_label(self, x_val, side=RIGHT, label=None, color=WHITE, animated=False, **kwargs):
|
||||
triangle = RegularPolygon(n=3, start_angle=np.pi / 2)
|
||||
triangle.set_height(MED_SMALL_BUFF)
|
||||
triangle.move_to(self.coords_to_point(x_val, 0), UP)
|
||||
triangle.set_fill(color, 1)
|
||||
triangle.set_stroke(width=0)
|
||||
if label is None:
|
||||
T_label = Tex(self.variable_point_label, fill_color=color)
|
||||
else:
|
||||
T_label = Tex(label, fill_color=color)
|
||||
|
||||
T_label.next_to(triangle, DOWN)
|
||||
v_line = self.get_vertical_line_to_graph(
|
||||
x_val, self.v_graph,
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
if animated:
|
||||
self.play(
|
||||
DrawBorderThenFill(triangle),
|
||||
ShowCreation(v_line),
|
||||
Write(T_label, run_time=1),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if np.all(side == LEFT):
|
||||
self.left_T_label_group = VGroup(T_label, triangle)
|
||||
self.left_v_line = v_line
|
||||
self.add(self.left_T_label_group, self.left_v_line)
|
||||
elif np.all(side == RIGHT):
|
||||
self.right_T_label_group = VGroup(T_label, triangle)
|
||||
self.right_v_line = v_line
|
||||
self.add(self.right_T_label_group, self.right_v_line)
|
||||
|
||||
def get_animation_integral_bounds_change(
|
||||
self,
|
||||
graph,
|
||||
new_t_min,
|
||||
new_t_max,
|
||||
fade_close_to_origin=True,
|
||||
run_time=1.0
|
||||
):
|
||||
curr_t_min = self.x_axis.point_to_number(self.area.get_left())
|
||||
curr_t_max = self.x_axis.point_to_number(self.area.get_right())
|
||||
if new_t_min is None:
|
||||
new_t_min = curr_t_min
|
||||
if new_t_max is None:
|
||||
new_t_max = curr_t_max
|
||||
|
||||
group = VGroup(self.area)
|
||||
group.add(self.left_v_line)
|
||||
group.add(self.left_T_label_group)
|
||||
group.add(self.right_v_line)
|
||||
group.add(self.right_T_label_group)
|
||||
|
||||
def update_group(group, alpha):
|
||||
area, left_v_line, left_T_label, right_v_line, right_T_label = group
|
||||
t_min = interpolate(curr_t_min, new_t_min, alpha)
|
||||
t_max = interpolate(curr_t_max, new_t_max, alpha)
|
||||
new_area = self.get_area(graph, t_min, t_max)
|
||||
|
||||
new_left_v_line = self.get_vertical_line_to_graph(
|
||||
t_min, graph
|
||||
)
|
||||
new_left_v_line.set_color(left_v_line.get_color())
|
||||
left_T_label.move_to(new_left_v_line.get_bottom(), UP)
|
||||
|
||||
new_right_v_line = self.get_vertical_line_to_graph(
|
||||
t_max, graph
|
||||
)
|
||||
new_right_v_line.set_color(right_v_line.get_color())
|
||||
right_T_label.move_to(new_right_v_line.get_bottom(), UP)
|
||||
|
||||
# Fade close to 0
|
||||
if fade_close_to_origin:
|
||||
if len(left_T_label) > 0:
|
||||
left_T_label[0].set_fill(opacity=min(1, np.abs(t_min)))
|
||||
if len(right_T_label) > 0:
|
||||
right_T_label[0].set_fill(opacity=min(1, np.abs(t_max)))
|
||||
|
||||
Transform(area, new_area).update(1)
|
||||
Transform(left_v_line, new_left_v_line).update(1)
|
||||
Transform(right_v_line, new_right_v_line).update(1)
|
||||
return group
|
||||
|
||||
return UpdateFromAlphaFunc(group, update_group, run_time=run_time)
|
||||
|
||||
def animate_secant_slope_group_change(
|
||||
self, secant_slope_group,
|
||||
target_dx=None,
|
||||
target_x=None,
|
||||
run_time=3,
|
||||
added_anims=None,
|
||||
**anim_kwargs
|
||||
):
|
||||
if target_dx is None and target_x is None:
|
||||
raise Exception(
|
||||
"At least one of target_x and target_dx must not be None")
|
||||
if added_anims is None:
|
||||
added_anims = []
|
||||
|
||||
start_dx = secant_slope_group.kwargs["dx"]
|
||||
start_x = secant_slope_group.kwargs["x"]
|
||||
if target_dx is None:
|
||||
target_dx = start_dx
|
||||
if target_x is None:
|
||||
target_x = start_x
|
||||
|
||||
def update_func(group, alpha):
|
||||
dx = interpolate(start_dx, target_dx, alpha)
|
||||
x = interpolate(start_x, target_x, alpha)
|
||||
kwargs = dict(secant_slope_group.kwargs)
|
||||
kwargs["dx"] = dx
|
||||
kwargs["x"] = x
|
||||
new_group = self.get_secant_slope_group(**kwargs)
|
||||
group.become(new_group)
|
||||
return group
|
||||
|
||||
self.play(
|
||||
UpdateFromAlphaFunc(
|
||||
secant_slope_group, update_func,
|
||||
run_time=run_time,
|
||||
**anim_kwargs
|
||||
),
|
||||
*added_anims
|
||||
)
|
||||
secant_slope_group.kwargs["x"] = target_x
|
||||
secant_slope_group.kwargs["dx"] = target_dx
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.animation.creation import ShowCreation
|
||||
from manimlib.animation.fading import FadeOut
|
||||
from manimlib.animation.transform import ApplyMethod
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.matrix import Matrix
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class NumericalMatrixMultiplication(Scene):
|
||||
CONFIG = {
|
||||
"left_matrix": [[1, 2], [3, 4]],
|
||||
"right_matrix": [[5, 6], [7, 8]],
|
||||
"use_parens": True,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
left_string_matrix, right_string_matrix = [
|
||||
np.array(matrix).astype("string")
|
||||
for matrix in (self.left_matrix, self.right_matrix)
|
||||
]
|
||||
if right_string_matrix.shape[0] != left_string_matrix.shape[1]:
|
||||
raise Exception("Incompatible shapes for matrix multiplication")
|
||||
|
||||
left = Matrix(left_string_matrix)
|
||||
right = Matrix(right_string_matrix)
|
||||
result = self.get_result_matrix(
|
||||
left_string_matrix, right_string_matrix
|
||||
)
|
||||
|
||||
self.organize_matrices(left, right, result)
|
||||
self.animate_product(left, right, result)
|
||||
|
||||
def get_result_matrix(self, left, right):
|
||||
(m, k), n = left.shape, right.shape[1]
|
||||
mob_matrix = np.array([VGroup()]).repeat(m * n).reshape((m, n))
|
||||
for a in range(m):
|
||||
for b in range(n):
|
||||
template = "(%s)(%s)" if self.use_parens else "%s%s"
|
||||
parts = [
|
||||
prefix + template % (left[a][c], right[c][b])
|
||||
for c in range(k)
|
||||
for prefix in ["" if c == 0 else "+"]
|
||||
]
|
||||
mob_matrix[a][b] = Tex(parts, next_to_buff=0.1)
|
||||
return Matrix(mob_matrix)
|
||||
|
||||
def add_lines(self, left, right):
|
||||
line_kwargs = {
|
||||
"color": BLUE,
|
||||
"stroke_width": 2,
|
||||
}
|
||||
left_rows = [
|
||||
VGroup(*row) for row in left.get_mob_matrix()
|
||||
]
|
||||
h_lines = VGroup()
|
||||
for row in left_rows[:-1]:
|
||||
h_line = Line(row.get_left(), row.get_right(), **line_kwargs)
|
||||
h_line.next_to(row, DOWN, buff=left.v_buff / 2.)
|
||||
h_lines.add(h_line)
|
||||
|
||||
right_cols = [
|
||||
VGroup(*col) for col in np.transpose(right.get_mob_matrix())
|
||||
]
|
||||
v_lines = VGroup()
|
||||
for col in right_cols[:-1]:
|
||||
v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs)
|
||||
v_line.next_to(col, RIGHT, buff=right.h_buff / 2.)
|
||||
v_lines.add(v_line)
|
||||
|
||||
self.play(ShowCreation(h_lines))
|
||||
self.play(ShowCreation(v_lines))
|
||||
self.wait()
|
||||
self.show_frame()
|
||||
|
||||
def organize_matrices(self, left, right, result):
|
||||
equals = Tex("=")
|
||||
everything = VGroup(left, right, equals, result)
|
||||
everything.arrange()
|
||||
everything.set_width(FRAME_WIDTH - 1)
|
||||
self.add(everything)
|
||||
|
||||
def animate_product(self, left, right, result):
|
||||
l_matrix = left.get_mob_matrix()
|
||||
r_matrix = right.get_mob_matrix()
|
||||
result_matrix = result.get_mob_matrix()
|
||||
circle = Circle(
|
||||
radius=l_matrix[0][0].get_height(),
|
||||
color=GREEN
|
||||
)
|
||||
circles = VGroup(*[
|
||||
entry.get_point_mobject()
|
||||
for entry in (l_matrix[0][0], r_matrix[0][0])
|
||||
])
|
||||
(m, k), n = l_matrix.shape, r_matrix.shape[1]
|
||||
for mob in result_matrix.flatten():
|
||||
mob.set_color(BLACK)
|
||||
lagging_anims = []
|
||||
for a in range(m):
|
||||
for b in range(n):
|
||||
for c in range(k):
|
||||
l_matrix[a][c].set_color(YELLOW)
|
||||
r_matrix[c][b].set_color(YELLOW)
|
||||
for c in range(k):
|
||||
start_parts = VGroup(
|
||||
l_matrix[a][c].copy(),
|
||||
r_matrix[c][b].copy()
|
||||
)
|
||||
result_entry = result_matrix[a][b].split()[c]
|
||||
|
||||
new_circles = VGroup(*[
|
||||
circle.copy().shift(part.get_center())
|
||||
for part in start_parts.split()
|
||||
])
|
||||
self.play(Transform(circles, new_circles))
|
||||
self.play(
|
||||
Transform(
|
||||
start_parts,
|
||||
result_entry.copy().set_color(YELLOW),
|
||||
path_arc=-np.pi / 2,
|
||||
lag_ratio=0,
|
||||
),
|
||||
*lagging_anims
|
||||
)
|
||||
result_entry.set_color(YELLOW)
|
||||
self.remove(start_parts)
|
||||
lagging_anims = [
|
||||
ApplyMethod(result_entry.set_color, WHITE)
|
||||
]
|
||||
|
||||
for c in range(k):
|
||||
l_matrix[a][c].set_color(WHITE)
|
||||
r_matrix[c][b].set_color(WHITE)
|
||||
self.play(FadeOut(circles), *lagging_anims)
|
||||
self.wait()
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
camera_config = dict(samples=4)
|
||||
|
||||
def begin_ambient_camera_rotation(self, rate=0.02):
|
||||
pass # TODO
|
||||
|
||||
def stop_ambient_camera_rotation(self):
|
||||
pass # TODO
|
||||
|
||||
def move_camera(self,
|
||||
phi=None,
|
||||
theta=None,
|
||||
distance=None,
|
||||
gamma=None,
|
||||
frame_center=None,
|
||||
**kwargs):
|
||||
pass # TODO
|
||||
|
|
@ -1,511 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from manimlib.animation.animation import Animation
|
||||
from manimlib.animation.creation import ShowCreation
|
||||
from manimlib.animation.creation import Write
|
||||
from manimlib.animation.fading import FadeOut
|
||||
from manimlib.animation.growing import GrowArrow
|
||||
from manimlib.animation.transform import ApplyFunction
|
||||
from manimlib.animation.transform import ApplyPointwiseFunction
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.constants import BLACK, BLUE_D, GREEN_C, RED_C, GREY, WHITE, YELLOW
|
||||
from manimlib.constants import DL, DOWN, ORIGIN, RIGHT, UP
|
||||
from manimlib.constants import FRAME_WIDTH, FRAME_X_RADIUS, FRAME_Y_RADIUS
|
||||
from manimlib.constants import SMALL_BUFF
|
||||
from manimlib.mobject.coordinate_systems import Axes
|
||||
from manimlib.mobject.coordinate_systems import NumberPlane
|
||||
from manimlib.mobject.geometry import Arrow
|
||||
from manimlib.mobject.geometry import Dot
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.geometry import Rectangle
|
||||
from manimlib.mobject.geometry import Vector
|
||||
from manimlib.mobject.matrix import Matrix
|
||||
from manimlib.mobject.matrix import VECTOR_LABEL_SCALE_FACTOR
|
||||
from manimlib.mobject.matrix import vector_coordinate_label
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.rate_functions import rush_from
|
||||
from manimlib.utils.rate_functions import rush_into
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from manimlib.constants import ManimColor
|
||||
from typing import List
|
||||
|
||||
X_COLOR = GREEN_C
|
||||
Y_COLOR = RED_C
|
||||
Z_COLOR = BLUE_D
|
||||
|
||||
# TODO: Much of this scene type seems dependent on the coordinate system chosen.
|
||||
# That is, being centered at the origin with grid units corresponding to the
|
||||
# arbitrary space units. Change it!
|
||||
#
|
||||
# Also, methods I would have thought of as getters, like coords_to_vector, are
|
||||
# actually doing a lot of animating.
|
||||
class VectorScene(Scene):
|
||||
basis_vector_stroke_width: int = 6
|
||||
|
||||
def add_plane(self, animate=False, **kwargs):
|
||||
plane = NumberPlane(**kwargs)
|
||||
if animate:
|
||||
self.play(ShowCreation(plane, lag_ratio=0.5))
|
||||
self.add(plane)
|
||||
return plane
|
||||
|
||||
def add_axes(self, animate=False, color=WHITE, **kwargs):
|
||||
axes = Axes(color=color, tick_frequency=1)
|
||||
if animate:
|
||||
self.play(ShowCreation(axes))
|
||||
self.add(axes)
|
||||
return axes
|
||||
|
||||
def lock_in_faded_grid(self, dimness=0.7, axes_dimness=0.5):
|
||||
plane = self.add_plane()
|
||||
axes = plane.get_axes()
|
||||
plane.fade(dimness)
|
||||
axes.set_color(WHITE)
|
||||
axes.fade(axes_dimness)
|
||||
self.add(axes)
|
||||
self.freeze_background()
|
||||
|
||||
def get_vector(self, numerical_vector, **kwargs):
|
||||
return Arrow(
|
||||
self.plane.coords_to_point(0, 0),
|
||||
self.plane.coords_to_point(*numerical_vector[:2]),
|
||||
buff=0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def add_vector(self, vector, color=YELLOW, animate=True, **kwargs):
|
||||
if not isinstance(vector, Arrow):
|
||||
vector = Vector(vector, color=color, **kwargs)
|
||||
if animate:
|
||||
self.play(GrowArrow(vector))
|
||||
self.add(vector)
|
||||
return vector
|
||||
|
||||
def write_vector_coordinates(self, vector, **kwargs):
|
||||
coords = vector_coordinate_label(vector, **kwargs)
|
||||
self.play(Write(coords))
|
||||
return coords
|
||||
|
||||
def get_basis_vectors(self, i_hat_color=X_COLOR, j_hat_color=Y_COLOR):
|
||||
return VGroup(*[
|
||||
Vector(
|
||||
vect,
|
||||
color=color,
|
||||
stroke_width=self.basis_vector_stroke_width
|
||||
)
|
||||
for vect, color in [
|
||||
([1, 0], i_hat_color),
|
||||
([0, 1], j_hat_color)
|
||||
]
|
||||
])
|
||||
|
||||
def get_basis_vector_labels(self, **kwargs):
|
||||
i_hat, j_hat = self.get_basis_vectors()
|
||||
return VGroup(*[
|
||||
self.get_vector_label(
|
||||
vect, label, color=color,
|
||||
label_scale_factor=1,
|
||||
**kwargs
|
||||
)
|
||||
for vect, label, color in [
|
||||
(i_hat, "\\hat{\\imath}", X_COLOR),
|
||||
(j_hat, "\\hat{\\jmath}", Y_COLOR),
|
||||
]
|
||||
])
|
||||
|
||||
def get_vector_label(self, vector, label,
|
||||
at_tip=False,
|
||||
direction="left",
|
||||
rotate=False,
|
||||
color=None,
|
||||
label_scale_factor=VECTOR_LABEL_SCALE_FACTOR):
|
||||
if not isinstance(label, Tex):
|
||||
if len(label) == 1:
|
||||
label = "\\vec{\\textbf{%s}}" % label
|
||||
label = Tex(label)
|
||||
if color is None:
|
||||
color = vector.get_color()
|
||||
label.set_color(color)
|
||||
label.scale(label_scale_factor)
|
||||
label.add_background_rectangle()
|
||||
|
||||
if at_tip:
|
||||
vect = vector.get_vector()
|
||||
vect /= get_norm(vect)
|
||||
label.next_to(vector.get_end(), vect, buff=SMALL_BUFF)
|
||||
else:
|
||||
angle = vector.get_angle()
|
||||
if not rotate:
|
||||
label.rotate(-angle, about_point=ORIGIN)
|
||||
if direction == "left":
|
||||
label.shift(-label.get_bottom() + 0.1 * UP)
|
||||
else:
|
||||
label.shift(-label.get_top() + 0.1 * DOWN)
|
||||
label.rotate(angle, about_point=ORIGIN)
|
||||
label.shift((vector.get_end() - vector.get_start()) / 2)
|
||||
return label
|
||||
|
||||
def label_vector(self, vector, label, animate=True, **kwargs):
|
||||
label = self.get_vector_label(vector, label, **kwargs)
|
||||
if animate:
|
||||
self.play(Write(label, run_time=1))
|
||||
self.add(label)
|
||||
return label
|
||||
|
||||
def position_x_coordinate(self, x_coord, x_line, vector):
|
||||
x_coord.next_to(x_line, -np.sign(vector[1]) * UP)
|
||||
x_coord.set_color(X_COLOR)
|
||||
return x_coord
|
||||
|
||||
def position_y_coordinate(self, y_coord, y_line, vector):
|
||||
y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT)
|
||||
y_coord.set_color(Y_COLOR)
|
||||
return y_coord
|
||||
|
||||
def coords_to_vector(self, vector, coords_start=2 * RIGHT + 2 * UP, clean_up=True):
|
||||
starting_mobjects = list(self.mobjects)
|
||||
array = Matrix(vector)
|
||||
array.shift(coords_start)
|
||||
arrow = Vector(vector)
|
||||
x_line = Line(ORIGIN, vector[0] * RIGHT)
|
||||
y_line = Line(x_line.get_end(), arrow.get_end())
|
||||
x_line.set_color(X_COLOR)
|
||||
y_line.set_color(Y_COLOR)
|
||||
x_coord, y_coord = array.get_mob_matrix().flatten()
|
||||
|
||||
self.play(Write(array, run_time=1))
|
||||
self.wait()
|
||||
self.play(ApplyFunction(
|
||||
lambda x: self.position_x_coordinate(x, x_line, vector),
|
||||
x_coord
|
||||
))
|
||||
self.play(ShowCreation(x_line))
|
||||
self.play(
|
||||
ApplyFunction(
|
||||
lambda y: self.position_y_coordinate(y, y_line, vector),
|
||||
y_coord
|
||||
),
|
||||
FadeOut(array.get_brackets())
|
||||
)
|
||||
y_coord, brackets = self.get_mobjects_from_last_animation()
|
||||
self.play(ShowCreation(y_line))
|
||||
self.play(ShowCreation(arrow))
|
||||
self.wait()
|
||||
if clean_up:
|
||||
self.clear()
|
||||
self.add(*starting_mobjects)
|
||||
|
||||
def vector_to_coords(self, vector, integer_labels=True, clean_up=True):
|
||||
starting_mobjects = list(self.mobjects)
|
||||
show_creation = False
|
||||
if isinstance(vector, Arrow):
|
||||
arrow = vector
|
||||
vector = arrow.get_end()[:2]
|
||||
else:
|
||||
arrow = Vector(vector)
|
||||
show_creation = True
|
||||
array = vector_coordinate_label(arrow, integer_labels=integer_labels)
|
||||
x_line = Line(ORIGIN, vector[0] * RIGHT)
|
||||
y_line = Line(x_line.get_end(), arrow.get_end())
|
||||
x_line.set_color(X_COLOR)
|
||||
y_line.set_color(Y_COLOR)
|
||||
x_coord, y_coord = array.get_mob_matrix().flatten()
|
||||
x_coord_start = self.position_x_coordinate(
|
||||
x_coord.copy(), x_line, vector
|
||||
)
|
||||
y_coord_start = self.position_y_coordinate(
|
||||
y_coord.copy(), y_line, vector
|
||||
)
|
||||
brackets = array.get_brackets()
|
||||
|
||||
if show_creation:
|
||||
self.play(ShowCreation(arrow))
|
||||
self.play(
|
||||
ShowCreation(x_line),
|
||||
Write(x_coord_start),
|
||||
run_time=1
|
||||
)
|
||||
self.play(
|
||||
ShowCreation(y_line),
|
||||
Write(y_coord_start),
|
||||
run_time=1
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
Transform(x_coord_start, x_coord, lag_ratio=0),
|
||||
Transform(y_coord_start, y_coord, lag_ratio=0),
|
||||
Write(brackets, run_time=1),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.remove(x_coord_start, y_coord_start, brackets)
|
||||
self.add(array)
|
||||
if clean_up:
|
||||
self.clear()
|
||||
self.add(*starting_mobjects)
|
||||
return array, x_line, y_line
|
||||
|
||||
def show_ghost_movement(self, vector):
|
||||
if isinstance(vector, Arrow):
|
||||
vector = vector.get_end() - vector.get_start()
|
||||
elif len(vector) == 2:
|
||||
vector = np.append(np.array(vector), 0.0)
|
||||
x_max = int(FRAME_X_RADIUS + abs(vector[0]))
|
||||
y_max = int(FRAME_Y_RADIUS + abs(vector[1]))
|
||||
dots = VMobject(*[
|
||||
Dot(x * RIGHT + y * UP)
|
||||
for x in range(-x_max, x_max)
|
||||
for y in range(-y_max, y_max)
|
||||
])
|
||||
dots.set_fill(BLACK, opacity=0)
|
||||
dots_halfway = dots.copy().shift(vector / 2).set_fill(WHITE, 1)
|
||||
dots_end = dots.copy().shift(vector)
|
||||
|
||||
self.play(Transform(
|
||||
dots, dots_halfway, rate_func=rush_into
|
||||
))
|
||||
self.play(Transform(
|
||||
dots, dots_end, rate_func=rush_from
|
||||
))
|
||||
self.remove(dots)
|
||||
|
||||
|
||||
class LinearTransformationScene(VectorScene):
|
||||
include_background_plane: bool = True
|
||||
include_foreground_plane: bool = True
|
||||
foreground_plane_kwargs: dict = dict(
|
||||
x_max=FRAME_WIDTH / 2,
|
||||
x_min=-FRAME_WIDTH / 2,
|
||||
y_max=FRAME_WIDTH / 2,
|
||||
y_min=-FRAME_WIDTH / 2,
|
||||
faded_line_ratio=0
|
||||
)
|
||||
background_plane_kwargs: dict = dict(
|
||||
color=GREY,
|
||||
axis_config=dict(color=GREY),
|
||||
background_line_style=dict(
|
||||
stroke_color=GREY,
|
||||
stroke_width=1,
|
||||
),
|
||||
)
|
||||
show_coordinates: bool = True
|
||||
show_basis_vectors: bool = True
|
||||
basis_vector_stroke_width: float = 6.0
|
||||
i_hat_color: ManimColor = X_COLOR
|
||||
j_hat_color: ManimColor = Y_COLOR
|
||||
leave_ghost_vectors: bool = False
|
||||
t_matrix: List[List[float]] = [[3, 0], [1, 2]]
|
||||
|
||||
def setup(self):
|
||||
# The has_already_setup attr is to not break all the old Scenes
|
||||
if hasattr(self, "has_already_setup"):
|
||||
return
|
||||
self.has_already_setup = True
|
||||
self.background_mobjects = []
|
||||
self.foreground_mobjects = []
|
||||
self.transformable_mobjects = []
|
||||
self.moving_vectors = []
|
||||
self.transformable_labels = []
|
||||
self.moving_mobjects = []
|
||||
|
||||
self.t_matrix = np.array(self.t_matrix)
|
||||
self.background_plane = NumberPlane(
|
||||
**self.background_plane_kwargs
|
||||
)
|
||||
|
||||
if self.show_coordinates:
|
||||
self.background_plane.add_coordinates()
|
||||
if self.include_background_plane:
|
||||
self.add_background_mobject(self.background_plane)
|
||||
if self.include_foreground_plane:
|
||||
self.plane = NumberPlane(**self.foreground_plane_kwargs)
|
||||
self.add_transformable_mobject(self.plane)
|
||||
if self.show_basis_vectors:
|
||||
self.basis_vectors = self.get_basis_vectors(
|
||||
i_hat_color=self.i_hat_color,
|
||||
j_hat_color=self.j_hat_color,
|
||||
)
|
||||
self.moving_vectors += list(self.basis_vectors)
|
||||
self.i_hat, self.j_hat = self.basis_vectors
|
||||
self.add(self.basis_vectors)
|
||||
|
||||
def add_special_mobjects(self, mob_list, *mobs_to_add):
|
||||
for mobject in mobs_to_add:
|
||||
if mobject not in mob_list:
|
||||
mob_list.append(mobject)
|
||||
self.add(mobject)
|
||||
|
||||
def add_background_mobject(self, *mobjects):
|
||||
self.add_special_mobjects(self.background_mobjects, *mobjects)
|
||||
|
||||
# TODO, this conflicts with Scene.add_fore
|
||||
def add_foreground_mobject(self, *mobjects):
|
||||
self.add_special_mobjects(self.foreground_mobjects, *mobjects)
|
||||
|
||||
def add_transformable_mobject(self, *mobjects):
|
||||
self.add_special_mobjects(self.transformable_mobjects, *mobjects)
|
||||
|
||||
def add_moving_mobject(self, mobject, target_mobject=None):
|
||||
mobject.target = target_mobject
|
||||
self.add_special_mobjects(self.moving_mobjects, mobject)
|
||||
|
||||
def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3):
|
||||
square = self.square = Rectangle(
|
||||
color=color,
|
||||
width=self.plane.get_x_unit_size(),
|
||||
height=self.plane.get_y_unit_size(),
|
||||
stroke_color=color,
|
||||
stroke_width=stroke_width,
|
||||
fill_color=color,
|
||||
fill_opacity=opacity
|
||||
)
|
||||
square.move_to(self.plane.coords_to_point(0, 0), DL)
|
||||
return square
|
||||
|
||||
def add_unit_square(self, animate=False, **kwargs):
|
||||
square = self.get_unit_square(**kwargs)
|
||||
if animate:
|
||||
self.play(
|
||||
DrawBorderThenFill(square),
|
||||
Animation(Group(*self.moving_vectors))
|
||||
)
|
||||
self.add_transformable_mobject(square)
|
||||
self.bring_to_front(*self.moving_vectors)
|
||||
self.square = square
|
||||
return self
|
||||
|
||||
def add_vector(self, vector, color=YELLOW, **kwargs):
|
||||
vector = VectorScene.add_vector(
|
||||
self, vector, color=color, **kwargs
|
||||
)
|
||||
self.moving_vectors.append(vector)
|
||||
return vector
|
||||
|
||||
def write_vector_coordinates(self, vector, **kwargs):
|
||||
coords = VectorScene.write_vector_coordinates(self, vector, **kwargs)
|
||||
self.add_foreground_mobject(coords)
|
||||
return coords
|
||||
|
||||
def add_transformable_label(
|
||||
self, vector, label,
|
||||
transformation_name="L",
|
||||
new_label=None,
|
||||
**kwargs):
|
||||
label_mob = self.label_vector(vector, label, **kwargs)
|
||||
if new_label:
|
||||
label_mob.target_text = new_label
|
||||
else:
|
||||
label_mob.target_text = "%s(%s)" % (
|
||||
transformation_name,
|
||||
label_mob.get_tex()
|
||||
)
|
||||
label_mob.vector = vector
|
||||
label_mob.kwargs = kwargs
|
||||
if "animate" in label_mob.kwargs:
|
||||
label_mob.kwargs.pop("animate")
|
||||
self.transformable_labels.append(label_mob)
|
||||
return label_mob
|
||||
|
||||
def add_title(self, title, scale_factor=1.5, animate=False):
|
||||
if not isinstance(title, Mobject):
|
||||
title = TexText(title).scale(scale_factor)
|
||||
title.to_edge(UP)
|
||||
title.add_background_rectangle()
|
||||
if animate:
|
||||
self.play(Write(title))
|
||||
self.add_foreground_mobject(title)
|
||||
self.title = title
|
||||
return self
|
||||
|
||||
def get_matrix_transformation(self, matrix):
|
||||
return self.get_transposed_matrix_transformation(np.array(matrix).T)
|
||||
|
||||
def get_transposed_matrix_transformation(self, transposed_matrix):
|
||||
transposed_matrix = np.array(transposed_matrix)
|
||||
if transposed_matrix.shape == (2, 2):
|
||||
new_matrix = np.identity(3)
|
||||
new_matrix[:2, :2] = transposed_matrix
|
||||
transposed_matrix = new_matrix
|
||||
elif transposed_matrix.shape != (3, 3):
|
||||
raise Exception("Matrix has bad dimensions")
|
||||
return lambda point: np.dot(point, transposed_matrix)
|
||||
|
||||
def get_piece_movement(self, pieces):
|
||||
start = VGroup(*pieces)
|
||||
target = VGroup(*[mob.target for mob in pieces])
|
||||
if self.leave_ghost_vectors:
|
||||
self.add(start.copy().fade(0.7))
|
||||
return Transform(start, target, lag_ratio=0)
|
||||
|
||||
def get_moving_mobject_movement(self, func):
|
||||
for m in self.moving_mobjects:
|
||||
if m.target is None:
|
||||
m.target = m.copy()
|
||||
target_point = func(m.get_center())
|
||||
m.target.move_to(target_point)
|
||||
return self.get_piece_movement(self.moving_mobjects)
|
||||
|
||||
def get_vector_movement(self, func):
|
||||
for v in self.moving_vectors:
|
||||
v.target = Vector(func(v.get_end()), color=v.get_color())
|
||||
norm = get_norm(v.target.get_end())
|
||||
if norm < 0.1:
|
||||
v.target.get_tip().scale(norm)
|
||||
return self.get_piece_movement(self.moving_vectors)
|
||||
|
||||
def get_transformable_label_movement(self):
|
||||
for l in self.transformable_labels:
|
||||
l.target = self.get_vector_label(
|
||||
l.vector.target, l.target_text, **l.kwargs
|
||||
)
|
||||
return self.get_piece_movement(self.transformable_labels)
|
||||
|
||||
def apply_matrix(self, matrix, **kwargs):
|
||||
self.apply_transposed_matrix(np.array(matrix).T, **kwargs)
|
||||
|
||||
def apply_inverse(self, matrix, **kwargs):
|
||||
self.apply_matrix(np.linalg.inv(matrix), **kwargs)
|
||||
|
||||
def apply_transposed_matrix(self, transposed_matrix, **kwargs):
|
||||
func = self.get_transposed_matrix_transformation(transposed_matrix)
|
||||
if "path_arc" not in kwargs:
|
||||
net_rotation = np.mean([
|
||||
angle_of_vector(func(RIGHT)),
|
||||
angle_of_vector(func(UP)) - np.pi / 2
|
||||
])
|
||||
kwargs["path_arc"] = net_rotation
|
||||
self.apply_function(func, **kwargs)
|
||||
|
||||
def apply_inverse_transpose(self, t_matrix, **kwargs):
|
||||
t_inv = np.linalg.inv(np.array(t_matrix).T).T
|
||||
self.apply_transposed_matrix(t_inv, **kwargs)
|
||||
|
||||
def apply_nonlinear_transformation(self, function, **kwargs):
|
||||
self.plane.prepare_for_nonlinear_transform()
|
||||
self.apply_function(function, **kwargs)
|
||||
|
||||
def apply_function(self, function, added_anims=[], **kwargs):
|
||||
if "run_time" not in kwargs:
|
||||
kwargs["run_time"] = 3
|
||||
anims = [
|
||||
ApplyPointwiseFunction(function, t_mob)
|
||||
for t_mob in self.transformable_mobjects
|
||||
] + [
|
||||
self.get_vector_movement(function),
|
||||
self.get_transformable_label_movement(),
|
||||
self.get_moving_mobject_movement(function),
|
||||
] + [
|
||||
Animation(f_mob)
|
||||
for f_mob in self.foreground_mobjects
|
||||
] + added_anims
|
||||
self.play(*anims, **kwargs)
|
||||
Loading…
Add table
Reference in a new issue