Merge branch 'master' of github.com:3b1b/manim into wallis-g

This commit is contained in:
Grant Sanderson
2018-04-12 15:12:07 -07:00
6 changed files with 670 additions and 225 deletions

View File

@ -1,5 +1,9 @@
from big_ol_pile_of_manim_imports import *
from old_projects.eoc.chapter8 import *
from active_projects.eop.histograms import *
from svgpathtools import *
import scipy.special
COIN_RADIUS = 0.3
COIN_THICKNESS = 0.4 * COIN_RADIUS
@ -665,19 +669,35 @@ class IllustrateAreaModel1(Scene):
label_B_knowing_A = label_B
self.play(FadeOut(label_B_copy))
self.remove(indep_formula.get_part_by_tex("P(B)"))
label_B_knowing_A_copy = label_B_knowing_A.copy()
self.add(label_B_knowing_A_copy)
self.play(
label_B_knowing_A_copy.next_to, indep_formula[-2], RIGHT
label_B_knowing_A_copy.next_to, indep_formula.get_part_by_tex("\cdot"), RIGHT,
)
# solve formula for P(B|A)
rearranged_formula = TexMobject(["P(B\mid A)", "=", "{P(A\\text{ and }B) \over P(A)}"])
rearranged_formula.move_to(indep_formula)
self.wait()
self.play(
# in some places get_part_by_tex does not find the correct part
# so I picked out fitting indices
label_B_knowing_A_copy.move_to, rearranged_formula.get_part_by_tex("P(B\mid A)"),
label_A_copy.move_to, rearranged_formula[-1][10],
label_A_and_B_copy.move_to, rearranged_formula[-1][3],
indep_formula.get_part_by_tex("=").move_to, rearranged_formula.get_part_by_tex("="),
Transform(indep_formula.get_part_by_tex("\cdot"), rearranged_formula[-1][8]),
)
# # # # # # # # # # # # # # # # #
# Old version with SampleSpace #
# # # # # # # # # # # # # # # # #
# def show_independent_events(self):
# sample_space = SampleSpace(
@ -747,62 +767,200 @@ class IllustrateAreaModel1(Scene):
def color_label(self, label):
label.set_color_by_tex("B", RED)
label.set_color_by_tex("I", GREEN)
# def color_label(self, label):
# label.set_color_by_tex("B", RED)
# label.set_color_by_tex("I", GREEN)
class IllustrateAreaModel2(AreaIsDerivative):
class IllustrateAreaModel2(GraphScene):
CONFIG = {
"y_max" : 4,
"y_min" : -4,
"num_iterations" : 7,
"x_min" : -5,
"x_max" : 5,
"y_min" : -0,
"y_max" : 0.6,
"graph_origin": 3*DOWN,
"num_rects": 20,
"y_axis_label" : "",
"num_rects" : 400,
"dT" : 0.25,
"variable_point_label" : "T",
"area_opacity" : 0.8,
"x_axis_label" : "",
"variable_point_label" : "x",
"y_axis_height" : 4
}
def construct(self):
x_max_1 = 0
x_min_1 = -x_max_1
x_max_2 = 5
x_min_2 = -x_max_2
self.setup_axes()
self.introduce_variable_area()
graph = self.get_graph(lambda x: np.exp(-x**2) / ((0.5 * TAU) ** 0.5))
graph, label = self.get_v_graph_and_label()
self.add(graph)
rect_list = self.get_riemann_rectangles_list(
graph, self.num_iterations
)
VGroup(*rect_list).set_fill(opacity = 0.8)
rects = rect_list[0]
self.play(ShowCreation(graph))
self.play(Write(rects))
for new_rects in rect_list[1:]:
rects.align_submobjects(new_rects)
for every_other_rect in rects[::2]:
every_other_rect.set_fill(opacity = 0)
self.play(Transform(
rects, new_rects,
run_time = 2,
submobject_mode = "lagged_start"
))
self.wait()
cdf_formula = TexMobject("P(|X-\mu| < x) = \int_{-x}^x {\exp(-{1\over 2}({t\over \sigma})^2) \over \sigma\sqrt{2\pi}} dt")
cdf_formula.set_color_by_tex("x", YELLOW)
cdf_formula.next_to(graph, LEFT, buff = 1)
self.add(cdf_formula)
# self.play(FadeOut(self.x_axis.numbers))
self.add_T_label(6)
self.change_area_bounds(
new_t_max = 4,
rate_func = there_and_back,
run_time = 2
self.v_graph = graph
self.add_T_label(x_min_1, color = YELLOW, animated = False)
self.remove(self.T_label_group, self.right_v_line)
#self.T_label_group[0].set_fill(opacity = 0).set_stroke(width = 0)
#self.T_label_group[1].set_fill(opacity = 0).set_stroke(width = 0)
#self.right_v_line.set_fill(opacity = 0).set_stroke(width = 0)
#self.add(self.T_label_group)
area = self.area = self.get_area(graph, x_min_1, x_max_1)
right_bound_label = TexMobject("x", color = YELLOW)
right_bound_label.next_to(self.coords_to_point(0,0), DOWN)
right_bound_label.target = right_bound_label.copy().next_to(self.coords_to_point(self.x_max,0), DOWN)
right_bound_label.set_fill(opacity = 0).set_stroke(width = 0)
left_bound_label = TexMobject("-x", color = YELLOW)
left_bound_label.next_to(self.coords_to_point(0,0), DOWN)
left_bound_label.target = right_bound_label.copy().next_to(self.coords_to_point(self.x_min,0), DOWN)
left_bound_label.set_fill(opacity = 0).set_stroke(width = 0)
#integral = self.get_riemann_rectangles(
#graph,x_min = self.x_min, x_max = x_max_1)
self.add(area)
def integral_update_func(t):
return 100 * scipy.special.erf(
self.point_to_coords(self.right_v_line.get_center())[0]
)
cdf_value = DecimalNumber(0, unit = "\%")
cdf_value.move_to(self.coords_to_point(0,0.2))
self.add_foreground_mobject(cdf_value)
self.add(ContinualChangingDecimal(
decimal_number_mobject = cdf_value,
number_update_func = integral_update_func,
num_decimal_points = 1
))
anim = self.get_animation_integral_bounds_change(
graph, x_min_2, x_max_2, run_time = 3)
# changing_cdf_value = ChangingDecimal(
# decimal_number_mobject = cdf_value,
# number_update_func = integral_update_func,
# num_decimal_points = 1
# )
self.play(
anim
)
def func(self, x):
return np.exp(-x**2/2)
class IllustrateAreaModel3(Scene):
def construct(self):
formula = TexMobject("E[X] = \sum_{i=1}^N p_i x_i").move_to(3 * LEFT + UP)
self.add(formula)
x_scale = 5.0
y_scale = 1.0
probabilities = np.array([1./8, 3./8, 3./8, 1./8])
prob_strings = ["{1\over 8}","{3\over 8}","{3\over 8}","{1\over 8}"]
cumulative_probabilities = np.cumsum(probabilities)
cumulative_probabilities = np.insert(cumulative_probabilities, 0, 0)
print cumulative_probabilities
y_values = np.array([0, 1, 2, 3])
hist = Histogram(probabilities, y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
)
flat_hist = Histogram(probabilities, 0 * y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
)
self.play(FadeIn(flat_hist))
self.play(
ReplacementTransform(flat_hist, hist)
)
braces = VGroup()
p_labels = VGroup()
# add x labels (braces)
for (p,string,bar) in zip(probabilities, prob_strings,hist.bars):
brace = Brace(bar, DOWN, buff = 0.1)
p_label = TexMobject(string).next_to(brace, DOWN, buff = SMALL_BUFF).scale(0.7)
group = VGroup(brace, p_label)
braces.add(brace)
p_labels.add(p_label)
self.play(
Write(group)
)
labels = VGroup()
for (y, bar) in zip(y_values, hist.bars):
label = TexMobject(str(int(y))).scale(0.7).next_to(bar, UP, buff = SMALL_BUFF)
self.play(FadeIn(label))
labels.add(label)
y_average = np.mean(y_values)
averaged_y_values = y_average * np.ones(np.shape(y_values))
averaged_hist = flat_hist = Histogram(probabilities, averaged_y_values,
mode = "widths",
x_scale = x_scale,
y_scale = y_scale,
x_labels = "none"
).fade(0.2)
ghost_hist = hist.copy().fade(0.8)
labels.fade(0.8)
self.bring_to_back(ghost_hist)
self.play(Transform(hist, averaged_hist))
average_label = TexMobject(str(y_average)).scale(0.7).next_to(averaged_hist, UP, SMALL_BUFF)
one_brace = Brace(averaged_hist, DOWN, buff = 0.1)
one_p_label = TexMobject(str(1)).next_to(one_brace, DOWN, buff = SMALL_BUFF).scale(0.7)
one_group = VGroup(one_brace, one_p_label)
self.play(
FadeIn(average_label),
Transform(braces, one_brace),
Transform(p_labels, one_p_label),
)
class AreaSplitting(Scene):
@ -932,6 +1090,62 @@ class AreaSplitting(Scene):
#self.play(FadeIn(tally))
class DieFace(SVGMobject):
def __init__(self, value, **kwargs):
self.value = value
self.file_name = "Dice-" + str(value)
self.ensure_valid_file()
paths, attributes = svg2paths(self.file_path)
print paths, attributes
SVGMobject.__init__(self, file_name = self.file_name)
# for submob in self.submobject_family():
# if type(submob) == Rectangle:
# submob.set_fill(opacity = 0)
# submob.set_stroke(width = 7)
class RowOfDice(VGroup):
CONFIG = {
"values" : range(1,7)
}
def generate_points(self):
for value in self.values:
new_die = DieFace(value)
new_die.submobjects[0].set_fill(opacity = 0)
new_die.submobjects[0].set_stroke(width = 7)
new_die.next_to(self, RIGHT)
self.add(new_die)
class ShowUncertainty(PiCreatureScene):
def construct(self):
row_of_dice = RowOfDice().scale(0.5).move_to(ORIGIN)
self.add(row_of_dice)
rounded_rect = RoundedRectangle(
width = 3,
height = 2,
corner_radius = 0.1
).shift(3*LEFT)
self.add(rounded_rect)

View File

@ -2,190 +2,231 @@ from big_ol_pile_of_manim_imports import *
from random import *
def text_range(start,stop,step): # a range as a list of strings
numbers = np.arange(start,stop,step)
labels = []
for x in numbers:
labels.append(str(x))
return labels
numbers = np.arange(start,stop,step)
labels = []
for x in numbers:
labels.append(str(x))
return labels
class Histogram(VMobject):
CONFIG = {
"start_color" : RED,
"end_color" : BLUE,
"x_scale" : 1.0,
"y_scale" : 1.0,
}
CONFIG = {
"start_color" : RED,
"end_color" : BLUE,
"x_scale" : 1.0,
"y_scale" : 1.0,
"x_labels" : "auto",
"x_min" : 0
}
def __init__(self, x_values, y_values, **kwargs):
def __init__(self, x_values, y_values, mode = "widths", **kwargs):
# mode = "widths" : x_values means the widths of the bars
# mode = "posts" : x_values means the delimiters btw the bars
digest_config(self, kwargs)
digest_config(self, kwargs)
# preliminaries
self.x_values = x_values
self.y_values = y_values
if mode == "widths" and len(x_values) != len(y_values):
raise Exception("Array lengths do not match up!")
elif mode == "posts" and len(x_values) != len(y_values) + 1:
raise Exception("Array lengths do not match up!")
self.x_steps = x_values[1:] - x_values[:-1]
self.x_min = x_values[0] - self.x_steps[0] * 0.5
self.x_posts = (x_values[1:] + x_values[:-1]) * 0.5
self.x_max = x_values[-1] + self.x_steps[-1] * 0.5
self.x_posts = np.insert(self.x_posts,0,self.x_min)
self.x_posts = np.append(self.x_posts,self.x_max)
# preliminaries
self.y_values = np.array(y_values)
self.x_widths = self.x_posts[1:] - self.x_posts[:-1]
if mode == "widths":
self.widths = x_values
self.posts = np.cumsum(self.widths)
self.posts = np.insert(self.posts, 0, 0)
self.posts += self.x_min
self.x_max = self.posts[-1]
elif mode == "posts":
self.posts = x_values
self.widths = x_values[1:] - x_values[:-1]
self.x_min = self.posts[0]
self.x_max = self.posts[-1]
else:
raise Exception("Invalid mode or no mode specified!")
self.x_values_scaled = self.x_scale * x_values
self.x_steps_scaled = self.x_scale * self.x_steps
self.x_posts_scaled = self.x_scale * self.x_posts
self.x_min_scaled = self.x_scale * self.x_min
self.x_max_scaled = self.x_scale * self.x_max
self.x_widths_scaled = self.x_scale * self.x_widths
self.x_mids = 0.5 * (self.posts[:-1] + self.posts[1:])
self.y_values_scaled = self.y_scale * self.y_values
self.widths_scaled = self.x_scale * self.widths
self.posts_scaled = self.x_scale * self.posts
self.x_min_scaled = self.x_scale * self.x_min
self.x_max_scaled = self.x_scale * self.x_max
VMobject.__init__(self, **kwargs)
digest_config(self, kwargs)
self.y_values_scaled = self.y_scale * self.y_values
VMobject.__init__(self, **kwargs)
digest_config(self, kwargs)
def generate_points(self):
def generate_points(self):
previous_bar = ORIGIN
self.bars = []
outline_points = []
self.x_labels = text_range(self.x_values[0], self.x_max, self.x_steps[0])
def empty_string_array(n):
arr = []
for i in range(n):
arr.append("")
return arr
for (i,x) in enumerate(self.x_values):
def num_arr_to_string_arr(arr): # converts number array to string array
ret_arr = []
for x in arr:
ret_arr.append(str(x))
return ret_arr
bar = Rectangle(
width = self.x_widths_scaled[i],
height = self.y_values_scaled[i],
)
t = float(x - self.x_values[0])/(self.x_values[-1] - self.x_values[0])
bar_color = interpolate_color(
self.start_color,
self.end_color,
t
)
bar.set_fill(color = bar_color, opacity = 1)
bar.set_stroke(width = 0)
bar.next_to(previous_bar,RIGHT,buff = 0, aligned_edge = DOWN)
previous_bar = ORIGIN
self.bars = []
outline_points = []
if self.x_labels == "widths":
self.x_labels = num_arr_to_string_arr(self.widths)
elif self.x_labels == "mids":
print self.x_mids
self.x_labels = num_arr_to_string_arr(self.x_mids)
elif self.x_labels == "none":
self.x_labels = empty_string_array(len(self.widths))
self.add(bar)
self.bars.append(bar)
print self.x_labels
label = TextMobject(self.x_labels[i])
label.next_to(bar,DOWN)
self.add(label)
for (i,x) in enumerate(self.x_mids):
if i == 0:
# start with the lower left
outline_points.append(bar.get_anchors()[-2])
bar = Rectangle(
width = self.widths_scaled[i],
height = self.y_values_scaled[i],
)
t = float(x - self.x_min)/(self.x_max - self.x_min)
bar_color = interpolate_color(
self.start_color,
self.end_color,
t
)
bar.set_fill(color = bar_color, opacity = 1)
bar.set_stroke(width = 0)
bar.next_to(previous_bar,RIGHT,buff = 0, aligned_edge = DOWN)
# upper two points of each bar
outline_points.append(bar.get_anchors()[0])
outline_points.append(bar.get_anchors()[1])
self.add(bar)
self.bars.append(bar)
previous_bar = bar
label = TextMobject(self.x_labels[i])
label.next_to(bar,DOWN)
self.add(label)
# close the outline
# lower right
outline_points.append(bar.get_anchors()[2])
# lower left
outline_points.append(outline_points[0])
if i == 0:
# start with the lower left
outline_points.append(bar.get_anchors()[-2])
# upper two points of each bar
outline_points.append(bar.get_anchors()[0])
outline_points.append(bar.get_anchors()[1])
previous_bar = bar
# close the outline
# lower right
outline_points.append(bar.get_anchors()[2])
# lower left
outline_points.append(outline_points[0])
self.outline = Polygon(*outline_points)
self.outline.set_stroke(color = WHITE)
self.add(self.outline)
def get_lower_left_point(self):
return self.bars[0].get_anchors()[-2]
class BuildUpHistogram(Animation):
def __init__(self, hist, **kwargs):
self.histogram = hist
self.outline = Polygon(*outline_points)
self.outline.set_stroke(color = WHITE)
self.add(self.outline)
def get_lower_left_point(self):
return self.bars[0].get_anchors()[-2]
class FlashThroughHistogram(Animation):
CONFIG = {
"cell_color" : WHITE,
"cell_opacity" : 0.8,
"hist_opacity" : 0.2
}
CONFIG = {
"cell_color" : WHITE,
"cell_opacity" : 0.8,
"hist_opacity" : 0.2
}
def __init__(self, mobject, direction = "horizontal", mode = "random", **kwargs):
def __init__(self, mobject, direction = "horizontal", mode = "random", **kwargs):
digest_config(self, kwargs)
digest_config(self, kwargs)
self.cell_height = mobject.y_scale
self.prototype_cell = Rectangle(
width = 1,
height = self.cell_height,
fill_color = self.cell_color,
fill_opacity = self.cell_opacity,
stroke_width = 0,
)
self.cell_height = mobject.y_scale
self.prototype_cell = Rectangle(
width = 1,
height = self.cell_height,
fill_color = self.cell_color,
fill_opacity = self.cell_opacity,
stroke_width = 0,
)
x_values = mobject.x_values
y_values = mobject.y_values
x_values = mobject.x_values
y_values = mobject.y_values
self.mode = mode
self.direction = direction
self.mode = mode
self.direction = direction
self.generate_cell_indices(x_values,y_values)
Animation.__init__(self,mobject,**kwargs)
self.generate_cell_indices(x_values,y_values)
Animation.__init__(self,mobject,**kwargs)
def generate_cell_indices(self,x_values,y_values):
def generate_cell_indices(self,x_values,y_values):
self.cell_indices = []
for (i,x) in enumerate(x_values):
self.cell_indices = []
for (i,x) in enumerate(x_values):
nb_cells = y_values[i]
for j in range(nb_cells):
self.cell_indices.append((i, j))
nb_cells = y_values[i]
for j in range(nb_cells):
self.cell_indices.append((i, j))
self.reordered_cell_indices = self.cell_indices
if self.mode == "random":
shuffle(self.reordered_cell_indices)
self.reordered_cell_indices = self.cell_indices
if self.mode == "random":
shuffle(self.reordered_cell_indices)
def cell_for_index(self,i,j):
def cell_for_index(self,i,j):
if self.direction == "vertical":
width = self.mobject.x_scale
height = self.mobject.y_scale
x = (i + 0.5) * self.mobject.x_scale
y = (j + 0.5) * self.mobject.y_scale
center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
if self.direction == "vertical":
width = self.mobject.x_scale
height = self.mobject.y_scale
x = (i + 0.5) * self.mobject.x_scale
y = (j + 0.5) * self.mobject.y_scale
center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
elif self.direction == "horizontal":
width = self.mobject.x_scale / self.mobject.y_values[i]
height = self.mobject.y_scale * self.mobject.y_values[i]
x = i * self.mobject.x_scale + (j + 0.5) * width
y = height / 2
center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
elif self.direction == "horizontal":
width = self.mobject.x_scale / self.mobject.y_values[i]
height = self.mobject.y_scale * self.mobject.y_values[i]
x = i * self.mobject.x_scale + (j + 0.5) * width
y = height / 2
center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP
cell = Rectangle(width = width, height = height)
cell.move_to(center)
return cell
cell = Rectangle(width = width, height = height)
cell.move_to(center)
return cell
def update_mobject(self,t):
def update_mobject(self,t):
if t == 0:
self.mobject.add(self.prototype_cell)
if t == 0:
self.mobject.add(self.prototype_cell)
flash_nb = int(t * (len(self.cell_indices))) - 1
(i,j) = self.reordered_cell_indices[flash_nb]
cell = self.cell_for_index(i,j)
self.prototype_cell.width = cell.get_width()
self.prototype_cell.height = cell.get_height()
self.prototype_cell.generate_points()
self.prototype_cell.move_to(cell.get_center())
flash_nb = int(t * (len(self.cell_indices))) - 1
(i,j) = self.reordered_cell_indices[flash_nb]
cell = self.cell_for_index(i,j)
self.prototype_cell.width = cell.get_width()
self.prototype_cell.height = cell.get_height()
self.prototype_cell.generate_points()
self.prototype_cell.move_to(cell.get_center())
#if t == 1:
# self.mobject.remove(self.prototype_cell)
#if t == 1:
# self.mobject.remove(self.prototype_cell)
@ -205,47 +246,47 @@ class FlashThroughHistogram(Animation):
class SampleScene(Scene):
def construct(self):
def construct(self):
x_values = np.array([1,2,3,4,5])
y_values = np.array([4,3,5,2,3])
x_values = np.array([1,2,3,4,5])
y_values = np.array([4,3,5,2,3])
hist1 = Histogram(
x_values = x_values,
y_values = y_values,
x_scale = 0.5,
y_scale = 0.5,
).shift(1*DOWN)
self.add(hist1)
self.wait()
hist1 = Histogram(
x_values = x_values,
y_values = y_values,
x_scale = 0.5,
y_scale = 0.5,
).shift(1*DOWN)
self.add(hist1)
self.wait()
y_values2 = np.array([3,8,7,15,5])
y_values2 = np.array([3,8,7,15,5])
hist2 = Histogram(
x_values = x_values,
y_values = y_values2,
x_scale = 0.5,
y_scale = 0.5,
x_labels = text_range(1,6,1),
)
hist2 = Histogram(
x_values = x_values,
y_values = y_values2,
x_scale = 0.5,
y_scale = 0.5,
x_labels = text_range(1,6,1),
)
v1 = hist1.get_lower_left_point()
v2 = hist2.get_lower_left_point()
hist2.shift(v1 - v2)
v1 = hist1.get_lower_left_point()
v2 = hist2.get_lower_left_point()
hist2.shift(v1 - v2)
# self.play(
# ReplacementTransform(hist1,hist2)
# )
# self.play(
# ReplacementTransform(hist1,hist2)
# )
self.play(
FlashThroughHistogram(
hist1,
direction = "horizontal",
mode = "linear",
run_time = 10,
rate_func = None,
)
)
self.play(
FlashThroughHistogram(
hist1,
direction = "horizontal",
mode = "linear",
run_time = 10,
rate_func = None,
)
)

View File

@ -717,6 +717,41 @@ class Square(Rectangle):
)
class RoundedRectangle(Rectangle):
CONFIG = {
"corner_radius" : 0.5,
"close_new_points" : True
}
def generate_points(self):
y, x = self.height / 2., self.width / 2.
r = self.corner_radius
arc_ul = ArcBetweenPoints(x * LEFT + (y - r) * UP, (x - r) * LEFT + y * UP, angle = -TAU/4)
arc_ur = ArcBetweenPoints((x - r) * RIGHT + y * UP, x * RIGHT + (y - r) * UP, angle = -TAU/4)
arc_lr = ArcBetweenPoints(x * RIGHT + (y - r) * DOWN, (x - r) * RIGHT + y * DOWN, angle = -TAU/4)
arc_ll = ArcBetweenPoints(x * LEFT + (y - r) * DOWN, (x - r) * LEFT + y * DOWN, angle = TAU/4) # sic! bug in ArcBetweenPoints?
points = arc_ul.points
points = np.append(points,np.array([y * UP]), axis = 0)
points = np.append(points,np.array([y * UP]), axis = 0)
points = np.append(points,arc_ur.points, axis = 0)
points = np.append(points,np.array([x * RIGHT]), axis = 0)
points = np.append(points,np.array([x * RIGHT]), axis = 0)
points = np.append(points,arc_lr.points, axis = 0)
points = np.append(points,np.array([y * DOWN]), axis = 0)
points = np.append(points,np.array([y * DOWN]), axis = 0)
points = np.append(points,arc_ll.points[::-1], axis = 0) # sic! see comment above
points = np.append(points,np.array([x * LEFT]), axis = 0)
points = np.append(points,np.array([x * LEFT]), axis = 0)
points = np.append(points,np.array([x * LEFT + (y - r) * UP]), axis = 0)
points = points[::-1]
self.set_points(points)
class Grid(VMobject):
CONFIG = {
"height": 6.0,

View File

@ -4,10 +4,12 @@ import string
import warnings
from xml.dom import minidom
from utils.color import *
from constants import *
from mobject.geometry import Circle
from mobject.geometry import Rectangle
from mobject.geometry import RoundedRectangle
from utils.bezier import is_closed
from utils.config_ops import digest_config
from utils.config_ops import digest_locals
@ -34,7 +36,7 @@ class SVGMobject(VMobject):
"file_name": None,
"unpack_groups": True, # if False, creates a hierarchy of VGroups
"stroke_width": 0,
"fill_opacity": 1,
"fill_opacity": 1.0,
# "fill_color" : LIGHT_GREY,
"propagate_style_to_family": True,
}
@ -155,16 +157,54 @@ class SVGMobject(VMobject):
return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN)
def rect_to_mobject(self, rect_element):
if rect_element.hasAttribute("fill"):
if Color(str(rect_element.getAttribute("fill"))) == Color(WHITE):
return
mob = Rectangle(
width=float(rect_element.getAttribute("width")),
height=float(rect_element.getAttribute("height")),
stroke_width=0,
fill_color=WHITE,
fill_opacity=1.0
)
fill_color = rect_element.getAttribute("fill")
stroke_color = rect_element.getAttribute("stroke")
stroke_width = rect_element.getAttribute("stroke-width")
corner_radius = rect_element.getAttribute("rx")
# input preprocessing
if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE):
opacity = 0
fill_color = BLACK # shdn't be necessary but avoids error msgs
if fill_color in ["#000", "#000000"]:
fill_color = WHITE
if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE):
stroke_width = 0
stroke_color = BLACK
if stroke_color in ["#000", "#000000"]:
stroke_color = WHITE
if stroke_width in ["", "none", "0"]:
stroke_width = 0
# is there sth to draw?
if opacity == 0 and stroke_width == 0:
return
if corner_radius in ["", "0", "none"]:
corner_radius = 0
corner_radius = float(corner_radius)
if corner_radius == 0:
mob = Rectangle(
width = float(rect_element.getAttribute("width")),
height = float(rect_element.getAttribute("height")),
stroke_width = stroke_width,
stroke_color = stroke_color,
fill_color = fill_color,
fill_opacity = opacity
)
else:
mob = RoundedRectangle(
width = float(rect_element.getAttribute("width")),
height = float(rect_element.getAttribute("height")),
stroke_width = stroke_width,
stroke_color = stroke_color,
fill_color = fill_color,
fill_opacity = opacity,
corner_radius = corner_radius
)
mob.shift(mob.get_center() - mob.get_corner(UP + LEFT))
return mob

View File

@ -4,12 +4,13 @@ from constants import *
import itertools as it
from scene.scene import Scene
from animation.creation import Write
from animation.creation import Write, DrawBorderThenFill, ShowCreation
from animation.transform import Transform
from animation.update import UpdateFromAlphaFunc
from mobject.functions import ParametricFunction
from mobject.geometry import Line
from mobject.geometry import Rectangle
from mobject.geometry import RegularPolygon
from mobject.number_line import NumberLine
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
@ -49,6 +50,8 @@ class GraphScene(Scene):
"default_input_color": YELLOW,
"default_riemann_start_color": BLUE,
"default_riemann_end_color": GREEN,
"area_opacity" : 0.8,
"num_rects" : 50,
}
def setup(self):
@ -245,6 +248,8 @@ class GraphScene(Scene):
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)
@ -284,6 +289,18 @@ class GraphScene(Scene):
for n in range(n_iterations)
]
def get_area(self, graph, t_min, t_max):
numerator = max(t_max - t_min, 0.01)
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,
@ -421,6 +438,84 @@ class GraphScene(Scene):
return group
def add_T_label(self, x_val, color = WHITE, animated = False, **kwargs):
triangle = RegularPolygon(n=3, start_angle = np.pi/2)
triangle.scale_to_fit_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)
T_label = TexMobject(self.variable_point_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
)
else:
self.add(triangle, v_line, T_label)
self.T_label_group = VGroup(T_label, triangle)
self.right_v_line = v_line
def get_animation_integral_bounds_change(
self,
graph,
new_t_min,
new_t_max,
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)
if hasattr(self, "right_v_line"):
group.add(self.right_v_line)
else:
group.add(VGroup())
# because update_group expects 3 elements in group
if hasattr(self, "T_label_group"):
group.add(self.T_label_group)
else:
group.add(VGroup())
def update_group(group, alpha):
area, v_line, 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_v_line = self.get_vertical_line_to_graph(
t_max, graph
)
new_v_line.set_color(v_line.get_color())
T_label.move_to(new_v_line.get_bottom(), UP)
#Fade close to 0
if len(T_label) > 0:
T_label[0].set_fill(opacity = min(1, t_max))
Transform(area, new_area).update(1)
Transform(v_line, new_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,
@ -462,3 +557,23 @@ class GraphScene(Scene):
)
secant_slope_group.kwargs["x"] = target_x
secant_slope_group.kwargs["dx"] = target_dx