From aa98e32ed3141ec508a613efbd2ed63c07ee0dbd Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 21:44:33 +0200 Subject: [PATCH 01/12] added central input sampling fro integrals --- scene/graph_scene.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scene/graph_scene.py b/scene/graph_scene.py index 5caec9e6..1fde8684 100644 --- a/scene/graph_scene.py +++ b/scene/graph_scene.py @@ -245,6 +245,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) @@ -421,6 +423,9 @@ class GraphScene(Scene): return group + def get_animation_integral_bounds_change(self): + pass + def animate_secant_slope_group_change( self, secant_slope_group, target_dx=None, From 51c61e0d0dd305a400399d6a75ea0136c5308dc3 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 21:44:49 +0200 Subject: [PATCH 02/12] Test graph scene --- active_projects/eop/chapter1.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 7cb6c469..be2c5204 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -762,7 +762,7 @@ class IllustrateAreaModel2(AreaIsDerivative): "y_min" : -4, "num_iterations" : 7, "y_axis_label" : "", - "num_rects" : 400, + "num_rects" : 50, "dT" : 0.25, "variable_point_label" : "T", "area_opacity" : 0.8, @@ -771,6 +771,7 @@ class IllustrateAreaModel2(AreaIsDerivative): self.setup_axes() self.introduce_variable_area() + return graph, label = self.get_v_graph_and_label() @@ -935,6 +936,29 @@ class AreaSplitting(Scene): +class Test(GraphScene): + CONFIG = { + "x_min" : -3, + "x_max" : 3, + "y_min" : -0.2, + "y_max" : 2, + "graph_origin": 3*DOWN + } + + def construct(self): + + self.setup_axes() + graph = self.get_graph(lambda x: np.exp(-x**2/2)) + self.add(graph) + + integral = self.get_riemann_rectangles(graph,x_min = -3, x_max = 0) + self.add(integral) + + anim = self.get_animation_integral_bounds_change(graph, new_xmin = 3, new_xmax = 2) + + self.play(anim) + + From 319a94537e4a99eceea424dec4b8cd5e971a793c Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 22:54:07 +0200 Subject: [PATCH 03/12] moved integral drawing to GraphScene --- old_projects/eoc/chapter8.py | 2 +- scene/graph_scene.py | 110 ++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/old_projects/eoc/chapter8.py b/old_projects/eoc/chapter8.py index 17e500e1..db9505c5 100644 --- a/old_projects/eoc/chapter8.py +++ b/old_projects/eoc/chapter8.py @@ -1501,7 +1501,7 @@ class AreaIsDerivative(PlotVelocity, ReconfigurableScene): self.add(*self.get_v_graph_and_label()) self.x_axis_label_mob.shift(MED_LARGE_BUFF*DOWN) self.v_graph_label.shift(MED_LARGE_BUFF*DOWN) - self.foreground_mobjects = [] + self.foreground_mobjects = [] def construct(self): self.introduce_variable_area() diff --git a/scene/graph_scene.py b/scene/graph_scene.py index 1fde8684..72107241 100644 --- a/scene/graph_scene.py +++ b/scene/graph_scene.py @@ -49,6 +49,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): @@ -286,6 +288,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, @@ -423,8 +437,80 @@ class GraphScene(Scene): return group - def get_animation_integral_bounds_change(self): - pass + + def add_T_label(self, x_val, **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(WHITE, 1) + triangle.set_stroke(width = 0) + T_label = TexMobject(self.variable_point_label) + T_label.next_to(triangle, DOWN) + v_line = self.get_vertical_line_to_graph( + x_val, self.v_graph, + color = YELLOW + ) + + self.play( + DrawBorderThenFill(triangle), + ShowCreation(v_line), + Write(T_label, run_time = 1), + **kwargs + ) + + 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, @@ -467,3 +553,23 @@ class GraphScene(Scene): ) secant_slope_group.kwargs["x"] = target_x secant_slope_group.kwargs["dx"] = target_dx + + + + + + + + + + + + + + + + + + + + From c0c202072bedff547d6b05f171a780b0597f1545 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Wed, 11 Apr 2018 22:54:22 +0200 Subject: [PATCH 04/12] basic integral drawing working --- active_projects/eop/chapter1.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index be2c5204..54439e7a 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -941,20 +941,27 @@ class Test(GraphScene): "x_min" : -3, "x_max" : 3, "y_min" : -0.2, - "y_max" : 2, - "graph_origin": 3*DOWN + "y_max" : 2.0, + "graph_origin": 3*DOWN, + "num_rects": 100 } def construct(self): + x_max_1 = -2.99 + x_max_2 = 3.0 + self.setup_axes() graph = self.get_graph(lambda x: np.exp(-x**2/2)) self.add(graph) + area = self.area = self.get_area(graph,self.x_min, x_max_1) - integral = self.get_riemann_rectangles(graph,x_min = -3, x_max = 0) + integral = self.get_riemann_rectangles( + graph,x_min = self.x_min, x_max = x_max_1) self.add(integral) - anim = self.get_animation_integral_bounds_change(graph, new_xmin = 3, new_xmax = 2) + anim = self.get_animation_integral_bounds_change( + graph, self.x_min, x_max_2, run_time = 3) self.play(anim) From 8e7f33c65a157b752800a9ea927f7c5e97ed87ec Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 15:17:25 +0200 Subject: [PATCH 05/12] colored triangle indicator in GraphScene --- scene/graph_scene.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/scene/graph_scene.py b/scene/graph_scene.py index 72107241..a224ba59 100644 --- a/scene/graph_scene.py +++ b/scene/graph_scene.py @@ -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 @@ -438,25 +439,28 @@ class GraphScene(Scene): return group - def add_T_label(self, x_val, **kwargs): + 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(WHITE, 1) + triangle.set_fill(color, 1) triangle.set_stroke(width = 0) - T_label = TexMobject(self.variable_point_label) + 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 ) - self.play( - DrawBorderThenFill(triangle), - ShowCreation(v_line), - Write(T_label, run_time = 1), - **kwargs - ) + 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 From dca4400b09a793be2af8c9bb7c4442a2fd40f0b2 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 15:17:47 +0200 Subject: [PATCH 06/12] improved area model examples 1 and 2 --- active_projects/eop/chapter1.py | 181 +++++++++++++++++++------------- 1 file changed, 107 insertions(+), 74 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 54439e7a..a18c0c7b 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -1,6 +1,8 @@ from big_ol_pile_of_manim_imports import * from old_projects.eoc.chapter8 import * +import scipy.special + COIN_RADIUS = 0.3 COIN_THICKNESS = 0.4 * COIN_RADIUS COIN_FORESHORTENING = 0.3 @@ -665,19 +667,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,63 +765,108 @@ 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" : 50, - "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() - return + graph = self.get_graph(lambda x: np.exp(-x**2) / ((0.5 * TAU) ** 0.5)) + self.add(graph) - graph, label = self.get_v_graph_and_label() - rect_list = self.get_riemann_rectangles_list( - graph, self.num_iterations - ) - VGroup(*rect_list).set_fill(opacity = 0.8) - rects = rect_list[0] + 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, UP, buff = 1) + self.add(cdf_formula) + - 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() + 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.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.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 AreaSplitting(Scene): @@ -936,36 +999,6 @@ class AreaSplitting(Scene): -class Test(GraphScene): - CONFIG = { - "x_min" : -3, - "x_max" : 3, - "y_min" : -0.2, - "y_max" : 2.0, - "graph_origin": 3*DOWN, - "num_rects": 100 - } - - def construct(self): - - x_max_1 = -2.99 - x_max_2 = 3.0 - - self.setup_axes() - graph = self.get_graph(lambda x: np.exp(-x**2/2)) - self.add(graph) - area = self.area = self.get_area(graph,self.x_min, x_max_1) - - integral = self.get_riemann_rectangles( - graph,x_min = self.x_min, x_max = x_max_1) - self.add(integral) - - anim = self.get_animation_integral_bounds_change( - graph, self.x_min, x_max_2, run_time = 3) - - self.play(anim) - - From e9f99f1cff16e7bae59575b5067158eb917956f1 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 17:21:46 +0200 Subject: [PATCH 07/12] expanded (and fixed) Histogram class --- active_projects/eop/histograms.py | 379 +++++++++++++++++------------- 1 file changed, 210 insertions(+), 169 deletions(-) diff --git a/active_projects/eop/histograms.py b/active_projects/eop/histograms.py index 2de66ea4..2f5a847e 100644 --- a/active_projects/eop/histograms.py +++ b/active_projects/eop/histograms.py @@ -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 - def generate_points(self): + VMobject.__init__(self, **kwargs) + digest_config(self, kwargs) + - previous_bar = ORIGIN - self.bars = [] - outline_points = [] - self.x_labels = text_range(self.x_values[0], self.x_max, self.x_steps[0]) + def generate_points(self): - for (i,x) in enumerate(self.x_values): + def empty_string_array(n): + arr = [] + for i in range(n): + arr.append("") + return 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) - - self.add(bar) - self.bars.append(bar) + 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 - label = TextMobject(self.x_labels[i]) - label.next_to(bar,DOWN) - self.add(label) + 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)) - if i == 0: - # start with the lower left - outline_points.append(bar.get_anchors()[-2]) + print self.x_labels - # upper two points of each bar - outline_points.append(bar.get_anchors()[0]) - outline_points.append(bar.get_anchors()[1]) + for (i,x) in enumerate(self.x_mids): - previous_bar = bar + 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) + + self.add(bar) + self.bars.append(bar) - # close the outline - # lower right - outline_points.append(bar.get_anchors()[2]) - # lower left - outline_points.append(outline_points[0]) + label = TextMobject(self.x_labels[i]) + label.next_to(bar,DOWN) + self.add(label) + + 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 - - 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 + 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 - 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) - - # self.play( - # ReplacementTransform(hist1,hist2) - # ) + v1 = hist1.get_lower_left_point() + v2 = hist2.get_lower_left_point() + hist2.shift(v1 - v2) + + # 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, + ) + ) From 6f268e58d48f95fd8e4c7f6a20c0d0a4fc919278 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 17:22:03 +0200 Subject: [PATCH 08/12] wrote a 3rd example for area model --- active_projects/eop/chapter1.py | 92 +++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index a18c0c7b..cd17db66 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -1,5 +1,6 @@ from big_ol_pile_of_manim_imports import * from old_projects.eoc.chapter8 import * +from active_projects.eop.histograms import * import scipy.special @@ -808,6 +809,7 @@ class IllustrateAreaModel2(GraphScene): 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) @@ -862,6 +864,96 @@ class IllustrateAreaModel2(GraphScene): +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), + ) + + + + + From 7e28eff6202910506c2b0778568b2099bf440caf Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 18:32:33 +0200 Subject: [PATCH 09/12] introduced die faces --- active_projects/eop/chapter1.py | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index cd17db66..25eca271 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -798,12 +798,13 @@ class IllustrateAreaModel2(GraphScene): self.setup_axes() graph = self.get_graph(lambda x: np.exp(-x**2) / ((0.5 * TAU) ** 0.5)) + self.add(graph) 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, UP, buff = 1) + cdf_formula.next_to(graph, LEFT, buff = 1) self.add(cdf_formula) @@ -1088,6 +1089,46 @@ class AreaSplitting(Scene): #self.play(FadeIn(tally)) +class DieFace(SVGMobject): + + def __init__(self, value, **kwargs): + + self.value = value + SVGMobject.__init__(self, file_name = "Dice-" + str(value)) + +class RowOfDice(VGroup): + CONFIG = { + "values" : range(1,7) + } + + def generate_points(self): + for value in self.values: + new_die = DieFace(value) + new_die.next_to(self, RIGHT) + self.add(new_die) + + + +class ShowUncertainty(PiCreatureScene): + + def construct(self): + + row_of_dice = RowOfDice(values = [1]) + self.add(row_of_dice) + + + + + + + + + + + + + + From cf5d461aa6b2e867ee7687652e818fa751148362 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 18:33:16 +0200 Subject: [PATCH 10/12] working fixing SVG import issue (preprocessing fill and stroke) --- mobject/svg/svg_mobject.py | 55 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/mobject/svg/svg_mobject.py b/mobject/svg/svg_mobject.py index ebfbde3d..84ac55b4 100644 --- a/mobject/svg/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -4,6 +4,7 @@ import string import warnings from xml.dom import minidom +from utils.color import * from constants import * from mobject.geometry import Circle @@ -155,15 +156,57 @@ 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 + fill_color = rect_element.getAttribute("fill") + stroke_color = rect_element.getAttribute("stroke") + stroke_width = rect_element.getAttribute("stroke-width") + print "fill_color =", fill_color + print "stroke_color =", stroke_color + print "stroke_width =", stroke_width + + # input preprocessing + if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE): + print "no fill" + opacity = 0 + fill_color = BLACK # shdn't be necessary but avoids error msgs + if fill_color in ["#000", "#000000"]: + print "flipping fill color" + fill_color = WHITE + if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE): + stroke_width = 0 + stroke_color = BLACK + print "no stroke color" + if stroke_color in ["#000", "#000000"]: + print "flipping stroke color" + stroke_color = WHITE + if stroke_width in ["", "none", "0"]: + print "no stroke width" + stroke_width = 0 + + # is there sth to draw? + if opacity == 0 and stroke_width == 0: + print "nothing to draw" + return + + print "after preprocessing:" + print "fill_color =", fill_color + print "stroke_color =", stroke_color + print "stroke_width =", stroke_width + print "opacity = ", opacity + + + # if rect_element.hasAttribute("fill"): + # color_attr = str(rect_element.getAttribute("fill")) + # if color_attr == "none": + # return + # elif Color(color_attr) == 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 + stroke_width = stroke_width, + stroke_color = stroke_color, + fill_color = fill_color, + fill_opacity = opacity ) mob.shift(mob.get_center() - mob.get_corner(UP + LEFT)) return mob From ca8135a90fd8d5f2e088758a46ae7cd5b3d322b2 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 23:19:09 +0200 Subject: [PATCH 11/12] new class RoundedRectangle and SVG support for it --- mobject/geometry.py | 35 ++++++++++++++++++++++++ mobject/svg/svg_mobject.py | 55 ++++++++++++++++++-------------------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/mobject/geometry.py b/mobject/geometry.py index 79896fa4..86a7fc0d 100644 --- a/mobject/geometry.py +++ b/mobject/geometry.py @@ -728,6 +728,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, diff --git a/mobject/svg/svg_mobject.py b/mobject/svg/svg_mobject.py index 84ac55b4..3f405005 100644 --- a/mobject/svg/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -9,6 +9,7 @@ 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 @@ -35,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, } @@ -159,55 +160,51 @@ class SVGMobject(VMobject): fill_color = rect_element.getAttribute("fill") stroke_color = rect_element.getAttribute("stroke") stroke_width = rect_element.getAttribute("stroke-width") - print "fill_color =", fill_color - print "stroke_color =", stroke_color - print "stroke_width =", stroke_width + corner_radius = rect_element.getAttribute("rx") # input preprocessing if fill_color in ["", "none", "#FFF", "#FFFFFF"] or Color(fill_color) == Color(WHITE): - print "no fill" opacity = 0 fill_color = BLACK # shdn't be necessary but avoids error msgs if fill_color in ["#000", "#000000"]: - print "flipping fill color" fill_color = WHITE if stroke_color in ["", "none", "#FFF", "#FFFFFF"] or Color(stroke_color) == Color(WHITE): stroke_width = 0 stroke_color = BLACK - print "no stroke color" if stroke_color in ["#000", "#000000"]: - print "flipping stroke color" stroke_color = WHITE if stroke_width in ["", "none", "0"]: - print "no stroke width" stroke_width = 0 # is there sth to draw? if opacity == 0 and stroke_width == 0: - print "nothing to draw" return - print "after preprocessing:" - print "fill_color =", fill_color - print "stroke_color =", stroke_color - print "stroke_width =", stroke_width - print "opacity = ", opacity + 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 + ) - # if rect_element.hasAttribute("fill"): - # color_attr = str(rect_element.getAttribute("fill")) - # if color_attr == "none": - # return - # elif Color(color_attr) == Color(WHITE): - # return - 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 - ) mob.shift(mob.get_center() - mob.get_corner(UP + LEFT)) return mob From 64ea39393c78b806c994133529d92d5fa4f11660 Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Thu, 12 Apr 2018 23:20:08 +0200 Subject: [PATCH 12/12] improvements on dice --- active_projects/eop/chapter1.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 25eca271..8d71e708 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -1,6 +1,7 @@ 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 @@ -1094,7 +1095,16 @@ class DieFace(SVGMobject): def __init__(self, value, **kwargs): self.value = value - SVGMobject.__init__(self, file_name = "Dice-" + str(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 = { @@ -1104,6 +1114,8 @@ class RowOfDice(VGroup): 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) @@ -1113,9 +1125,14 @@ class ShowUncertainty(PiCreatureScene): def construct(self): - row_of_dice = RowOfDice(values = [1]) + 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)