diff --git a/active_projects/eop/chapter1.py b/active_projects/eop/chapter1.py index 7cb6c469..8d71e708 100644 --- a/active_projects/eop/chapter1.py +++ b/active_projects/eop/chapter1.py @@ -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) + + + + + + + + + + + + + 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, + ) + ) diff --git a/mobject/geometry.py b/mobject/geometry.py index 46bce6f7..f7c3e53c 100644 --- a/mobject/geometry.py +++ b/mobject/geometry.py @@ -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, diff --git a/mobject/svg/svg_mobject.py b/mobject/svg/svg_mobject.py index ebfbde3d..3f405005 100644 --- a/mobject/svg/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -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 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 5caec9e6..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 @@ -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 + + + + + + + + + + + + + + + + + + + +