def get_det_text( matrix: Matrix, determinant: int | str | None = None, background_rect: bool = False, initial_scale_factor: int = 2 ) -> VGroup: parens = Tex("(", ")") 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 = TexText("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 = Tex("=") eq.next_to(r_paren, RIGHT, buff=0.1) result = Tex(str(determinant)) result.next_to(eq, RIGHT, buff=0.2) det_text.add(eq, result) return det_text
def get_dashed_rectangle(self, width, height): h1 = [ORIGIN, UP * height] w1 = [UP * height, UP * height + RIGHT * width] h2 = [UP * height + RIGHT * width, RIGHT * width] w2 = [RIGHT * width, ORIGIN] alpha = width / height divs = self.num_dashes n_h = int(divs / (2 * (alpha + 1))) n_w = int(alpha * n_h) dashedrectangle = VGroup() for n, l in zip([n_w, n_h], [[w1, w2], [h1, h2]]): for side in l: line = VMobject() line.set_points_as_corners(side) dashedrectangle.add( DashedVMobject( line, num_dashes=n, positive_space_ratio=self.positive_space_ratio, ).set_color(self.color).set_style(**self.line_config)) return [ dashedrectangle[0], dashedrectangle[3], dashedrectangle[1], dashedrectangle[2] ]
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 __init__(self, point, color=YELLOW, **kwargs): digest_config(self, kwargs) lines = VGroup() for angle in np.arange(0, TAU, TAU / self.num_lines): line = Line(ORIGIN, self.line_length * RIGHT) line.shift((self.flash_radius - self.line_length) * RIGHT) line.rotate(angle, about_point=ORIGIN) lines.add(line) lines.move_to(point) lines.set_color(color) lines.set_stroke(width=3) line_anims = [ ShowCreationThenDestruction( line, rate_func=squish_rate_func(smooth, 0, 0.5) ) for line in lines ] fade_anims = [ UpdateFromAlphaFunc( line, lambda m, a: m.set_stroke( width=self.line_stroke_width * (1 - a) ), rate_func=squish_rate_func(smooth, 0, 0.75) ) for line in lines ] AnimationGroup.__init__( self, *line_anims + fade_anims, **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 __init__(self, *text, line_spacing=-1, alignment=None, **config): self.line_spacing = line_spacing self.alignment = alignment VGroup.__init__(self, **config) lines_str = "\n".join(list(text)) self.lines_text = Text(lines_str, line_spacing=line_spacing, **config) lines_str_list = lines_str.split("\n") self.chars = self.gen_chars(lines_str_list) chars_lines_text_list = VGroup() char_index_counter = 0 for line_index in range(lines_str_list.__len__()): chars_lines_text_list.add( self.lines_text[char_index_counter:char_index_counter + lines_str_list[line_index].__len__() + 1]) char_index_counter += lines_str_list[line_index].__len__() + 1 self.lines = [] self.lines.append([]) for line_no in range(chars_lines_text_list.__len__()): self.lines[0].append(chars_lines_text_list[line_no]) self.lines_initial_positions = [] for line_no in range(self.lines[0].__len__()): self.lines_initial_positions.append( self.lines[0][line_no].get_center()) self.lines.append([]) self.lines[1].extend( [self.alignment for _ in range(chars_lines_text_list.__len__())]) VGroup.__init__( self, *[self.lines[0][i] for i in range(self.lines[0].__len__())], **config) self.move_to(np.array([0, 0, 0])) if self.alignment: self.set_all_lines_alignments(self.alignment)
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)
class Shadow_2d(VGroup): CONFIG = { 'shadow_color': DARK_GRAY, 'shadow_opacity': 0.6, 'blur_width': 0.25, 'layer_num': 40, 'scale_factor': 1, 'shadow_out': True, 'show_basic_shape': True, 'plot_depth':-1, 'rate_func': lambda t: t ** 0.5, } def __init__(self, mob_or_points, **kwargs): VGroup.__init__(self, **kwargs) if type(mob_or_points) == list: self.shape = Polygon(*mob_or_points, stroke_width=0, plot_depth=-1) else: self.shape = mob_or_points.set_stroke(width=0) self.shape.set_fill(color=self.shadow_color, opacity=self.shadow_opacity * (1 if self.show_basic_shape else 0)).scale(self.scale_factor) self.blur_outline = VGroup() s = (self.shape.get_height() + self.shape.get_width())/2 if self.blur_width > 1e-4: for i in range(self.layer_num): layer_i = self.shape.copy().set_stroke(color=self.shadow_color, width=100 * self.blur_width/self.layer_num, opacity=self.shadow_opacity * (1-self.rate_func(i/self.layer_num))).\ set_fill(opacity=0).scale((s + (1 if self.shadow_out else -1) * self.blur_width/self.layer_num * (i+0.5))/ s).set_plot_depth(-2) self.blur_outline.add(layer_i) self.add(self.shape, self.blur_outline)
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)
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 = Tex(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)
def add_label(self): self.id_to_label = [ "0", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og" ] self.label_to_id = {} for i, label in enumerate(self.id_to_label): self.label_to_id[label] = i labels = VGroup() for i in range(0, 119): label = TextMobject(self.id_to_label[i], color=BLACK, background_stroke_width=0) label.scale(0.5).move_to(self[0][i][1].get_center()).set_shade_in_3d(z_index_as_group=True).shift(OUT * 1e-3) labels.add(label) labels[0].set_fill(opacity=0) for i in [43, 61, *list(range(84, 119))]: labels[i].set_color(RED) self.add(labels) return self
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 = Tex(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_coordinate_labels(self, *numbers): # TODO: Should merge this with the code from NumberPlane.get_coordinate_labels result = VGroup() if len(numbers) == 0: numbers = list(range(-int(self.x_radius), int(self.x_radius) + 1)) numbers += [ complex(0, y) for y in range(-int(self.y_radius), int(self.y_radius) + 1) if y != 0 ] for number in numbers: # if number == complex(0, 0): # continue point = self.number_to_point(number) num_str = str(number).replace("j", "i") if num_str.startswith("0"): num_str = "0" elif num_str in ["1i", "-1i"]: num_str = num_str.replace("1", "") num_mob = TexMobject(num_str) num_mob.add_background_rectangle() num_mob.set_height(self.written_coordinate_height) num_mob.next_to(point, DOWN + LEFT, SMALL_BUFF) result.add(num_mob) self.coordinate_labels = result return result
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 add_numbers(self, *numbers, excluding=None, font_size=24, x_values=None, **kwargs): if x_values is None: if numbers is not None: if len(numbers) == 1 and isinstance(numbers[0], list): x_values = (numbers[0]) if len(numbers) == 1 and numbers[0] is None: x_values = self.get_tick_range() elif len(numbers) > 0 and not isinstance(numbers[0], list): x_values = list(numbers) else: x_values =self.get_tick_range() else: x_values = self.get_tick_range() kwargs["font_size"] = font_size numbers = VGroup() for x in x_values: if self.numbers_to_exclude is not None and x in self.numbers_to_exclude: continue if excluding is not None and x in excluding: continue numbers.add(self.get_number_mobject(x, **kwargs)) self.add(numbers) self.numbers = numbers return numbers '''
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
def get_words(self, *index): pre_index = [] pos_index = [] row_parameter = max([self[i].get_height() for i in range(len(self))]) word_parameter = max([self[i].get_width() for i in range(len(self))]) #print(word_parameter) start = 0 for i in range(len(self) - 1): distance_letter_between = abs(self[i + 1].get_x() - self[i].get_x()) distance_row_between = abs(self[i + 1].get_y() - self[i].get_y()) if distance_letter_between > word_parameter: pre_index.append(start) pos_index.append(i + 1) start = i + 1 #print(pre_index) pre_index.append(pos_index[-1] + 1) pos_index.append(len(self) - 1) all_words = VGroup( *[self[pre:pos + 1] for pre, pos in zip(pre_index, pos_index)]) words = VGroup() for word in index: words.add(all_words[word]) if len(index) == 0: return all_words else: return words
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 create_path(self): path = VGroup() self.get_path_xyz() if len(self.path_xyz) > 1: for i in range(len(self.path_xyz) - 1): if type(self.trail_color) == str: path.add( Line(self.path_xyz[i], self.path_xyz[i + 1], stroke_color=self.trail_color, stroke_opacity=self.rate_func(i / len(self.path_xyz)), plot_depth=self.rate_func(2 - i / len(self.path_xyz)), stroke_width=self.max_width * self.rate_func(i / len(self.path_xyz)))) else: path.add( Line(self.path_xyz[i], self.path_xyz[i + 1], stroke_color=self.colors[i], stroke_opacity=self.rate_func(i / len(self.path_xyz)), plot_depth=self.rate_func(2 - i / len(self.path_xyz)), stroke_width=self.max_width * self.rate_func(i / len(self.path_xyz)))) # print('i = %d' % i) # # print(self.path_xyz) # print(self.color) # print(self.rate_func(i/len(self.path_xyz))) # print(self.max_width*self.rate_func(i/len(self.path_xyz))) return path
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 DoubleArrow(Arrow): def init_tip(self): self.tip = VGroup() for b in True, False: t = self.add_tip(add_at_end=b) t.add_at_end = b self.tip.add(t) self.tip.match_style(self.tip[0])
class ComplexPlane(NumberPlane): CONFIG = { "color": BLUE, "line_frequency": 1, } def number_to_point(self, number: complex | float) -> np.ndarray: number = complex(number) return self.coords_to_point(number.real, number.imag) def n2p(self, number: complex | float) -> np.ndarray: return self.number_to_point(number) def point_to_number(self, point: np.ndarray) -> complex: x, y = self.point_to_coords(point) return complex(x, y) def p2n(self, point: np.ndarray) -> complex: return self.point_to_number(point) def get_default_coordinate_values(self, skip_first: bool = True ) -> list[complex]: x_numbers = self.get_x_axis().get_tick_range()[1:] y_numbers = self.get_y_axis().get_tick_range()[1:] y_numbers = [complex(0, y) for y in y_numbers if y != 0] return [*x_numbers, *y_numbers] def add_coordinate_labels(self, numbers: list[complex] | None = None, skip_first: bool = True, **kwargs): if numbers is None: numbers = self.get_default_coordinate_values(skip_first) self.coordinate_labels = VGroup() for number in numbers: z = complex(number) if abs(z.imag) > abs(z.real): axis = self.get_y_axis() value = z.imag kwargs["unit"] = "i" else: axis = self.get_x_axis() value = z.real number_mob = axis.get_number_mobject(value, **kwargs) # For i and -i, remove the "1" if z.imag == 1: number_mob.remove(number_mob[0]) if z.imag == -1: number_mob.remove(number_mob[1]) number_mob[0].next_to(number_mob[1], LEFT, buff=number_mob[0].get_width() / 4) self.coordinate_labels.add(number_mob) self.add(self.coordinate_labels) return self
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 get_submobject_index_labels(mobject, label_height=0.15): labels = VGroup() for n, submob in enumerate(mobject): label = Integer(n) label.set_height(label_height) label.move_to(submob) label.set_stroke(BLACK, 5, background=True) labels.add(label) return labels
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 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 add_ticks(self) -> None: ticks = VGroup() for x in self.get_tick_range(): size = self.tick_size if np.isclose(self.numbers_with_elongated_ticks, x).any(): size *= self.longer_tick_multiple ticks.add(self.get_tick(x, size)) self.add(ticks) self.ticks = ticks
def get_parts_from_keys(mobject, keys): if isinstance(keys, str): keys = [keys] result = VGroup() for key in keys: if not isinstance(key, str): raise TypeError(key) result.add(*mobject.get_parts_by_string(key)) return result
def gen_chars(self, lines_str_list): char_index_counter = 0 chars = VGroup() for line_no in range(lines_str_list.__len__()): chars.add(VGroup()) chars[line_no].add( *self.lines_text.chars[char_index_counter:char_index_counter + lines_str_list[line_no].__len__() + 1]) char_index_counter += lines_str_list[line_no].__len__() + 1 return chars
def index_labels(mobject: Mobject | np.ndarray, label_height: float = 0.15) -> VGroup: labels = VGroup() for n, submob in enumerate(mobject): label = Integer(n) label.set_height(label_height) label.move_to(submob) label.set_stroke(BLACK, 5, background=True) labels.add(label) return labels
def create_lines(self): lines = VGroup() for angle in np.arange(0, TAU, TAU / self.num_lines): line = Line(ORIGIN, self.line_length * RIGHT) line.shift((self.flash_radius - self.line_length) * RIGHT) line.rotate(angle, about_point=ORIGIN) lines.add(line) lines.set_stroke(color=self.color, width=self.line_stroke_width) lines.add_updater(lambda l: l.move_to(self.point)) return lines
def create_boxes(self): pos = MyBoxes(resolution=(9, 18), bottom_size=self.bottom_size, gap=0.15, box_height=0.4) boxes = VGroup() for i in range(0, 119): box = MyBox(pos=pos[self.id_to_pos[i]].get_center(), bottom_size=self.bottom_size, box_height=self.box_height, fill_color=self.fill_color) box.reset_color() boxes.add(box) boxes[0].set_fill(opacity=0) self.add(boxes)
def bar(self, x, height, width=0.8, align='center', color=None, fill_opacity=None, stroke_width=None, **kwargs): if len(x) != len(height): raise ValueError("'x' and 'height' should be equal sized") if align == 'center': align = ORIGIN elif align == 'edge' or align == 'right': align = RIGHT elif align == 'left': align = LEFT if color is None: color = it.cycle([next(self.default_graph_colors_cycle)]) else: if is_sequence(color): color = it.cycle(color) else: color = it.cycle([color]) if fill_opacity is None: fill_opacity = self.default_bar_opacity if stroke_width is None: stroke_width = self.default_bar_stroke_width bar_sequence = VGroup() p_x0, p_y0, p_z0 = self.coords_to_point(0, 0) for i in range(len(x)): if is_sequence(width): bar_width = width[i] else: bar_width = width p_x1, p_y1, p_z1 = self.coords_to_point(x[i], height[i]) p_x2, p_y2, p_z2 = self.coords_to_point(bar_width, 0) bar_height = p_y1 - p_y0 bar_width = p_x2 - p_x0 bar_center = (p_x1, p_y0, p_z1) bar = Rectangle( height=bar_height, width=bar_width, color=next(color), fill_opacity=fill_opacity, stroke_width=stroke_width, **kwargs, ).next_to(bar_center, UP + align, buff=0) bar_sequence.add(bar) if hasattr(self, "bar_sequences") is False: self.bar_sequences = VGroup() self.bar_sequences.add(bar_sequence) self.add(self.bar_sequences) return bar_sequence
class Axes(VGroup, CoordinateSystem): CONFIG = { "axis_config": { "include_tip": True, }, "x_axis_config": {}, "y_axis_config": { "line_to_number_direction": LEFT, }, } def __init__(self, x_range=None, y_range=None, **kwargs): VGroup.__init__(self, **kwargs) self.x_axis = self.create_axis( x_range or self.x_range, self.x_axis_config, self.width, ) self.y_axis = self.create_axis(y_range or 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 = np.array(origin) 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 add_coordinate_labels(self, x_values=None, y_values=None): axes = self.get_axes() self.coordinate_labels = VGroup() for axis, values in zip(axes, [x_values, y_values]): numbers = axis.add_numbers(values, excluding=[0]) self.coordinate_labels.add(numbers) return self.coordinate_labels
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 get_tips(self): """ Returns a VGroup (collection of VMobjects) containing the TipableVMObject instance's tips. """ result = VGroup() if hasattr(self, "tip"): result.add(self.tip) if hasattr(self, "start_tip"): result.add(self.start_tip) return result
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 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_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_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_number_design(self, value, symbol): num = int(value) n_rows = { 2: 2, 3: 3, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3, 9: 4, 10: 4, }[num] n_cols = 1 if num in [2, 3] else 2 insertion_indices = { 5: [0], 7: [0], 8: [0, 1], 9: [1], 10: [0, 2], }.get(num, []) top = self.get_top() + symbol.get_height() * DOWN bottom = self.get_bottom() + symbol.get_height() * UP column_points = [ interpolate(top, bottom, alpha) for alpha in np.linspace(0, 1, n_rows) ] design = VGroup(*[ symbol.copy().move_to(point) for point in column_points ]) if n_cols == 2: space = 0.2 * self.get_width() column_copy = design.copy().shift(space * RIGHT) design.shift(space * LEFT) design.add(*column_copy) design.add(*[ symbol.copy().move_to( center_of_mass(column_points[i:i + 2]) ) for i in insertion_indices ]) for symbol in design: if symbol.get_center()[1] < self.get_center()[1]: symbol.rotate_in_place(np.pi) return design
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 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, 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_bars(self, values): buff = float(self.width) / (2 * len(values) + 1) bars = VGroup() for i, value in enumerate(values): bar = Rectangle( height=(value / self.max_value) * self.height, width=buff, stroke_width=self.bar_stroke_width, fill_opacity=self.bar_fill_opacity, ) bar.move_to((2 * i + 1) * buff * RIGHT, DOWN + LEFT) bars.add(bar) bars.set_color_by_gradient(*self.bar_colors) bar_labels = VGroup() for bar, name in zip(bars, self.bar_names): label = TexMobject(str(name)) label.scale(self.bar_label_scale_val) label.next_to(bar, DOWN, SMALL_BUFF) bar_labels.add(label) self.add(bars, bar_labels) self.bars = bars self.bar_labels = bar_labels
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 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
class CountingScene(Scene): CONFIG = { "digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], "counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP, "count_dot_starting_radius": 0.5, "dot_configuration_height": 2, "ones_configuration_location": UP + 2 * RIGHT, "num_scale_factor": 2, "num_start_location": 2 * DOWN, } 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_template_configuration(self, place): # This should probably be replaced for non-base-10 counting scenes down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN result = [] for down_right_steps in range(5): for left_steps in range(down_right_steps): result.append( down_right_steps * down_right + left_steps * LEFT ) return reversed(result[:self.get_place_max(place)]) 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 add_configuration(self): new_template = self.get_dot_template(len(self.dot_templates)) new_template.move_to(self.ones_configuration_location) left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT new_template.shift( left_vect * len(self.dot_templates) ) self.dot_templates.append(new_template) self.dot_template_iterators.append( it.cycle(new_template) ) self.curr_configurations.append(VGroup()) def count(self, max_val, run_time_per_anim=1): for x in range(max_val): self.increment(run_time_per_anim) def increment(self, run_time_per_anim=1): moving_dot = Dot( self.counting_dot_starting_position, radius=self.count_dot_starting_radius, color=self.digit_place_colors[0], ) moving_dot.generate_target() moving_dot.set_fill(opacity=0) kwargs = { "run_time": run_time_per_anim } continue_rolling_over = True first_move = True place = 0 while continue_rolling_over: added_anims = [] if first_move: added_anims += self.get_digit_increment_animations() first_move = False moving_dot.target.replace( next(self.dot_template_iterators[place]) ) self.play(MoveToTarget(moving_dot), *added_anims, **kwargs) self.curr_configurations[place].add(moving_dot) if len(self.curr_configurations[place].split()) == self.get_place_max(place): full_configuration = self.curr_configurations[place] self.curr_configurations[place] = VGroup() place += 1 center = full_configuration.get_center_of_mass() radius = 0.6 * max( full_configuration.get_width(), full_configuration.get_height(), ) circle = Circle( radius=radius, stroke_width=0, fill_color=self.digit_place_colors[place], fill_opacity=0.5, ) circle.move_to(center) moving_dot = VGroup(circle, full_configuration) moving_dot.generate_target() moving_dot[0].set_fill(opacity=0) else: continue_rolling_over = False def get_digit_increment_animations(self): result = [] self.number += 1 is_next_digit = self.is_next_digit() if is_next_digit: self.max_place += 1 new_number_mob = self.get_number_mob(self.number) new_number_mob.move_to(self.number_mob, RIGHT) if is_next_digit: self.add_configuration() place = len(new_number_mob.split()) - 1 result.append(FadeIn(self.dot_templates[place])) arrow = Arrow( new_number_mob[place].get_top(), self.dot_templates[place].get_bottom(), color=self.digit_place_colors[place] ) self.arrows.add(arrow) result.append(ShowCreation(arrow)) result.append(Transform( self.number_mob, new_number_mob, lag_ratio=0.5 )) return result 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 is_next_digit(self): return False def get_place_num(self, num, place): return 0 def get_place_max(self, place): return 0
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 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)