def __init__(self, **kwargs): digest_config(self, kwargs) videos = [VideoIcon() for x in range(self.num_videos)] VGroup.__init__(self, *videos, **kwargs) self.arrange() self.set_width(FRAME_WIDTH - MED_LARGE_BUFF) self.set_color_by_gradient(*self.gradient_colors)
def get_tips(self): result = VGroup() if hasattr(self, "tip"): result.add(self.tip) if hasattr(self, "start_tip"): result.add(self.start_tip) return result
class PartyHat(SVGMobject): CONFIG = { "file_name": "party_hat", "height": 1.5, "pi_creature": None, "stroke_width": 0, "fill_opacity": 1, "frills_colors": [MAROON_B, PURPLE], "cone_color": GREEN, "dots_colors": [YELLOW], } NUM_FRILLS = 7 NUM_DOTS = 6 def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) self.set_height(self.height) if self.pi_creature is not None: self.next_to(self.pi_creature.eyes, UP, buff=0) self.frills = VGroup(*self[:self.NUM_FRILLS]) self.cone = self[self.NUM_FRILLS] self.dots = VGroup(*self[self.NUM_FRILLS + 1:]) self.frills.set_color_by_gradient(*self.frills_colors) self.cone.set_color(self.cone_color) self.dots.set_color_by_gradient(*self.dots_colors)
def __init__(self, mobject, **kwargs): VGroup.__init__(self, Line(UP + LEFT, DOWN + RIGHT), Line(UP + RIGHT, DOWN + LEFT), ) self.replace(mobject, stretch=True) self.set_stroke(self.stroke_color, self.stroke_width)
def __init__(self, **kwargs): VGroup.__init__(self, **kwargs) for i in range(self.width): for j in range(self.height): pi = PiCreature().scale(0.3) pi.move_to(i * DOWN + j * RIGHT) self.add(pi)
def __init__(self, values, **kwargs): VGroup.__init__(self, **kwargs) if self.max_value is None: self.max_value = max(values) self.add_axes() self.add_bars(values) self.center()
def __init__(self, **kwargs): possible_values = list(map(str, list(range(1, 11)))) + ["J", "Q", "K"] possible_suits = ["hearts", "diamonds", "spades", "clubs"] VGroup.__init__(self, *[ PlayingCard(value=value, suit=suit, **kwargs) for value in possible_values for suit in possible_suits ])
def get_transformer(self, **kwargs): transform_kwargs = dict(self.default_apply_complex_function_kwargs) transform_kwargs.update(kwargs) transformer = VGroup() if hasattr(self, "plane"): self.prepare_for_transformation(self.plane) transformer.add(self.plane) transformer.add(*self.transformable_mobjects) return transformer, transform_kwargs
def __init__(self, stream_lines, **kwargs): VGroup.__init__(self, **kwargs) self.stream_lines = stream_lines for line in stream_lines: line.anim = self.line_anim_class(line, **self.line_anim_config) line.anim.begin() line.time = -self.lag_range * random.random() self.add(line.anim.mobject) self.add_updater(lambda m, dt: m.update(dt))
def pop_tips(self): start, end = self.get_start_and_end() result = VGroup() if self.has_tip(): result.add(self.tip) self.remove(self.tip) if self.has_start_tip(): result.add(self.start_tip) self.remove(self.start_tip) self.put_start_and_end_on(start, end) return result
def arrange_subparts(self, *subparts): for i, piece in enumerate(subparts): piece.rotate(i * np.pi / 12, about_point=ORIGIN) p1, p2, p3, p4, p5, p6, p7 = subparts center_row = VGroup(p1, p4, p7) center_row.arrange(RIGHT, buff=0) for p in p2, p3, p5, p6: p.set_width(p1.get_width()) p2.move_to(p1.get_top(), DOWN + LEFT) p3.move_to(p1.get_bottom(), UP + LEFT) p5.move_to(p4.get_top(), DOWN + LEFT) p6.move_to(p4.get_bottom(), UP + LEFT)
def animate_product(self, left, right, result): l_matrix = left.get_mob_matrix() r_matrix = right.get_mob_matrix() result_matrix = result.get_mob_matrix() circle = Circle( radius=l_matrix[0][0].get_height(), color=GREEN ) circles = VGroup(*[ entry.get_point_mobject() for entry in (l_matrix[0][0], r_matrix[0][0]) ]) (m, k), n = l_matrix.shape, r_matrix.shape[1] for mob in result_matrix.flatten(): mob.set_color(BLACK) lagging_anims = [] for a in range(m): for b in range(n): for c in range(k): l_matrix[a][c].set_color(YELLOW) r_matrix[c][b].set_color(YELLOW) for c in range(k): start_parts = VGroup( l_matrix[a][c].copy(), r_matrix[c][b].copy() ) result_entry = result_matrix[a][b].split()[c] new_circles = VGroup(*[ circle.copy().shift(part.get_center()) for part in start_parts.split() ]) self.play(Transform(circles, new_circles)) self.play( Transform( start_parts, result_entry.copy().set_color(YELLOW), path_arc=-np.pi / 2, lag_ratio=0, ), *lagging_anims ) result_entry.set_color(YELLOW) self.remove(start_parts) lagging_anims = [ ApplyMethod(result_entry.set_color, WHITE) ] for c in range(k): l_matrix[a][c].set_color(WHITE) r_matrix[c][b].set_color(WHITE) self.play(FadeOut(circles), *lagging_anims) self.wait()
def __init__(self, **kwargs): super().__init__(**kwargs) body = Cube(side_length=1) for dim, scale_factor in enumerate(self.body_dimensions): body.stretch(scale_factor, dim=dim) body.set_width(self.width) body.set_fill(self.shaded_body_color, opacity=1) body.sort(lambda p: p[2]) body[-1].set_fill(self.body_color) screen_plate = body.copy() keyboard = VGroup(*[ VGroup(*[ Square(**self.key_color_kwargs) for x in range(12 - y % 2) ]).arrange(RIGHT, buff=SMALL_BUFF) for y in range(4) ]).arrange(DOWN, buff=MED_SMALL_BUFF) keyboard.stretch_to_fit_width( self.keyboard_width_to_body_width * body.get_width(), ) keyboard.stretch_to_fit_height( self.keyboard_height_to_body_height * body.get_height(), ) keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF) keyboard.shift(MED_SMALL_BUFF * UP) body.add(keyboard) screen_plate.stretch(self.screen_thickness / self.body_dimensions[2], dim=2) screen = Rectangle( stroke_width=0, fill_color=BLACK, fill_opacity=1, ) screen.replace(screen_plate, stretch=True) screen.scale_in_place(self.screen_width_to_screen_plate_width) screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen_plate.add(screen) screen_plate.next_to(body, UP, buff=0) screen_plate.rotate( self.open_angle, RIGHT, about_point=screen_plate.get_bottom() ) self.screen_plate = screen_plate self.screen = screen axis = Line( body.get_corner(UP + LEFT + OUT), body.get_corner(UP + RIGHT + OUT), color=BLACK, stroke_width=2 ) self.axis = axis self.add(body, screen_plate, axis) self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN) self.rotate(np.pi / 6, DOWN, about_point=ORIGIN)
def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) self.set_height(self.height) if self.pi_creature is not None: self.next_to(self.pi_creature.eyes, UP, buff=0) self.frills = VGroup(*self[:self.NUM_FRILLS]) self.cone = self[self.NUM_FRILLS] self.dots = VGroup(*self[self.NUM_FRILLS + 1:]) self.frills.set_color_by_gradient(*self.frills_colors) self.cone.set_color(self.cone_color) self.dots.set_color_by_gradient(*self.dots_colors)
def name_parts(self): self.mouth = self.submobjects[MOUTH_INDEX] self.body = self.submobjects[BODY_INDEX] self.pupils = VGroup(*[ self.submobjects[LEFT_PUPIL_INDEX], self.submobjects[RIGHT_PUPIL_INDEX] ]) self.eyes = VGroup(*[ self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX] ]) self.eye_parts = VGroup(self.eyes, self.pupils) self.parts_named = True
def get_dot_template(self, place): # This should be replaced for non-base-10 counting scenes dots = VGroup(*[ Dot( point, radius=0.25, fill_opacity=0, stroke_width=2, stroke_color=WHITE, ) for point in self.get_template_configuration(place) ]) dots.set_height(self.dot_configuration_height) return dots
def get_riemann_rectangles( self, graph, x_min=None, x_max=None, dx=0.1, input_sample_type="left", stroke_width=1, stroke_color=BLACK, fill_opacity=1, start_color=None, end_color=None, show_signed_area=True, width_scale_factor=1.001 ): x_min = x_min if x_min is not None else self.x_min x_max = x_max if x_max is not None else self.x_max if start_color is None: start_color = self.default_riemann_start_color if end_color is None: end_color = self.default_riemann_end_color rectangles = VGroup() x_range = np.arange(x_min, x_max, dx) colors = color_gradient([start_color, end_color], len(x_range)) for x, color in zip(x_range, colors): if input_sample_type == "left": sample_input = x elif input_sample_type == "right": sample_input = x + dx elif input_sample_type == "center": sample_input = x + 0.5 * dx else: raise Exception("Invalid input sample type") graph_point = self.input_to_graph_point(sample_input, graph) points = VGroup(*list(map(VectorizedPoint, [ self.coords_to_point(x, 0), self.coords_to_point(x + width_scale_factor * dx, 0), graph_point ]))) rect = Rectangle() rect.replace(points, stretch=True) if graph_point[1] < self.graph_origin[1] and show_signed_area: fill_color = invert_color(color) else: fill_color = color rect.set_fill(fill_color, opacity=fill_opacity) rect.set_stroke(stroke_color, width=stroke_width) rectangles.add(rect) return rectangles
def get_number_mob(self, num): result = VGroup() place = 0 max_place = self.max_place while place < max_place: digit = TexMobject(str(self.get_place_num(num, place))) if place >= len(self.digit_place_colors): self.digit_place_colors += self.digit_place_colors digit.set_color(self.digit_place_colors[place]) digit.scale(self.num_scale_factor) digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN) result.add(digit) place += 1 return result
def get_division_along_dimension(self, p_list, dim, colors, vect): p_list = self.complete_p_list(p_list) colors = color_gradient(colors, len(p_list)) last_point = self.get_edge_center(-vect) parts = VGroup() for factor, color in zip(p_list, colors): part = SampleSpace() part.set_fill(color, 1) part.replace(self, stretch=True) part.stretch(factor, dim) part.move_to(last_point, -vect) last_point = part.get_edge_center(vect) parts.add(part) return parts
def get_order_n_self(self, order): if order == 0: result = self.get_seed_shape() else: lower_order = self.get_order_n_self(order - 1) subparts = [ lower_order.copy() for x in range(self.num_subparts) ] self.arrange_subparts(*subparts) result = VGroup(*subparts) result.set_height(self.height) result.center() return result
def setup(self): self.dots = VGroup() self.number = 0 self.max_place = 0 self.number_mob = VGroup(TexMobject(str(self.number))) self.number_mob.scale(self.num_scale_factor) self.number_mob.shift(self.num_start_location) self.dot_templates = [] self.dot_template_iterators = [] self.curr_configurations = [] self.arrows = VGroup() self.add(self.number_mob)
def get_animation_integral_bounds_change( self, graph, new_t_min, new_t_max, fade_close_to_origin=True, run_time=1.0 ): curr_t_min = self.x_axis.point_to_number(self.area.get_left()) curr_t_max = self.x_axis.point_to_number(self.area.get_right()) if new_t_min is None: new_t_min = curr_t_min if new_t_max is None: new_t_max = curr_t_max group = VGroup(self.area) group.add(self.left_v_line) group.add(self.left_T_label_group) group.add(self.right_v_line) group.add(self.right_T_label_group) def update_group(group, alpha): area, left_v_line, left_T_label, right_v_line, right_T_label = group t_min = interpolate(curr_t_min, new_t_min, alpha) t_max = interpolate(curr_t_max, new_t_max, alpha) new_area = self.get_area(graph, t_min, t_max) new_left_v_line = self.get_vertical_line_to_graph( t_min, graph ) new_left_v_line.set_color(left_v_line.get_color()) left_T_label.move_to(new_left_v_line.get_bottom(), UP) new_right_v_line = self.get_vertical_line_to_graph( t_max, graph ) new_right_v_line.set_color(right_v_line.get_color()) right_T_label.move_to(new_right_v_line.get_bottom(), UP) # Fade close to 0 if fade_close_to_origin: if len(left_T_label) > 0: left_T_label[0].set_fill(opacity=min(1, np.abs(t_min))) if len(right_T_label) > 0: right_T_label[0].set_fill(opacity=min(1, np.abs(t_max))) Transform(area, new_area).update(1) Transform(left_v_line, new_left_v_line).update(1) Transform(right_v_line, new_right_v_line).update(1) return group return UpdateFromAlphaFunc(group, update_group, run_time=run_time)
def setup_in_uv_space(self): u_values, v_values = self.get_u_values_and_v_values() faces = VGroup() for i in range(len(u_values) - 1): for j in range(len(v_values) - 1): u1, u2 = u_values[i:i + 2] v1, v2 = v_values[j:j + 2] face = ThreeDVMobject() face.set_points_as_corners([ [u1, v1, 0], [u2, v1, 0], [u2, v2, 0], [u1, v2, 0], [u1, v1, 0], ]) faces.add(face) face.u_index = i face.v_index = j face.u1 = u1 face.u2 = u2 face.v1 = v1 face.v2 = v2 faces.set_fill( color=self.fill_color, opacity=self.fill_opacity ) faces.set_stroke( color=self.stroke_color, width=self.stroke_width, opacity=self.stroke_opacity, ) self.add(*faces) if self.checkerboard_colors: self.set_fill_by_checkerboard(*self.checkerboard_colors)
def get_subdivision_braces_and_labels( self, parts, labels, direction, buff=SMALL_BUFF, min_num_quads=1 ): label_mobs = VGroup() braces = VGroup() for label, part in zip(labels, parts): brace = Brace( part, direction, min_num_quads=min_num_quads, buff=buff ) if isinstance(label, Mobject): label_mob = label else: label_mob = TexMobject(label) label_mob.scale(self.default_label_scale_val) label_mob.next_to(brace, direction, buff) braces.add(brace) label_mobs.add(label_mob) parts.braces = braces parts.labels = label_mobs parts.label_kwargs = { "labels": label_mobs.copy(), "direction": direction, "buff": buff, } return VGroup(parts.braces, parts.labels)
def get_axes(self): axes = ThreeDAxes(**self.three_d_axes_config) for axis in axes: if self.cut_axes_at_radius: p0 = axis.get_start() p1 = axis.number_to_point(-1) p2 = axis.number_to_point(1) p3 = axis.get_end() new_pieces = VGroup( Line(p0, p1), Line(p1, p2), Line(p2, p3), ) for piece in new_pieces: piece.shade_in_3d = True new_pieces.match_style(axis.pieces) axis.pieces.submobjects = new_pieces.submobjects for tick in axis.tick_marks: tick.add(VectorizedPoint( 1.5 * tick.get_center(), )) return axes
def get_det_text(matrix, determinant=None, background_rect=False, initial_scale_factor=2): parens = TexMobject("(", ")") parens.scale(initial_scale_factor) parens.stretch_to_fit_height(matrix.get_height()) l_paren, r_paren = parens.split() l_paren.next_to(matrix, LEFT, buff=0.1) r_paren.next_to(matrix, RIGHT, buff=0.1) det = TextMobject("det") det.scale(initial_scale_factor) det.next_to(l_paren, LEFT, buff=0.1) if background_rect: det.add_background_rectangle() det_text = VGroup(det, l_paren, r_paren) if determinant is not None: eq = TexMobject("=") eq.next_to(r_paren, RIGHT, buff=0.1) result = TexMobject(str(determinant)) result.next_to(eq, RIGHT, buff=0.2) det_text.add(eq, result) return det_text
def __init__(self, func, **kwargs): VGroup.__init__(self, **kwargs) self.func = func dt = self.dt start_points = self.get_start_points( **self.start_points_generator_config ) for point in start_points: points = [point] for t in np.arange(0, self.virtual_time, dt): last_point = points[-1] points.append(last_point + dt * func(last_point)) if get_norm(last_point) > self.cutoff_norm: break line = VMobject() step = max(1, int(len(points) / self.n_anchors_per_line)) line.set_points_smoothly(points[::step]) self.add(line) self.set_stroke(self.stroke_color, self.stroke_width) if self.color_by_arc_length: len_to_rgb = get_rgb_gradient_function( self.min_arc_length, self.max_arc_length, colors=self.colors, ) for line in self: arc_length = line.get_arc_length() rgb = len_to_rgb([arc_length])[0] color = rgb_to_color(rgb) line.set_color(color) elif self.color_by_magnitude: image_file = get_color_field_image_file( lambda p: get_norm(func(p)), min_value=self.min_magnitude, max_value=self.max_magnitude, colors=self.colors, ) self.color_using_background_image(image_file)
def __init__(self, focal_point, **kwargs): digest_config(self, kwargs) circles = VGroup() for x in range(self.n_circles): circle = Circle( radius=self.big_radius, stroke_color=BLACK, stroke_width=0, ) circle.add_updater( lambda c: c.move_to(focal_point) ) circle.save_state() circle.set_width(self.small_radius * 2) circle.set_stroke(self.color, self.start_stroke_width) circles.add(circle) animations = [ Restore(circle) for circle in circles ] super().__init__(*animations, **kwargs)
def add_lines(self, left, right): line_kwargs = { "color": BLUE, "stroke_width": 2, } left_rows = [ VGroup(*row) for row in left.get_mob_matrix() ] h_lines = VGroup() for row in left_rows[:-1]: h_line = Line(row.get_left(), row.get_right(), **line_kwargs) h_line.next_to(row, DOWN, buff=left.v_buff / 2.) h_lines.add(h_line) right_cols = [ VGroup(*col) for col in np.transpose(right.get_mob_matrix()) ] v_lines = VGroup() for col in right_cols[:-1]: v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs) v_line.next_to(col, RIGHT, buff=right.h_buff / 2.) v_lines.add(v_line) self.play(ShowCreation(h_lines)) self.play(ShowCreation(v_lines)) self.wait() self.show_frame()
def add_axes(self): x_axis = Line(self.tick_width * LEFT / 2, self.width * RIGHT) y_axis = Line(MED_LARGE_BUFF * DOWN, self.height * UP) ticks = VGroup() heights = np.linspace(0, self.height, self.n_ticks + 1) values = np.linspace(0, self.max_value, self.n_ticks + 1) for y, value in zip(heights, values): tick = Line(LEFT, RIGHT) tick.set_width(self.tick_width) tick.move_to(y * UP) ticks.add(tick) y_axis.add(ticks) self.add(x_axis, y_axis) self.x_axis, self.y_axis = x_axis, y_axis if self.label_y_axis: labels = VGroup() for tick, value in zip(ticks, values): label = TexMobject(str(np.round(value, 2))) label.set_height(self.y_axis_label_height) label.next_to(tick, LEFT, SMALL_BUFF) labels.add(label) self.y_axis_labels = labels self.add(labels)
class TeacherStudentsScene(PiCreatureScene): CONFIG = { "student_colors": [BLUE_D, BLUE_E, BLUE_C], "teacher_color": GREY_BROWN, "background_color": GREY_E, "student_scale_factor": 0.8, "seconds_to_blink": 2, "screen_height": 3, } def setup(self): self.background = FullScreenFadeRectangle( fill_color=self.background_color, fill_opacity=1, ) self.add(self.background) PiCreatureScene.setup(self) self.screen = ScreenRectangle(height=self.screen_height) self.screen.to_corner(UP + LEFT) self.hold_up_spot = self.teacher.get_corner(UP + LEFT) + MED_LARGE_BUFF * UP def create_pi_creatures(self): self.teacher = Mortimer(color=self.teacher_color) self.teacher.to_corner(DOWN + RIGHT) self.teacher.look(DOWN + LEFT) self.students = VGroup( *[Randolph(color=c) for c in self.student_colors]) self.students.arrange(RIGHT) self.students.scale(self.student_scale_factor) self.students.to_corner(DOWN + LEFT) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) return [self.teacher] + list(self.students) def get_teacher(self): return self.teacher def get_students(self): return self.students def teacher_says(self, *content, **kwargs): return self.pi_creature_says(self.get_teacher(), *content, **kwargs) def student_says(self, *content, **kwargs): if "target_mode" not in kwargs: target_mode = random.choice([ "raise_right_hand", "raise_left_hand", ]) kwargs["target_mode"] = target_mode if "bubble_kwargs" not in kwargs: kwargs["bubble_kwargs"] = {"direction": LEFT} student = self.get_students()[kwargs.get("student_index", 2)] return self.pi_creature_says(student, *content, **kwargs) def teacher_thinks(self, *content, **kwargs): return self.pi_creature_thinks(self.get_teacher(), *content, **kwargs) def student_thinks(self, *content, **kwargs): student = self.get_students()[kwargs.get("student_index", 2)] return self.pi_creature_thinks(student, *content, **kwargs) def change_all_student_modes(self, mode, **kwargs): self.change_student_modes(*[mode] * len(self.students), **kwargs) def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.pop("added_anims", []) self.play(self.get_student_changes(*modes, **kwargs), *added_anims) def get_student_changes(self, *modes, **kwargs): pairs = list(zip(self.get_students(), modes)) pairs = [(s, m) for s, m in pairs if m is not None] start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) if "look_at_arg" in kwargs: for pi in target: pi.look_at(kwargs["look_at_arg"]) anims = [Transform(s, t) for s, t in zip(start, target)] return LaggedStart( *anims, lag_ratio=kwargs.get("lag_ratio", 0.5), run_time=1, ) # return Transform( # start, target, # lag_ratio=lag_ratio, # run_time=2 # ) def zoom_in_on_thought_bubble(self, bubble=None, radius=FRAME_Y_RADIUS + FRAME_X_RADIUS): if bubble is None: for pi in self.get_pi_creatures(): if hasattr(pi, "bubble") and isinstance( pi.bubble, ThoughtBubble): bubble = pi.bubble break if bubble is None: raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() def func(point): centered = point + vect return radius * centered / get_norm(centered) self.play( * [ApplyPointwiseFunction(func, mob) for mob in self.get_mobjects()]) def teacher_holds_up(self, mobject, target_mode="raise_right_hand", added_anims=None, **kwargs): mobject.move_to(self.hold_up_spot, DOWN) mobject.shift_onto_screen() added_anims = added_anims or [] self.play(FadeIn(mobject, shift=UP), self.teacher.change, target_mode, *added_anims)
def add_T_label(self, x_val, side=RIGHT, label=None, color=WHITE, animated=False, **kwargs): """ This method adds to the Scene: -- a Vertical line from the x-axis to the corresponding point on the graph/curve. -- a small vertical Triangle whose top point lies on the base of the vertical line -- a TexMobject to be a label for the Line and Triangle, at the bottom of the Triangle. The scene needs to have the graph have the identifier/variable name self.v_graph. Parameters ---------- x_val (Union[float, int]) The x value at which the secant enters, and intersects the graph for the first time. side (np.ndarray()) label (str) The label to give the vertline and triangle color (str) The hex color of the label. animated (bool=False) Whether or not to animate the addition of the T_label **kwargs Any valid keyword argument of a self.play call. """ triangle = RegularPolygon(n=3, start_angle=np.pi / 2) triangle.set_height(MED_SMALL_BUFF) triangle.move_to(self.coords_to_point(x_val, 0), UP) triangle.set_fill(color, 1) triangle.set_stroke(width=0) if label is None: T_label = TexMobject(self.variable_point_label, fill_color=color) else: T_label = TexMobject(label, fill_color=color) T_label.next_to(triangle, DOWN) v_line = self.get_vertical_line_to_graph(x_val, self.v_graph, color=YELLOW) if animated: self.play(DrawBorderThenFill(triangle), ShowCreation(v_line), Write(T_label, run_time=1), **kwargs) if np.all(side == LEFT): self.left_T_label_group = VGroup(T_label, triangle) self.left_v_line = v_line self.add(self.left_T_label_group, self.left_v_line) elif np.all(side == RIGHT): self.right_T_label_group = VGroup(T_label, triangle) self.right_v_line = v_line self.add(self.right_T_label_group, self.right_v_line)
def setup_axes(self, animate=False): """ This method sets up the axes of the graph. Parameters ---------- animate (bool=False) Whether or not to animate the setting up of the Axes. """ # TODO, once eoc is done, refactor this to be less redundant. x_num_range = float(self.x_max - self.x_min) self.space_unit_to_x = self.x_axis_width / x_num_range if self.x_labeled_nums is None: self.x_labeled_nums = [] if self.x_leftmost_tick is None: self.x_leftmost_tick = self.x_min x_axis = NumberLine( x_min=self.x_min, x_max=self.x_max, unit_size=self.space_unit_to_x, tick_frequency=self.x_tick_frequency, leftmost_tick=self.x_leftmost_tick, numbers_with_elongated_ticks=self.x_labeled_nums, color=self.axes_color, # Added this line decimal_number_config={"color": self.label_nums_color}) x_axis.shift(self.graph_origin - x_axis.number_to_point(0)) if len(self.x_labeled_nums) > 0: if self.exclude_zero_label: self.x_labeled_nums = [ x for x in self.x_labeled_nums if x != 0 ] x_axis.add_numbers(*self.x_labeled_nums) if self.x_axis_label: x_label = TextMobject(self.x_axis_label) # Added this line x_label.set_color(self.label_color) x_label.next_to(x_axis.get_tick_marks(), UP + RIGHT, buff=SMALL_BUFF) x_label.shift_onto_screen() x_axis.add(x_label) self.x_axis_label_mob = x_label y_num_range = float(self.y_max - self.y_min) self.space_unit_to_y = self.y_axis_height / y_num_range if self.y_labeled_nums is None: self.y_labeled_nums = [] if self.y_bottom_tick is None: self.y_bottom_tick = self.y_min y_axis = NumberLine( x_min=self.y_min, x_max=self.y_max, unit_size=self.space_unit_to_y, tick_frequency=self.y_tick_frequency, leftmost_tick=self.y_bottom_tick, numbers_with_elongated_ticks=self.y_labeled_nums, color=self.axes_color, line_to_number_vect=LEFT, label_direction=LEFT, # yongze added this line decimal_number_config={"color": self.label_nums_color}, ) y_axis.shift(self.graph_origin - y_axis.number_to_point(0)) y_axis.rotate(np.pi / 2, about_point=y_axis.number_to_point(0)) if len(self.y_labeled_nums) > 0: if self.exclude_zero_label: self.y_labeled_nums = [ y for y in self.y_labeled_nums if y != 0 ] y_axis.add_numbers(*self.y_labeled_nums) if self.y_axis_label: y_label = TextMobject(self.y_axis_label) # yongze Added this line y_label.set_color(self.label_color) y_label.next_to(y_axis.get_corner(UP + RIGHT), UP + RIGHT, buff=SMALL_BUFF) y_label.shift_onto_screen() y_axis.add(y_label) self.y_axis_label_mob = y_label if animate: self.play(Write(VGroup(x_axis, y_axis))) else: self.add(x_axis, y_axis) self.x_axis, self.y_axis = self.axes = VGroup(x_axis, y_axis) self.default_graph_colors = it.cycle(self.default_graph_colors)
class PiCreature(SVGMobject): CONFIG = { "color": BLUE_E, "file_name_prefix": "PiCreatures", "stroke_width": 0, "stroke_color": BLACK, "fill_opacity": 1.0, "height": 3, "corner_scale_factor": 0.75, "flip_at_start": False, "is_looking_direction_purposeful": False, "start_corner": None, # Range of proportions along body where arms are "right_arm_range": [0.55, 0.7], "left_arm_range": [.34, .462], "pupil_to_eye_width_ratio": 0.4, "pupil_dot_to_pupil_width_ratio": 0.3, } def __init__(self, mode="plain", **kwargs): digest_config(self, kwargs) self.mode = mode self.parts_named = False try: svg_file = os.path.join( PI_CREATURE_DIR, "%s_%s.svg" % (self.file_name_prefix, mode)) SVGMobject.__init__(self, file_name=svg_file, **kwargs) except Exception: warnings.warn("No %s design with mode %s" % (self.file_name_prefix, mode)) # TODO, this needs to change to a different, better directory svg_file = os.path.join( FILE_DIR, "PiCreatures_plain.svg", ) SVGMobject.__init__(self, mode="plain", file_name=svg_file, **kwargs) if self.flip_at_start: self.flip() if self.start_corner is not None: self.to_corner(self.start_corner) def align_data(self, mobject): # This ensures that after a transform into a different mode, # the pi creatures mode will be updated appropriately SVGMobject.align_data(self, mobject) if isinstance(mobject, PiCreature): self.mode = mobject.get_mode() def name_parts(self): self.mouth = self.submobjects[MOUTH_INDEX] self.body = self.submobjects[BODY_INDEX] self.pupils = VGroup(*[ self.submobjects[LEFT_PUPIL_INDEX], self.submobjects[RIGHT_PUPIL_INDEX] ]) self.eyes = VGroup(*[ self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX] ]) self.eye_parts = VGroup(self.eyes, self.pupils) self.parts_named = True def init_colors(self): SVGMobject.init_colors(self) if not self.parts_named: self.name_parts() self.mouth.set_fill(BLACK, opacity=1) self.body.set_fill(self.color, opacity=1) self.eyes.set_fill(WHITE, opacity=1) self.init_pupils() return self def init_pupils(self): # Instead of what is drawn, make new circles. # This is mostly because the paths associated # with the eyes in all the drawings got slightly # messed up. for eye, pupil in zip(self.eyes, self.pupils): pupil_r = eye.get_width() / 2 pupil_r *= self.pupil_to_eye_width_ratio dot_r = pupil_r dot_r *= self.pupil_dot_to_pupil_width_ratio new_pupil = Circle( radius=pupil_r, color=BLACK, fill_opacity=1, stroke_width=0, ) dot = Circle( radius=dot_r, color=WHITE, fill_opacity=1, stroke_width=0, ) new_pupil.move_to(pupil) pupil.become(new_pupil) dot.shift( new_pupil.get_boundary_point(UL) - dot.get_boundary_point(UL)) pupil.add(dot) def copy(self): copy_mobject = SVGMobject.copy(self) copy_mobject.name_parts() return copy_mobject def set_color(self, color): self.body.set_fill(color) self.color = color return self def change_mode(self, mode): new_self = self.__class__(mode=mode, ) new_self.match_style(self) new_self.match_height(self) if self.is_flipped() != new_self.is_flipped(): new_self.flip() new_self.shift(self.eyes.get_center() - new_self.eyes.get_center()) if hasattr(self, "purposeful_looking_direction"): new_self.look(self.purposeful_looking_direction) self.become(new_self) self.mode = mode return self def get_mode(self): return self.mode def look(self, direction): norm = get_norm(direction) if norm == 0: return direction /= norm self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): c = eye.get_center() right = eye.get_right() - c up = eye.get_top() - c vect = direction[0] * right + direction[1] * up v_norm = get_norm(vect) p_radius = 0.5 * pupil.get_width() vect *= (v_norm - 0.75 * p_radius) / v_norm pupil.move_to(c + vect) self.pupils[1].align_to(self.pupils[0], DOWN) return self def look_at(self, point_or_mobject): if isinstance(point_or_mobject, Mobject): point = point_or_mobject.get_center() else: point = point_or_mobject self.look(point - self.eyes.get_center()) return self def change(self, new_mode, look_at_arg=None): self.change_mode(new_mode) if look_at_arg is not None: self.look_at(look_at_arg) return self def get_looking_direction(self): vect = self.pupils.get_center() - self.eyes.get_center() return normalize(vect) def get_look_at_spot(self): return self.eyes.get_center() + self.get_looking_direction() def is_flipped(self): return self.eyes.submobjects[0].get_center()[0] > \ self.eyes.submobjects[1].get_center()[0] def blink(self): eye_parts = self.eye_parts eye_bottom_y = eye_parts.get_bottom()[1] eye_parts.apply_function(lambda p: [p[0], eye_bottom_y, p[2]]) return self def to_corner(self, vect=None, **kwargs): if vect is not None: SVGMobject.to_corner(self, vect, **kwargs) else: self.scale(self.corner_scale_factor) self.to_corner(DOWN + LEFT, **kwargs) return self def get_bubble(self, *content, **kwargs): bubble_class = kwargs.get("bubble_class", ThoughtBubble) bubble = bubble_class(**kwargs) if len(content) > 0: if isinstance(content[0], str): content_mob = TextMobject(*content) else: content_mob = content[0] bubble.add_content(content_mob) if "height" not in kwargs and "width" not in kwargs: bubble.resize_to_content() bubble.pin_to(self) self.bubble = bubble return bubble def make_eye_contact(self, pi_creature): self.look_at(pi_creature.eyes) pi_creature.look_at(self.eyes) return self def shrug(self): self.change_mode("shruggie") top_mouth_point, bottom_mouth_point = [ self.mouth.points[np.argmax(self.mouth.points[:, 1])], self.mouth.points[np.argmin(self.mouth.points[:, 1])] ] self.look(top_mouth_point - bottom_mouth_point) return self def get_arm_copies(self): body = self.body return VGroup(*[ body.copy().pointwise_become_partial(body, *alpha_range) for alpha_range in (self.right_arm_range, self.left_arm_range) ])
def get_arm_copies(self): body = self.body return VGroup(*[ body.copy().pointwise_become_partial(body, *alpha_range) for alpha_range in (self.right_arm_range, self.left_arm_range) ])
def get_axis_labels(self, x_label_tex="x", y_label_tex="y"): self.axis_labels = VGroup( self.get_x_axis_label(x_label_tex), self.get_y_axis_label(y_label_tex), ) return self.axis_labels
class Arrow(Line): CONFIG = { "tip_length": 0.25, "tip_width_to_length_ratio": 1, "max_tip_length_to_length_ratio": 0.35, "max_stem_width_to_tip_width_ratio": 0.3, "buff": MED_SMALL_BUFF, "propagate_style_to_family": False, "preserve_tip_size_when_scaling": True, "normal_vector": OUT, "use_rectangular_stem": True, "rectangular_stem_width": 0.05, } def __init__(self, *args, **kwargs): points = list(map(self.pointify, args)) if len(args) == 1: args = (points[0] + UP + LEFT, points[0]) Line.__init__(self, *args, **kwargs) self.init_tip() if self.use_rectangular_stem and not hasattr(self, "rect"): self.add_rectangular_stem() self.init_colors() def init_tip(self): self.add_tip() def add_tip(self, add_at_end=True): tip = VMobject( close_new_points=True, mark_paths_closed=True, fill_color=self.color, fill_opacity=1, stroke_color=self.color, stroke_width=0, ) tip.add_at_end = add_at_end self.set_tip_points(tip, add_at_end, preserve_normal=False) self.add(tip) if not hasattr(self, 'tip'): self.tip = VGroup() self.tip.match_style(tip) self.tip.add(tip) return tip def add_rectangular_stem(self): self.rect = Rectangle( stroke_width=0, fill_color=self.tip.get_fill_color(), fill_opacity=self.tip.get_fill_opacity() ) self.add_to_back(self.rect) self.set_stroke(width=0) self.set_rectangular_stem_points() def set_rectangular_stem_points(self): start, end = self.get_start_and_end() tip_base_points = self.tip[0].get_anchors()[1:3] tip_base = center_of_mass(tip_base_points) tbp1, tbp2 = tip_base_points perp_vect = tbp2 - tbp1 tip_base_width = get_norm(perp_vect) if tip_base_width > 0: perp_vect /= tip_base_width width = min( self.rectangular_stem_width, self.max_stem_width_to_tip_width_ratio * tip_base_width, ) if hasattr(self, "second_tip"): start = center_of_mass( self.second_tip.get_anchors()[1:] ) self.rect.set_points_as_corners([ tip_base - perp_vect * width / 2, start - perp_vect * width / 2, start + perp_vect * width / 2, tip_base + perp_vect * width / 2, ]) self.stem = self.rect # Alternate name return self def set_tip_points( self, tip, add_at_end=True, tip_length=None, preserve_normal=True, ): if tip_length is None: tip_length = self.tip_length if preserve_normal: normal_vector = self.get_normal_vector() else: normal_vector = self.normal_vector line_length = get_norm(self.points[-1] - self.points[0]) tip_length = min( tip_length, self.max_tip_length_to_length_ratio * line_length ) indices = (-2, -1) if add_at_end else (1, 0) pre_end_point, end_point = [ self.get_anchors()[index] for index in indices ] vect = end_point - pre_end_point perp_vect = np.cross(vect, normal_vector) for v in vect, perp_vect: if get_norm(v) == 0: v[0] = 1 v *= tip_length / get_norm(v) ratio = self.tip_width_to_length_ratio tip.set_points_as_corners([ end_point, end_point - vect + perp_vect * ratio / 2, end_point - vect - perp_vect * ratio / 2, ]) return self def get_normal_vector(self): p0, p1, p2 = self.tip[0].get_anchors()[:3] result = np.cross(p2 - p1, p1 - p0) norm = get_norm(result) if norm == 0: return self.normal_vector else: return result / norm def reset_normal_vector(self): self.normal_vector = self.get_normal_vector() return self def get_end(self): if hasattr(self, "tip"): return self.tip[0].get_anchors()[0] else: return Line.get_end(self) def get_tip(self): return self.tip def put_start_and_end_on(self, *args, **kwargs): Line.put_start_and_end_on(self, *args, **kwargs) self.set_tip_points(self.tip[0], preserve_normal=False) self.set_rectangular_stem_points() return self def scale(self, scale_factor, **kwargs): Line.scale(self, scale_factor, **kwargs) if self.preserve_tip_size_when_scaling: for t in self.tip: self.set_tip_points(t, add_at_end=t.add_at_end) if self.use_rectangular_stem: self.set_rectangular_stem_points() return self def copy(self): return self.deepcopy()
def get_pis(self): return VGroup(Randolph(color=BLUE_E, mode="pondering"), Randolph(color=BLUE_D, mode="hooray"), Randolph(color=BLUE_C, mode="sassy"), Mortimer(color=GREY_BROWN, mode="thinking"))
def get_labels(self): return VGroup(*(self.numeral_labels.submobjects + self.alphabetical_labels.submobjects))
def add_spikes(self): layers = VGroup() radii = np.linspace( self.outer_radius, self.pupil_radius, self.n_spike_layers, endpoint=False, ) radii[:2] = radii[1::-1] # Swap first two if self.n_spike_layers > 2: radii[-1] = interpolate(radii[-1], self.pupil_radius, 0.25) for radius in radii: tip_angle = self.spike_angle half_base = radius * np.tan(tip_angle) triangle, right_half_triangle = [ Polygon( radius * UP, half_base * RIGHT, vertex3, fill_opacity=1, stroke_width=0, ) for vertex3 in ( half_base * LEFT, ORIGIN, ) ] left_half_triangle = right_half_triangle.copy() left_half_triangle.flip(UP, about_point=ORIGIN) n_spikes = self.n_spikes full_spikes = [ triangle.copy().rotate(-angle, about_point=ORIGIN) for angle in np.linspace(0, TAU, n_spikes, endpoint=False) ] index = (3 * n_spikes) // 4 if radius == radii[0]: layer = VGroup(*full_spikes) layer.rotate(-TAU / n_spikes / 2, about_point=ORIGIN) layer.brown_index = index else: half_spikes = [ right_half_triangle.copy(), left_half_triangle.copy().rotate( 90 * DEGREES, about_point=ORIGIN, ), right_half_triangle.copy().rotate( 90 * DEGREES, about_point=ORIGIN, ), left_half_triangle.copy() ] layer = VGroup(*it.chain( half_spikes[:1], full_spikes[1:index], half_spikes[1:3], full_spikes[index + 1:], half_spikes[3:], )) layer.brown_index = index + 1 layers.add(layer) # Color spikes blues = self.blue_spike_colors browns = self.brown_spike_colors for layer, blue, brown in zip(layers, blues, browns): index = layer.brown_index layer[:index].set_color(blue) layer[index:].set_color(brown) self.spike_layers = layers self.add(layers)
def get_lights(self): return VGroup(self.get_front_light(), self.get_rear_light())
def create_pi_creatures(self): """ Likely updated for subclasses """ return VGroup(self.create_pi_creature())
def get_basis_vectors(self, i_hat_color=X_COLOR, j_hat_color=Y_COLOR): return VGroup(*[ Vector( vect, color=color, stroke_width=self.basis_vector_stroke_width) for vect, color in [([1, 0], i_hat_color), ([0, 1], j_hat_color)] ])
def scroll_through_patrons(self): logo_box = Square(side_length=2.5) logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) total_width = FRAME_X_RADIUS - logo_box.get_right()[0] black_rect = Rectangle( fill_color=BLACK, fill_opacity=1, stroke_width=3, stroke_color=BLACK, width=FRAME_WIDTH, height=0.6 * FRAME_HEIGHT, ) black_rect.to_edge(UP, buff=0) line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) line.move_to(ORIGIN) thanks = TextMobject(self.thanks_words) thanks.scale(0.9) thanks.next_to(black_rect.get_bottom(), UP, SMALL_BUFF) thanks.set_color(YELLOW) underline = Line(LEFT, RIGHT) underline.match_width(thanks) underline.scale(1.1) underline.next_to(thanks, DOWN, SMALL_BUFF) thanks.add(underline) changed_patron_names = map( self.modify_patron_name, self.specific_patrons, ) patrons = VGroup(*map( TextMobject, changed_patron_names, )) patrons.scale(self.patron_scale_val) for patron in patrons: if patron.get_width() > self.max_patron_width: patron.set_width(self.max_patron_width) columns = VGroup(*[ VGroup(*patrons[i::self.n_patron_columns]) for i in range(self.n_patron_columns) ]) for column in columns: for n, name in enumerate(column): name.shift(n * self.name_y_spacing * DOWN) columns.arrange( RIGHT, buff=LARGE_BUFF, aligned_edge=UP, ) max_width = FRAME_WIDTH - 1 if columns.get_width() > max_width: columns.set_width(max_width) underline.match_width(columns) # thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) columns.next_to(underline, DOWN, buff=2) columns.generate_target() columns.target.to_edge(DOWN, buff=2) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 always_shift( columns, direction=normalize(vect), rate=(distance / wait_time) ) self.add(columns, black_rect, line, thanks) self.wait(wait_time)
class Axes(VGroup, CoordinateSystem): CONFIG = { "axis_config": { "include_tip": True, "numbers_to_exclude": [0], }, "x_axis_config": {}, "y_axis_config": { "line_to_number_direction": LEFT, }, "height": FRAME_HEIGHT - 2, "width": FRAME_WIDTH - 2, } def __init__(self, x_range=None, y_range=None, **kwargs): super().__init__(**kwargs) if x_range is not None: self.x_range[:len(x_range)] = x_range if y_range is not None: self.y_range[:len(y_range)] = y_range self.x_axis = self.create_axis( self.x_range, self.x_axis_config, self.width, ) self.y_axis = self.create_axis(self.y_range, self.y_axis_config, self.height) self.y_axis.rotate(90 * DEGREES, about_point=ORIGIN) # Add as a separate group in case various other # mobjects are added to self, as for example in # NumberPlane below self.axes = VGroup(self.x_axis, self.y_axis) self.add(*self.axes) self.center() def create_axis(self, range_terms, axis_config, length): new_config = merge_dicts_recursively(self.axis_config, axis_config) new_config["width"] = length axis = NumberLine(range_terms, **new_config) axis.shift(-axis.n2p(0)) return axis def coords_to_point(self, *coords): origin = self.x_axis.number_to_point(0) result = origin.copy() for axis, coord in zip(self.get_axes(), coords): result += (axis.number_to_point(coord) - origin) return result def point_to_coords(self, point): return tuple([axis.point_to_number(point) for axis in self.get_axes()]) def get_axes(self): return self.axes def get_all_ranges(self): return [self.x_range, self.y_range] def add_coordinate_labels(self, x_values=None, y_values=None, **kwargs): axes = self.get_axes() self.coordinate_labels = VGroup() for axis, values in zip(axes, [x_values, y_values]): labels = axis.add_numbers(values, **kwargs) self.coordinate_labels.add(labels) return self.coordinate_labels
def g_to_mobjects(self, g_element): mob = VGroup(*self.get_mobjects_from(g_element)) self.handle_transforms(g_element, mob) return mob.submobjects
class NumberLine(Line): CONFIG = { "color": GREY_B, "stroke_width": 2, # List of 2 or 3 elements, x_min, x_max, step_size "x_range": [-8, 8, 1], # How big is one one unit of this number line in terms of absolute spacial distance "unit_size": 1, "width": None, "include_ticks": True, "tick_size": 0.1, "longer_tick_multiple": 1.5, "tick_offset": 0, # Change name "numbers_with_elongated_ticks": [], "include_numbers": False, "line_to_number_direction": DOWN, "line_to_number_buff": MED_SMALL_BUFF, "include_tip": False, "tip_config": { "width": 0.25, "length": 0.25, }, "decimal_number_config": { "num_decimal_places": 0, "font_size": 36, }, "numbers_to_exclude": None } def __init__(self, x_range=None, **kwargs): digest_config(self, kwargs) if x_range is None: x_range = self.x_range if len(x_range) == 2: x_range = [*x_range, 1] x_min, x_max, x_step = x_range # A lot of old scenes pass in x_min or x_max explicitly, # so this is just here to keep those workin self.x_min = kwargs.get("x_min", x_min) self.x_max = kwargs.get("x_max", x_max) self.x_step = kwargs.get("x_step", x_step) super().__init__(self.x_min * RIGHT, self.x_max * RIGHT, **kwargs) if self.width: self.set_width(self.width) else: self.scale(self.unit_size) self.center() if self.include_tip: self.add_tip() self.tip.set_stroke( self.stroke_color, self.stroke_width, ) if self.include_ticks: self.add_ticks() if self.include_numbers: self.add_numbers(excluding=self.numbers_to_exclude) def get_tick_range(self): if self.include_tip: x_max = self.x_max else: x_max = self.x_max + self.x_step return np.arange(self.x_min, x_max, self.x_step) def add_ticks(self): ticks = VGroup() for x in self.get_tick_range(): size = self.tick_size if x in self.numbers_with_elongated_ticks: size *= self.longer_tick_multiple ticks.add(self.get_tick(x, size)) self.add(ticks) self.ticks = ticks def get_tick(self, x, size=None): if size is None: size = self.tick_size result = Line(size * DOWN, size * UP) result.rotate(self.get_angle()) result.move_to(self.number_to_point(x)) result.match_style(self) return result def get_tick_marks(self): return self.ticks def number_to_point(self, number): alpha = float(number - self.x_min) / (self.x_max - self.x_min) return interpolate(self.get_start(), self.get_end(), alpha) def point_to_number(self, point): start, end = self.get_start_and_end() unit_vect = normalize(end - start) proportion = fdiv( np.dot(point - start, unit_vect), np.dot(end - start, unit_vect), ) return interpolate(self.x_min, self.x_max, proportion) def n2p(self, number): """Abbreviation for number_to_point""" return self.number_to_point(number) def p2n(self, point): """Abbreviation for point_to_number""" return self.point_to_number(point) def get_unit_size(self): return (self.x_max - self.x_min) / self.get_length() def get_number_mobject(self, x, number_config=None, direction=None, buff=None): if number_config is None: number_config = {} number_config = merge_dicts_recursively( self.decimal_number_config, number_config ) if direction is None: direction = self.line_to_number_direction if buff is None: buff = self.line_to_number_buff num_mob = DecimalNumber(x, **number_config) num_mob.next_to( self.number_to_point(x), direction=direction, buff=buff ) if x < 0 and self.line_to_number_direction[0] == 0: # Align without the minus sign num_mob.shift(num_mob[0].get_width() * LEFT / 2) return num_mob def add_numbers(self, x_values=None, excluding=None, **kwargs): if x_values is None: x_values = self.get_tick_range() if excluding is not None: x_values = list_difference_update(x_values, excluding) self.numbers = VGroup() for x in x_values: self.numbers.add(self.get_number_mobject(x, **kwargs)) self.add(self.numbers) return self.numbers
def move_tip_to(self, point): mover = VGroup(self) if self.content is not None: mover.add(self.content) mover.shift(point - self.get_tip()) return self
def create_eyes(self, mode=None, thing_to_look_at=None): if mode is None: mode = self.mode if thing_to_look_at is None: thing_to_look_at = self.thing_to_look_at self.thing_to_look_at = thing_to_look_at self.mode = mode looking_direction = None pi = PiCreature(mode=mode) eyes = VGroup(pi.eyes, pi.pupils) if self.submobjects: eyes.match_height(self) eyes.move_to(self, DOWN) looking_direction = self[1].get_center() - self[0].get_center() else: eyes.set_height(self.height) eyes.move_to(self.body.get_top(), DOWN) height = eyes.get_height() if thing_to_look_at is not None: pi.look_at(thing_to_look_at) elif looking_direction is not None: pi.look(looking_direction) eyes.set_height(height) return eyes
def get_secant_slope_group( self, x, graph, dx=None, dx_line_color=None, df_line_color=None, dx_label=None, df_label=None, include_secant_line=True, secant_line_color=None, secant_line_length=10, ): """ Resulting group is of the form VGroup( dx_line, df_line, dx_label, (if applicable) df_label, (if applicable) secant_line, (if applicable) ) with attributes of those names. """ kwargs = locals() kwargs.pop("self") group = VGroup() group.kwargs = kwargs dx = dx or float(self.x_max - self.x_min) / 10 dx_line_color = dx_line_color or self.default_input_color df_line_color = df_line_color or graph.get_color() p1 = self.input_to_graph_point(x, graph) p2 = self.input_to_graph_point(x + dx, graph) interim_point = p2[0] * RIGHT + p1[1] * UP group.dx_line = Line(p1, interim_point, color=dx_line_color) group.df_line = Line(interim_point, p2, color=df_line_color) group.add(group.dx_line, group.df_line) labels = VGroup() if dx_label is not None: group.dx_label = TexMobject(dx_label) labels.add(group.dx_label) group.add(group.dx_label) if df_label is not None: group.df_label = TexMobject(df_label) labels.add(group.df_label) group.add(group.df_label) if len(labels) > 0: max_width = 0.8 * group.dx_line.get_width() max_height = 0.8 * group.df_line.get_height() if labels.get_width() > max_width: labels.set_width(max_width) if labels.get_height() > max_height: labels.set_height(max_height) if dx_label is not None: group.dx_label.next_to(group.dx_line, np.sign(dx) * DOWN, buff=group.dx_label.get_height() / 2) group.dx_label.set_color(group.dx_line.get_color()) if df_label is not None: group.df_label.next_to(group.df_line, np.sign(dx) * RIGHT, buff=group.df_label.get_height() / 2) group.df_label.set_color(group.df_line.get_color()) if include_secant_line: secant_line_color = secant_line_color or self.default_derivative_color group.secant_line = Line(p1, p2, color=secant_line_color) group.secant_line.scale_in_place(secant_line_length / group.secant_line.get_length()) group.add(group.secant_line) return group
def get_riemann_rectangles(self, graph, x_min=None, x_max=None, dx=0.1, input_sample_type="left", stroke_width=1, stroke_color=BLACK, fill_opacity=1, start_color=None, end_color=None, show_signed_area=True, width_scale_factor=1.001): """ This method returns the VGroup() of the Riemann Rectangles for a particular curve. Parameters ---------- graph (ParametricFunction) The graph whose area needs to be approximated by the Riemann Rectangles. x_min Union[int,float] The lower bound from which to start adding rectangles x_max Union[int,float] The upper bound where the rectangles stop. dx Union[int,float] The smallest change in x-values that is considered significant. input_sample_type str Can be any of "left", "right" or "center stroke_width : Union[int, float] The stroke_width of the border of the rectangles. stroke_color : str The string of hex colour of the rectangle's border. fill_opacity Union[int, float] The opacity of the rectangles. start_color : str, The hex starting colour for the rectangles, this will, if end_color is a different colour, make a nice gradient. end_color : str, The hex ending colour for the rectangles, this will, if start_color is a different colour, make a nice gradient. show_signed_area : bool (True) Whether or not to indicate -ve area if curve dips below x-axis. width_scale_factor : Union[int, float] How much the width of the rectangles are scaled by when transforming. Returns ------- VGroup A VGroup containing the Riemann Rectangles. """ x_min = x_min if x_min is not None else self.x_min x_max = x_max if x_max is not None else self.x_max if start_color is None: start_color = self.default_riemann_start_color if end_color is None: end_color = self.default_riemann_end_color rectangles = VGroup() x_range = np.arange(x_min, x_max, dx) colors = color_gradient([start_color, end_color], len(x_range)) for x, color in zip(x_range, colors): if input_sample_type == "left": sample_input = x elif input_sample_type == "right": sample_input = x + dx elif input_sample_type == "center": sample_input = x + 0.5 * dx else: raise Exception("Invalid input sample type") graph_point = self.input_to_graph_point(sample_input, graph) points = VGroup(*list( map(VectorizedPoint, [ self.coords_to_point(x, 0), self.coords_to_point(x + width_scale_factor * dx, 0), graph_point ]))) rect = Rectangle() rect.replace(points, stretch=True) if graph_point[1] < self.graph_origin[1] and show_signed_area: fill_color = invert_color(color) else: fill_color = color rect.set_fill(fill_color, opacity=fill_opacity) rect.set_stroke(stroke_color, width=stroke_width) rectangles.add(rect) return rectangles
def get_columns(self): return VGroup(*[ VGroup(*[row[i] for row in self.mob_matrix]) for i in range(len(self.mob_matrix[0])) ])
def get_secant_slope_group( self, x, graph, dx=None, dx_line_color=None, df_line_color=None, dx_label=None, df_label=None, include_secant_line=True, secant_line_color=None, secant_line_length=10, ): """ This method returns a VGroup of (two lines representing dx and df, the labels for dx and df, and the Secant to the Graph/curve at a particular x value. Parameters ---------- x (Union[float, int]) The x value at which the secant enters, and intersects the graph for the first time. graph (ParametricFunction) The curve/graph for which the secant must be found. dx (Union[float, int]) The change in x after which the secant exits. dx_line_color (str) The line color for the line that indicates the change in x. df_line_color (str) The line color for the line that indicates the change in y. dx_label (str) The label to be provided for the change in x. df_label (str) The label to be provided for the change in y. include_secant_line (bool=True) Whether or not to include the secant line in the graph, or just have the df and dx lines and labels. secant_line_color (str) The color of the secant line. secant_line_length (Union[float,int=10]) How long the secant line should be. Returns: -------- VGroup Resulting group is of the form VGroup( dx_line, df_line, dx_label, (if applicable) df_label, (if applicable) secant_line, (if applicable) ) with attributes of those names. """ kwargs = locals() kwargs.pop("self") group = VGroup() group.kwargs = kwargs dx = dx or float(self.x_max - self.x_min) / 10 dx_line_color = dx_line_color or self.default_input_color df_line_color = df_line_color or graph.get_color() p1 = self.input_to_graph_point(x, graph) p2 = self.input_to_graph_point(x + dx, graph) interim_point = p2[0] * RIGHT + p1[1] * UP group.dx_line = Line(p1, interim_point, color=dx_line_color) group.df_line = Line(interim_point, p2, color=df_line_color) group.add(group.dx_line, group.df_line) labels = VGroup() if dx_label is not None: group.dx_label = TexMobject(dx_label) labels.add(group.dx_label) group.add(group.dx_label) if df_label is not None: group.df_label = TexMobject(df_label) labels.add(group.df_label) group.add(group.df_label) if len(labels) > 0: max_width = 0.8 * group.dx_line.get_width() max_height = 0.8 * group.df_line.get_height() if labels.get_width() > max_width: labels.set_width(max_width) if labels.get_height() > max_height: labels.set_height(max_height) if dx_label is not None: group.dx_label.next_to(group.dx_line, np.sign(dx) * DOWN, buff=group.dx_label.get_height() / 2) group.dx_label.set_color(group.dx_line.get_color()) if df_label is not None: group.df_label.next_to(group.df_line, np.sign(dx) * RIGHT, buff=group.df_label.get_height() / 2) group.df_label.set_color(group.df_line.get_color()) if include_secant_line: secant_line_color = secant_line_color or self.default_derivative_color group.secant_line = Line(p1, p2, color=secant_line_color) group.secant_line.scale_in_place(secant_line_length / group.secant_line.get_length()) group.add(group.secant_line) return group
def get_number_mobjects(self, *numbers, **kwargs): if len(numbers) == 0: numbers = self.default_numbers_to_display() return VGroup( *[self.get_number_mobject(number, **kwargs) for number in numbers])
def get_animation_integral_bounds_change(self, graph, new_t_min, new_t_max, fade_close_to_origin=True, run_time=1.0): """ This method requires a lot of prerequisites: self.area must be defined from self.get_area() self.left_v_line and self.right_v_line must be defined from self.get_v_line self.left_T_label_group and self.right_T_label_group must be defined from self.add_T_label This method will returna VGroup of new mobjects for each of those, when provided the graph/curve, the new t_min and t_max, the run_time and a bool stating whether or not to fade when close to the origin. Parameters ---------- graph (ParametricFunction) The graph for which this must be done. new_t_min (Union[int,float]) The new lower bound. new_t_max (Union[int,float]) The new upper bound. fade_close_to_origin (bool=True) Whether or not to fade when close to the origin. run_time (Union[int,float=1.0]) The run_time of the animation of this change. """ curr_t_min = self.x_axis.point_to_number(self.area.get_left()) curr_t_max = self.x_axis.point_to_number(self.area.get_right()) if new_t_min is None: new_t_min = curr_t_min if new_t_max is None: new_t_max = curr_t_max group = VGroup(self.area) group.add(self.left_v_line) group.add(self.left_T_label_group) group.add(self.right_v_line) group.add(self.right_T_label_group) def update_group(group, alpha): area, left_v_line, left_T_label, right_v_line, right_T_label = group t_min = interpolate(curr_t_min, new_t_min, alpha) t_max = interpolate(curr_t_max, new_t_max, alpha) new_area = self.get_area(graph, t_min, t_max) new_left_v_line = self.get_vertical_line_to_graph(t_min, graph) new_left_v_line.set_color(left_v_line.get_color()) left_T_label.move_to(new_left_v_line.get_bottom(), UP) new_right_v_line = self.get_vertical_line_to_graph(t_max, graph) new_right_v_line.set_color(right_v_line.get_color()) right_T_label.move_to(new_right_v_line.get_bottom(), UP) # Fade close to 0 if fade_close_to_origin: if len(left_T_label) > 0: left_T_label[0].set_fill(opacity=min(1, np.abs(t_min))) if len(right_T_label) > 0: right_T_label[0].set_fill(opacity=min(1, np.abs(t_max))) Transform(area, new_area).update(1) Transform(left_v_line, new_left_v_line).update(1) Transform(right_v_line, new_right_v_line).update(1) return group return UpdateFromAlphaFunc(group, update_group, run_time=run_time)
def get_tick_marks(self): return VGroup( *self.tick_marks, *self.big_tick_marks, )
def setup(self): self.pi_creatures = VGroup(*self.create_pi_creatures()) self.pi_creature = self.get_primary_pi_creature() if self.pi_creatures_start_on_screen: self.add(*self.pi_creatures)
def get_piece_movement(self, pieces): start = VGroup(*pieces) target = VGroup(*[mob.target for mob in pieces]) if self.leave_ghost_vectors: self.add(start.copy().fade(0.7)) return Transform(start, target, lag_ratio=0)
def get_on_screen_pi_creatures(self): mobjects = self.get_mobject_family_members() return VGroup( *[pi for pi in self.get_pi_creatures() if pi in mobjects])
def get_tires(self): return VGroup(self[1][0], self[1][1])