Some graph utilities implemented for Moser videos

This commit is contained in:
Grant Sanderson 2015-04-26 14:25:43 -07:00
parent 5aff0b6374
commit 8d5240e070
10 changed files with 565 additions and 50 deletions

View file

@ -11,7 +11,7 @@ from images2gif import writeGif
from helpers import *
from constants import *
from mobject import Mobject
from mobject import Mobject, Point
class Animation(object):
def __init__(self,
@ -179,6 +179,9 @@ class Transform(Animation):
run_time = DEFAULT_TRANSFORM_RUN_TIME,
*args, **kwargs):
count1, count2 = mobject1.get_num_points(), mobject2.get_num_points()
if count2 == 0:
mobject2 = Point((SPACE_WIDTH, SPACE_HEIGHT, 0))
count2 = mobject2.get_num_points()
Mobject.align_data(mobject1, mobject2)
Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs)
self.ending_mobject = mobject2
@ -189,11 +192,13 @@ class Transform(Animation):
if count2 < count1:
#Ensure redundant pixels fade to black
indices = self.non_redundant_m2_indices = \
np.arange(0, count1-1, float(count1) / count2).astype('int')
indices = np.arange(
0, count1-1, float(count1) / count2
).astype('int')
temp = np.zeros(mobject2.points.shape)
temp[indices] = mobject2.rgbs[indices]
mobject2.rgbs = temp
self.non_redundant_m2_indices = indices
def update_mobject(self, alpha):
Mobject.interpolate(
@ -222,6 +227,17 @@ class FadeToColor(Transform):
target = copy.deepcopy(mobject).highlight(color)
Transform.__init__(self, mobject, target, *args, **kwargs)
class Highlight(FadeToColor):
def __init__(self, mobject, color = "red",
run_time = DEFAULT_ANIMATION_RUN_TIME,
alpha_func = there_and_back, *args, **kwargs):
FadeToColor.__init__(
self, mobject, color,
run_time = run_time,
alpha_func = alpha_func,
*args, **kwargs
)
class ScaleInPlace(Transform):
def __init__(self, mobject, scale_factor, *args, **kwargs):
target = copy.deepcopy(mobject)

View file

@ -1,7 +1,7 @@
import os
PRODUCTION_QUALITY = True
GENERALLY_BUFF_POINTS = True
GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50

View file

@ -59,6 +59,8 @@ def make_even_by_cycling(iterable_1, iterable_2):
def sigmoid(x):
return 1.0/(1 + np.exp(-x))
### Alpha Functions ###
def high_inflection_0_to_1(t, inflection = 10.0):
error = sigmoid(-inflection / 2)
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
@ -67,6 +69,10 @@ def there_and_back(t, inflection = 10.0):
new_t = 2*t if t < 0.5 else 2*(1 - t)
return high_inflection_0_to_1(new_t, inflection)
def not_quite_there(t, proportion = 0.7):
return proportion*high_inflection_0_to_1(t)
### Functional Functions ###
def composition(func_list):
"""
@ -124,8 +130,7 @@ def z_to_vector(vector):
return np.dot(rotation_about_z(theta), phi_down)
def rotate_vector(vector, angle, axis):
#Slightly hacky, changes vector in place
vector[:3] = np.dot(rotation_matrix(angle, axis), vector)
return np.dot(rotation_matrix(angle, axis), vector)
def angle_between(v1, v2):
return np.arccos(np.dot(

View file

@ -86,7 +86,7 @@ class VideoIcon(ImageMobject):
ImageMobject.__init__(self, "video_icon", *args, **kwargs)
self.scale(0.3)
#Purely redundant function to make singulars and plurals sensible
def tex_mobject(expression, size = "\HUGE"):
return tex_mobjects(expression, size)

View file

@ -89,13 +89,14 @@ class Mobject(object):
self.shift(-self.get_center())
return self
def get_center(self):
return np.apply_along_axis(np.mean, 0, self.points)
def scale(self, scale_factor):
self.points *= scale_factor
return self
def scale_in_place(self, scale_factor):
center = self.get_center()
return self.center().scale(scale_factor).shift(center)
def add(self, *mobjects):
for mobject in mobjects:
self.add_points(mobject.points, mobject.rgbs)
@ -146,6 +147,17 @@ class Mobject(object):
self.rgbs = self.rgbs[to_eliminate]
return self
### Getters ###
def get_center(self):
return np.apply_along_axis(np.mean, 0, self.points)
def get_width(self):
return np.max(self.points[:, 0]) - np.min(self.points[:, 0])
def get_height(self):
return np.max(self.points[:, 1]) - np.min(self.points[:, 1])
### Stuff subclasses should deal with ###
def should_buffer_points(self):
# potentially changed in subclasses
return GENERALLY_BUFF_POINTS
@ -228,6 +240,7 @@ class Point(Mobject):
self.rgbs = np.array(self.color.get_rgb()).reshape(1, 3)
class Arrow(Mobject1D):
DEFAULT_COLOR = "white"
NUNGE_DISTANCE = 0.1
def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0),
length = 1, tip_length = 0.25,
@ -246,7 +259,7 @@ class Arrow(Mobject1D):
])
tips_dir = np.array(-self.direction), np.array(-self.direction)
for i, sgn in zip([0, 1], [-1, 1]):
rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
self.add_points([
[x, x, x] * tips_dir[i] + self.point
for x in np.arange(0, self.tip_length, self.epsilon)
@ -271,13 +284,13 @@ class Dot(Mobject1D): #Use 1D density, even though 2D
raise Exception("Center must have 2 or 3 coordinates!")
elif center.size == 2:
center = np.append(center, [0])
self.center = center
self.center_point = center
self.radius = radius
Mobject1D.__init__(self, *args, **kwargs)
def generate_points(self):
self.add_points([
np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center
np.array((t*np.cos(theta), t*np.sin(theta), 0)) + self.center_point
for t in np.arange(0, self.radius, self.epsilon)
for theta in np.arange(0, 2 * np.pi, self.epsilon)
])
@ -305,6 +318,18 @@ class Line(Mobject1D):
for t in np.arange(0, 1, self.epsilon)
])
class CurvedLine(Line):
def generate_points(self):
equidistant_point = rotate_vector(
self.end - self.start,
np.pi/3, [0,0,1]
) + self.start
self.add_points([
(1 - t*(1-t))*(t*self.end + (1-t)*self.start) \
+ t*(1-t)*equidistant_point
for t in np.arange(0, 1, self.epsilon)
])
self.ep = equidistant_point
class CubeWithFaces(Mobject2D):
def generate_points(self):

112
moser/graphs.py Normal file
View file

@ -0,0 +1,112 @@
import itertools as it
import numpy as np
CUBE_GRAPH = {
"name" : "CubeGraph",
# 6 4
# 20
# 31
# 7 5
"vertices" : [
(x, y, 0)
for r in (1, 2)
for x, y in it.product([-r,r], [-r, r])
],
"edges" : [
(0, 1),
(0, 2),
(3, 1),
(3, 2),
(4, 5),
(4, 6),
(7, 5),
(7, 6),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
],
"region_cycles" : [
[0, 2, 3, 1],
[4, 0, 1, 5],
[4, 6, 2, 0],
[6, 7, 3, 2],
[7, 5, 1, 3],
[4, 6, 7, 5],#By convention, last region will be "outside"
]
}
SAMPLE_GRAPH = {
"name" : "SampleGraph",
# 4 2 3
# 0 1
#
# 5
"vertices" :[
( 0, 0, 0),
( 2, 0, 0),
( 1, 1, 0),
( 3, 1, 0),
(-1, 1, 0),
(-2,-2, 0),
],
"edges" : [
(0, 1),
(1, 2),
(1, 3),
(3, 2),
(2, 4),
(4, 0),
(2, 0),
(4, 5),
(0, 5),
(1, 5),
],
"region_cycles" : [
(0, 1, 2),
(1, 3, 2),
(2, 4, 0),
(4, 5, 0),
(0, 5, 1),
(4, 5, 1, 3),
]
}
OCTOHEDRON_GRAPH = {
"name" : "OctohedronGraph",
# 3
#
# 1 0
# 2
#4 5
"vertices" : [
(r*np.cos(angle), r*np.sin(angle)-1, 0)
for r, s in [(1, 0), (3, 3)]
for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s])
],
"edges" : [
(0, 1),
(1, 2),
(2, 0),
(5, 0),
(0, 3),
(3, 5),
(3, 1),
(3, 4),
(1, 4),
(4, 2),
(4, 5),
(5, 2),
],
"region_cycles" : [
(0, 1, 2),
(0, 5, 3),
(3, 1, 0),
(3, 4, 1),
(1, 4, 2),
(2, 4, 5),
(5, 0, 2),
(3, 4, 5),
]
}

View file

@ -28,7 +28,7 @@ def logo_to_circle():
)
big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS)
sc.add(small_circle)
sc.dither()
sc.dither()`
sc.animate(Transform(small_circle, big_circle))
return sc

View file

@ -4,6 +4,7 @@ import numpy as np
import itertools as it
import operator as op
from copy import deepcopy
from random import random
from animation import *
@ -14,10 +15,14 @@ from region import *
from scene import Scene
from moser_helpers import *
from graphs import *
RADIUS = SPACE_HEIGHT - 0.1
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
movie_prefix = "moser/"
############################################
class CircleScene(Scene):
def __init__(self, radians, *args, **kwargs):
@ -32,6 +37,34 @@ class CircleScene(Scene):
self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)]
self.add(self.circle, *self.dots + self.lines)
class GraphScene(Scene):
#Note, the placement of vertices in this is pretty hard coded, be
#warned if you want to change it.
def __init__(self, graph, *args, **kwargs):
Scene.__init__(self, *args, **kwargs)
#See CUBE_GRAPH above for format of graph
self.graph = graph
self.points = map(np.array, graph["vertices"])
self.vertices = self.dots = [Dot(p) for p in self.points]
self.edges = [
Line(self.points[i], self.points[j])
for i, j in graph["edges"]
]
self.add(*self.dots + self.edges)
def generate_regions(self):
regions = [
region_from_line_boundary(*[
[
self.points[rc[i]],
self.points[rc[(i+1)%len(rc)]]
]
for i in range(len(rc))
])
for rc in self.graph["region_cycles"]
]
regions[-1].complement()#Outer region painted outwardly...
self.regions = regions
##################################################
@ -72,7 +105,7 @@ def count_lines(*radians):
else:
anims.append(FadeOut(mob))
sc.animate(*anims, run_time = 1)
return sc
sc.write_to_movie(movie_prefix + "CountLines%dPoints"%len(radians))
def count_intersection_points(*radians):
@ -84,7 +117,7 @@ def count_intersection_points(*radians):
for p in it.combinations(sc.points, 4)
]
intersection_dots = [Dot(point) for point in intersection_points]
text_center = (sc.radius + 1, sc.radius -0.5, 0)
text_center = (sc.radius + 0.5, sc.radius -0.5, 0)
size = r"\large"
scale_factor = 0.4
text = tex_mobject(r"\text{How Many Intersection Points?}", size = size)
@ -107,7 +140,7 @@ def count_intersection_points(*radians):
# ])
sc.add(text)
sc.count(intersection_dots, "show_creation", num_offset = (0, 0, 0))
sc.count(intersection_dots, "show", num_offset = (0, 0, 0))
sc.dither()
# sc.animate(Transform(intersection_dots, new_dots))
anims = []
@ -116,9 +149,11 @@ def count_intersection_points(*radians):
anims.append(Transform(mob, answer))
else:
anims.append(FadeOut(mob))
anims.append(FadeIn(formula)) #Put here to so they are foreground
anims.append(Animation(formula))
sc.animate(*anims, run_time = 1)
return sc
name = "CountIntersectionPoints%dPoints"%len(radians)
sc.write_to_movie(movie_prefix + name)
def non_general_position():
radians = np.arange(1, 7)
@ -151,7 +186,7 @@ def non_general_position():
Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME)
for mob1, mob2 in zip(sc1.mobjects, sc2.mobjects)
])
return sc1
sc1.write_to_movie(movie_prefix + "NonGeneralPosition")
def line_corresponds_with_pair(radians, r1, r2):
sc = CircleScene(radians)
@ -165,40 +200,319 @@ def line_corresponds_with_pair(radians, r1, r2):
sc.dots.remove(dot0)
sc.dots.remove(dot1)
sc.dither()
sc.animate(*[FadeOut(mob) for mob in sc.lines + sc.dots])
sc.animate(*[
FadeOut(mob, alpha_func = not_quite_there)
for mob in sc.lines + sc.dots
])
sc.add(sc.circle)
sc.animate(*[
ScaleInPlace(mob, 3, alpha_func = there_and_back)
for mob in (dot0, dot1)
])
sc.animate(Transform(line, dot0))
return sc
name = "LineCorrspondsWithPair%d%d"%(dot0_index, dot1_index)
sc.write_to_movie(movie_prefix + name)
def illustrate_n_choose_2(n):
#TODO, maybe make this snazzy
def illustrate_n_choose_k(n, k):
sc = Scene()
nrange = range(1, n+1)
nrange_im = tex_mobject(str(nrange))
pairs_str = str(list(it.combinations(nrange, 2)))
exp = tex_mobject(r"{{%d \choose 2} = %d \text{ total pairs}}"%(n, choose(n, 2)))
pairs_im = tex_mobject(r"\underbrace{%s}"%pairs_str, size=r"\tiny")
nrange_im.shift((0, 2, 0))
pairs_im.scale(0.7)
exp.shift((0, -2, 0))
sc.add(nrange_im)
tuples = list(it.combinations(nrange, k))
nrange_mobs = tex_mobjects([str(n) + r'\;' for n in nrange])
tuple_mobs = tex_mobjects(
[
(r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p)
for p, c in zip(tuples, it.count())
],
size = r"\small"
)
tuple_terms = {
2 : "pairs",
3 : "triplets",
4 : "quadruplets",
}
tuple_term = tuple_terms[k] if k in tuple_terms else "tuples"
form1, count, form2 = tex_mobject([
r"{%d \choose %d} = "%(n, k),
"%d"%choose(n, k),
r" \text{ total %s}"%tuple_term
])
for mob in nrange_mobs:
mob.shift((0, 2, 0))
for mob in form1, count, form2:
mob.shift((0, -SPACE_HEIGHT + 1, 0))
count_center = count.get_center()
for mob in tuple_mobs:
mob.scale(0.6)
sc.add(*nrange_mobs)
sc.dither()
sc.animate(FadeIn(pairs_im), FadeIn(exp))
sc.add(pairs_im)
return sc
run_time = 6.0
frame_time = run_time / len(tuples)
for tup, count in zip(tuples, it.count()):
count_mob = tex_mobject(str(count+1))
count_mob.center().shift(count_center)
sc.add(count_mob)
tuple_copy = CompoundMobject(*[nrange_mobs[index-1] for index in tup])
tuple_copy.highlight()
sc.add(tuple_copy)
sc.add(tuple_mobs[count])
sc.dither(frame_time)
sc.remove(count_mob)
sc.remove(tuple_copy)
sc.add(count_mob)
sc.animate(FadeIn(CompoundMobject(form1, form2)))
sc.write_to_movie(movie_prefix + "Illustrate%dChoose%d"%(n, k))
def intersection_point_correspondances(radians, indices):
assert(len(indices) == 4)
indices.sort()
sc = CircleScene(radians)
intersection_point = intersection(
(sc.points[indices[0]], sc.points[indices[2]]),
(sc.points[indices[1]], sc.points[indices[3]])
)
intersection_point = tuple(list(intersection_point) + [0])
intersection_dot = Dot(intersection_point)
intersection_dot_arrow = Arrow(intersection_point).nudge()
sc.add(intersection_dot)
pairs = list(it.combinations(range(len(radians)), 2))
lines_to_save = [
sc.lines[pairs.index((indices[p0], indices[p1]))]
for p0, p1 in [(0, 2), (1, 3)]
]
dots_to_save = [
sc.dots[p]
for p in indices
]
line_statement = tex_mobject(r"\text{Pair of Lines}")
dots_statement = tex_mobject(r"&\text{Quadruplet of} \\ &\text{outer dots}")
for mob in line_statement, dots_statement:
mob.center()
mob.scale(0.7)
mob.shift((SPACE_WIDTH-2, SPACE_HEIGHT - 1, 0))
fade_outs = []
line_highlights = []
dot_highlights = []
dot_pointers = []
for mob in sc.mobjects:
if mob in lines_to_save:
line_highlights.append(Highlight(mob))
elif mob in dots_to_save:
dot_highlights.append(Highlight(mob))
dot_pointers.append(Arrow(mob.get_center()).nudge())
elif mob != intersection_dot:
fade_outs.append(FadeOut(mob, alpha_func = not_quite_there))
sc.add(intersection_dot_arrow)
sc.animate(Highlight(intersection_dot))
sc.remove(intersection_dot_arrow)
sc.animate(*fade_outs)
sc.dither()
sc.add(line_statement)
sc.animate(*line_highlights)
sc.remove(line_statement)
sc.dither()
sc.add(dots_statement, *dot_pointers)
sc.animate(*dot_highlights)
sc.remove(dots_statement, *dot_pointers)
name = "IntersectionPointCorrespondances"
for ind in indices:
name += str(ind)
sc.write_to_movie(movie_prefix + name)
def lines_intersect_outside(radians, indices):
assert(len(indices) == 4)
indices.sort()
sc = CircleScene(radians)
intersection_point = intersection(
(sc.points[indices[0]], sc.points[indices[1]]),
(sc.points[indices[2]], sc.points[indices[3]])
)
intersection_point = tuple(list(intersection_point) + [0])
intersection_dot = Dot(intersection_point)
pairs = list(it.combinations(range(len(radians)), 2))
lines_to_save = [
sc.lines[pairs.index((indices[p0], indices[p1]))]
for p0, p1 in [(0, 1), (2, 3)]
]
sc.animate(*[
FadeOut(mob, alpha_func = not_quite_there)
for mob in sc.mobjects if mob not in lines_to_save
])
sc.animate(*[
Transform(
Line(sc.points[indices[p0]], sc.points[indices[p1]]),
Line(sc.points[indices[p0]], intersection_point))
for p0, p1 in [(0, 1), (3, 2)]
] + [ShowCreation(intersection_dot)])
name = "LinesIntersectOutside"
for ind in indices:
name += str(ind)
sc.write_to_movie(movie_prefix + name)
def quadruplets_to_intersections(*radians):
sc = CircleScene(radians)
quadruplets = it.combinations(range(len(radians)), 4)
frame_time = 1.0
for quad in quadruplets:
intersection_dot = Dot(intersection(
(sc.points[quad[0]], sc.points[quad[2]]),
(sc.points[quad[1]], sc.points[quad[3]])
)).repeat(3)
dot_quad = [deepcopy(sc.dots[i]) for i in quad]
for dot in dot_quad:
dot.scale_in_place(2)
# arrows = [Arrow(d.get_center()) for d in dot_quad]
dot_quad = CompoundMobject(*dot_quad)
# arrows = CompoundMobject(*arrows)
dot_quad.highlight()
# sc.add(arrows)
sc.add(dot_quad)
sc.dither(frame_time / 3)
sc.animate(Transform(
dot_quad,
intersection_dot,
run_time = 3*frame_time/2
))
# sc.remove(arrows)
name = "QuadrupletsToIntersections" + len(radians)
sc.write_to_movie(movie_prefix + name)
def defining_graph(graph):
gs = GraphScene(graph)
dots, lines = gs.vertices, gs.edges
gs.remove(*dots + lines)
all_dots = CompoundMobject(*dots)
gs.animate(ShowCreation(all_dots))
gs.remove(all_dots)
gs.add(*dots)
gs.dither()
gs.animate(*[
ShowCreation(line) for line in lines
])
#Move to new graph
new_graph = deepcopy(graph)
new_graph["vertices"] = [
(v[0] + 3*random(), v[1] + 3*random(), 0)
for v in new_graph["vertices"]
]
ngs = GraphScene(new_graph)
gs.animate(*[
Transform(m[0], m[1])
for m in zip(gs.mobjects, ngs.mobjects)
], run_time = 7.0)
name = "DefiningGraph" + graph["name"]
gs.write_to_movie(movie_prefix + name)
def doubled_edges(graph):
gs = GraphScene(graph)
lines_to_double = gs.edges[:9:3]
crazy_lines = [
(
line,
Line(line.end, line.start),
CurvedLine(line.start, line.end) ,
CurvedLine(line.end, line.start)
)
for line in lines_to_double
]
anims = []
outward_curved_lines = []
kwargs = {"run_time" : 3.0}
for straight, backwards, inward, outward in crazy_lines:
anims += [
Transform(straight, inward, **kwargs),
Transform(backwards, outward, **kwargs),
]
outward_curved_lines.append(outward)
gs.animate(*anims)
gs.dither()
gs.remove(*outward_curved_lines)
name = "DoubledEdges" + graph["name"]
gs.write_to_movie(movie_prefix + name)
def eulers_formula(graph):
gs = GraphScene(graph)
terms = "V - E + F =2".split(" ")
form = dict([
(key, mob)
for key, mob in zip(terms, tex_mobjects(terms))
])
for mob in form.values():
mob.shift((0, SPACE_HEIGHT-1.5, 0))
formula = CompoundMobject(*form.values())
new_form = dict([
(key, deepcopy(mob).shift((0, -0.7, 0)))
for key, mob in zip(form.keys(), form.values())
])
gs.add(formula)
colored_dots = [
deepcopy(d).scale_in_place(1.5).highlight("red")
for d in gs.dots
]
colored_edges = [
deepcopy(e).highlight("red")
for e in gs.edges
]
frame_time = 0.3
gs.generate_regions()
parameters = [
(colored_dots, "V", "mobject", "-", "show_creation"),
(colored_edges, "E", "mobject", "+", "show_creation"),
(gs.regions, "F", "region", "=2", "show_all")
]
for items, letter, item_type, symbol, mode in parameters:
gs.count(
items,
item_type = item_type,
mode = mode,
num_offset = new_form[letter].get_center(),
run_time = frame_time*len(items)
)
gs.dither()
if item_type == "mobject":
gs.remove(*items)
gs.add(new_form[symbol])
gs.reset_background()
name = "EulersFormula" + graph["name"]
gs.write_to_movie(movie_prefix + name)
##################################################
if __name__ == '__main__':
radians = np.arange(0, 6, 6.0/7)
# count_lines(*radians).write_to_movie("moser/CountLines")
count_intersection_points(*radians).write_to_movie("moser/CountIntersectionPoints")
# non_general_position().write_to_movie("moser/NonGeneralPosition")
# line_corresponds_with_pair(radians, radians[3], radians[4]).write_to_movie("moser/LineCorrespondsWithPair34")
# line_corresponds_with_pair(radians, radians[2], radians[5]).write_to_movie("moser/LineCorrespondsWithPair25")
# illustrate_n_choose_2(6).write_to_movie("moser/IllustrateNChoose2with6")
# count_lines(*radians)
# count_lines(*radians[:4])
# count_intersection_points(*radians[:4])
# count_intersection_points(*radians[:6])
# count_intersection_points(*radians)
# non_general_position()
# line_corresponds_with_pair(radians, radians[3], radians[4])
# line_corresponds_with_pair(radians, radians[2], radians[5])
# illustrate_n_choose_k(7, 2)
# illustrate_n_choose_k(6, 4)
# intersection_point_correspondances(radians, range(0, 7, 2))
# lines_intersect_outside(radians, [2, 4, 5, 6])
quadruplets_to_intersections(*radians[:6])
# defining_graph(SAMPLE_GRAPH)
# doubled_edges(CUBE_GRAPH)
# eulers_formula(CUBE_GRAPH)
# eulers_formula(SAMPLE_GRAPH)
# eulers_formula(OCTOHEDRON_GRAPH)

View file

@ -76,6 +76,12 @@ class HalfPlane(Region):
return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0)
Region.__init__(self, condition, *args, **kwargs)
def region_from_line_boundary(*lines):
reg = Region()
for line in lines:
reg.intersect(HalfPlane(line))
return reg
def plane_partition(*lines):
"""
A 'line' is a pair of points [(x0, y0,...), (x1, y1,...)]

View file

@ -16,6 +16,9 @@ from image_mobject import *
from animation import *
import displayer as disp
DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
DEFAULT_COUNT_RUN_TIME = 5.0
class Scene(object):
def __init__(self,
name = None,
@ -99,19 +102,29 @@ class Scene(object):
self.add(*moving_mobjects)
progress_bar.finish()
def count(self, mobjects, mode = "highlight",
color = "red",
num_offset = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0),
run_time = 5.0):
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)
def count_mobjects(
self, mobjects, mode = "highlight",
color = "red",
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME):
"""
Note: Leaves scene with a "number" attribute
for the final number mobject
for the final number mobject.
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 mode not in ["highlight", "show_creation"]:
raise Exception("Invalid mode")
if mode not in ["highlight", "show_creation", "show"]:
raise Warning("Unknown mode")
frame_time = run_time / len(mobjects)
if mode == "highlight":
self.add(*mobjects)
@ -126,10 +139,34 @@ class Scene(object):
mob.highlight(original_color)
if mode == "show_creation":
self.animate(ShowCreation(mob, run_time = frame_time))
if mode == "show":
self.add(mob)
self.dither(frame_time)
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
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_mobject(str(count))
num_mob.center().shift(num_offset)
self.add(num_mob)
self.highlight_region(region)
self.dither(frame_time)
if mode == "one_at_a_time":
self.reset_background()
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
def get_frame(self):
frame = self.background
for mob in self.mobjects:
@ -147,7 +184,7 @@ class Scene(object):
self.dither(end_dither_time)
disp.write_to_movie(self, name or str(self))
def show_frame(self):
def show(self):
Image.fromarray(self.get_frame()).show()