コード例 #1
0
ファイル: transform.py プロジェクト: coallaoh/manim
 def update_config(self, **kwargs):
     Animation.update_config(self, **kwargs)
     if "path_arc" in kwargs:
         self.path_func = path_along_arc(
             kwargs["path_arc"],
             kwargs.get("path_arc_axis", OUT)
         )
コード例 #2
0
 def __init__(self, mobject, mode="linear", **kwargs):
     if not isinstance(mobject, PiCreatureClass):
         raise Exception("FlashThroughClass mobject must be a PiCreatureClass")
     digest_config(self, kwargs)
     self.indices = list(range(mobject.height * mobject.width))
     if mode == "random":
         np.random.shuffle(self.indices)
     Animation.__init__(self, mobject, **kwargs)
コード例 #3
0
ファイル: transform.py プロジェクト: yuzhang49/manim
    def __init__(self, mobject, target_mobject, **kwargs):
        # Copy target_mobject so as to not mess with caller
        self.original_target_mobject = target_mobject
        target_mobject = target_mobject.copy()
        mobject.align_data(target_mobject)
        self.target_mobject = target_mobject
        digest_config(self, kwargs)
        self.init_path_func()

        Animation.__init__(self, mobject, **kwargs)
        self.name += "To" + str(target_mobject)
コード例 #4
0
ファイル: composition.py プロジェクト: yuzhang49/manim
 def __init__(self, AnimationClass, mobjects, **kwargs):
     full_kwargs = AnimationClass.CONFIG
     full_kwargs.update(kwargs)
     full_kwargs["mobject"] = Mobject(*[
         mob.get_point_mobject()
         for mob in mobjects
     ])
     self.centers_container = AnimationClass(**full_kwargs)
     full_kwargs.pop("mobject")
     Animation.__init__(self, Mobject(*mobjects), **full_kwargs)
     self.name = str(self) + AnimationClass.__name__
コード例 #5
0
 def __init__(self, clock, **kwargs):
     digest_config(self, kwargs)
     assert (isinstance(clock, Clock))
     rot_kwargs = {"axis": OUT, "about_point": clock.get_center()}
     hour_radians = -self.hours_passed * 2 * np.pi / 12
     self.hour_rotation = Rotating(clock.hour_hand,
                                   radians=hour_radians,
                                   **rot_kwargs)
     self.minute_rotation = Rotating(clock.minute_hand,
                                     radians=12 * hour_radians,
                                     **rot_kwargs)
     Animation.__init__(self, clock, **kwargs)
コード例 #6
0
    def __init__(self, *sub_anims, **kwargs):
        sub_anims = [x for x in sub_anims if not(x.empty)]
        digest_config(self, locals())
        self.update_config(**kwargs)  # Handles propagation to self.sub_anims

        if len(sub_anims) == 0:
            self.empty = True
            self.run_time = 0
        else:
            self.run_time = max([a.run_time for a in sub_anims])
        everything = Mobject(*[a.mobject for a in sub_anims])
        Animation.__init__(self, everything, **kwargs)
コード例 #7
0
ファイル: composition.py プロジェクト: eulertour/manim
 def __init__(self, *animations, **kwargs):
     if not hasattr(self, "args"):
         self.args = serialize_args(animations)
     if not hasattr(self, "config"):
         self.config = serialize_config({
             **kwargs,
         })
     digest_config(self, kwargs)
     self.animations = animations
     if self.group is None:
         self.group = Group(*remove_list_redundancies(
             [anim.mobject for anim in animations]))
     self.init_run_time()
     Animation.__init__(self, self.group, **kwargs)
コード例 #8
0
    def get_logo_animations(self, logo):
        layers = logo.spike_layers
        for i, layer in enumerate(layers):
            random.shuffle(layer.submobjects)
            for spike in layer:
                spike.save_state()
                spike.scale(0.5)
                spike.apply_complex_function(np.log)
                spike.rotate(-90 * DEGREES, about_point=ORIGIN)
                spike.set_fill(opacity=0)
            layer.rotate(i * PI / 5)

        logo.iris_background.save_state()
        logo.iris_background.scale(0.25)
        logo.iris_background.fade(1)

        return [
            Restore(
                logo.iris_background,
                run_time=3,
            ),
            AnimationGroup(*[
                LaggedStartMap(
                    Restore,
                    layer,
                    run_time=3,
                    path_arc=180 * DEGREES,
                    # rate_func=squish_rate_func(smooth, a / 3.0, (a + 1.9) / 3.0),
                    lag_ratio=0.8,
                ) for layer, a in zip(layers, [0, 0.2, 0.1, 0])
            ]),
            Animation(logo.pupil),
        ]
コード例 #9
0
ファイル: vector_space_scene.py プロジェクト: stschaef/LLL
    def add_unit_square(self, animate=False, **kwargs):
        """
        Adds a unit square to the scene via
        self.get_unit_square.

        Parameters
        ----------
        animate (bool)
            Whether or not to animate the addition
            with DrawBorderThenFill.
        **kwargs
            Any valid keyword arguments of
            self.get_unit_square()
        
        Returns
        -------
        Square
            The unit square.
        """
        square = self.get_unit_square(**kwargs)
        if animate:
            self.play(DrawBorderThenFill(square),
                      Animation(Group(*self.moving_vectors)))
        self.add_transformable_mobject(square)
        self.bring_to_front(*self.moving_vectors)
        self.square = square
        return self
コード例 #10
0
ファイル: vector_space_scene.py プロジェクト: stschaef/LLL
    def apply_function(self, function, added_anims=[], **kwargs):
        """
        Applies the given function to each of the mobjects in
        self.transformable_mobjects, and plays the animation showing
        this.

        Parameters
        ----------
        function (Function)
            The function that affects each point
            of each mobject in self.transformable_mobjects.
        
        added_anims (list)
            Any other animations that need to be played
            simulataneously with this.
        
        **kwargs
            Any valid keyword argument of a self.play() call.
        """
        if "run_time" not in kwargs:
            kwargs["run_time"] = 3
        anims = [
            ApplyPointwiseFunction(function, t_mob)
            for t_mob in self.transformable_mobjects
        ] + [
            self.get_vector_movement(function),
            self.get_transformable_label_movement(),
            self.get_moving_mobject_movement(function),
        ] + [Animation(f_mob)
             for f_mob in self.foreground_mobjects] + added_anims
        self.play(*anims, **kwargs)
コード例 #11
0
    def construct(self):
        number_line = NumberLine(x_min=-2, x_max=2)
        triangle = RegularPolygon(3, start_angle=-PI / 2) \
            .scale(0.2) \
            .next_to(number_line.get_left(), UP, buff=SMALL_BUFF)
        numbers = VGroup(
            *[TextMobject("%s" % i) \
                  .next_to(number_line.get_tick(i - 2), DOWN) for i in range(1, 5)]
        )

        self.add(number_line)
        self.play(ShowCreation(triangle))
        self.wait(0.3)

        self.play(
            ApplyMethod(triangle.shift,
                        RIGHT * 4,
                        rate_func=linear,
                        run_time=4),
            *[
                AnimationGroup(Animation(Mobject(), run_time=i + 1),
                               Write(numbers[i]),
                               lag_ratio=1) for i in range(4)
            ],
        )

        self.wait()
コード例 #12
0
    def construct(self):
        number_line = NumberLine(x_min=-2, x_max=2)
        text_1 = TextMobject("Theorem of") \
            .next_to(number_line, DOWN)
        text_2 = TextMobject("Beethoven") \
            .next_to(number_line, DOWN)
        dashed_line = DashedLine(
            number_line.get_left(),
            number_line.get_right(),
            color=YELLOW,
        ).set_stroke(width=11)

        self.add(number_line, text_1)

        self.play(
            LaggedStart(*[
                ShowCreationThenDestruction(dashed_segment)
                for dashed_segment in dashed_line
            ],
                        run_time=5),
            AnimationGroup(Animation(Mobject(), run_time=2.1),
                           ReplacementTransform(text_1, text_2),
                           lag_ratio=1))

        self.wait()
コード例 #13
0
 def __init__(self, text_mobject, **kwargs):
     digest_config(self, kwargs)
     tpc = self.time_per_char
     anims = it.chain(*[[
         ShowIncreasingSubsets(word, run_time=tpc * len(word)),
         Animation(word, run_time=0.005 * len(word)**1.5),
     ] for word in text_mobject])
     super().__init__(*anims, **kwargs)
コード例 #14
0
 def add_unit_square(self, animate=False, **kwargs):
     square = self.get_unit_square(**kwargs)
     if animate:
         self.play(DrawBorderThenFill(square),
                   Animation(Group(*self.moving_vectors)))
     self.add_transformable_mobject(square)
     self.bring_to_front(*self.moving_vectors)
     self.square = square
     return self
コード例 #15
0
 def __init__(self, clock, **kwargs):
     if not hasattr(self, "args"):
         self.args = serialize_args([clock])
     if not hasattr(self, "config"):
         self.config = serialize_config({
             **kwargs,
         })
     digest_config(self, kwargs)
     assert (isinstance(clock, Clock))
     rot_kwargs = {"axis": OUT, "about_point": clock.get_center()}
     hour_radians = -self.hours_passed * 2 * np.pi / 12
     self.hour_rotation = Rotating(clock.hour_hand,
                                   radians=hour_radians,
                                   **rot_kwargs)
     self.hour_rotation.begin()
     self.minute_rotation = Rotating(clock.minute_hand,
                                     radians=12 * hour_radians,
                                     **rot_kwargs)
     self.minute_rotation.begin()
     Animation.__init__(self, clock, **kwargs)
コード例 #16
0
    def __init__(self, start_anim: Animation, end_anim: Animation, **kwargs):
        digest_config(self, kwargs, locals())
        if "run_time" in kwargs:
            self.run_time = kwargs.pop("run_time")
        else:
            self.run_time = max(start_anim.run_time, end_anim.run_time)
        for anim in start_anim, end_anim:
            anim.set_run_time(self.run_time)

        if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
            start_anim.starting_mobject.align_data_and_family(end_anim.starting_mobject)
            for anim in start_anim, end_anim:
                if hasattr(anim, "target_mobject"):
                    anim.starting_mobject.align_data_and_family(anim.target_mobject)

        Transform.__init__(self, start_anim.mobject,
                           end_anim.mobject, **kwargs)
        # Rewire starting and ending mobjects
        start_anim.mobject = self.starting_mobject
        end_anim.mobject = self.target_mobject
コード例 #17
0
 def apply_function(self, function, added_anims=[], **kwargs):
     if "run_time" not in kwargs:
         kwargs["run_time"] = 3
     anims = [
         ApplyPointwiseFunction(function, t_mob)
         for t_mob in self.transformable_mobjects
     ] + [
         self.get_vector_movement(function),
         self.get_transformable_label_movement(),
         self.get_moving_mobject_movement(function),
     ] + [Animation(f_mob) for f_mob in self.submobjects] + added_anims
     self.play(*anims, **kwargs)
コード例 #18
0
ファイル: drawings.py プロジェクト: coallaoh/manim
 def __init__(self, clock, **kwargs):
     digest_config(self, kwargs)
     assert(isinstance(clock, Clock))
     rot_kwargs = {
         "axis": OUT,
         "about_point": clock.get_center()
     }
     hour_radians = -self.hours_passed * 2 * np.pi / 12
     self.hour_rotation = Rotating(
         clock.hour_hand,
         radians=hour_radians,
         **rot_kwargs
     )
     self.hour_rotation.begin()
     self.minute_rotation = Rotating(
         clock.minute_hand,
         radians=12 * hour_radians,
         **rot_kwargs
     )
     self.minute_rotation.begin()
     Animation.__init__(self, clock, **kwargs)
コード例 #19
0
def turn_animation_into_updater(animation: Animation,
                                cycle: bool = False,
                                **kwargs) -> Mobject:
    """
    Add an updater to the animation's mobject which applies
    the interpolation and update functions of the animation

    If cycle is True, this repeats over and over.  Otherwise,
    the updater will be popped uplon completion
    """
    mobject = animation.mobject
    animation.update_config(**kwargs)
    animation.suspend_mobject_updating = False
    animation.begin()
    animation.total_time = 0

    def update(m, dt):
        run_time = animation.get_run_time()
        time_ratio = animation.total_time / run_time
        if cycle:
            alpha = time_ratio % 1
        else:
            alpha = clip(time_ratio, 0, 1)
            if alpha >= 1:
                animation.finish()
                m.remove_updater(update)
                return
        animation.interpolate(alpha)
        animation.update_mobjects(dt)
        animation.total_time += dt

    mobject.add_updater(update)
    return mobject
コード例 #20
0
 def show_reflines(self):
     reflines = VGroup(*[rhombus.get_refline() for rhombus in self.rhombi])
     eqtri_text = self.bg_texts[1]
     self.play(Write(eqtri_text),
               ShowCreation(reflines),
               Animation(self.rhombi),
               run_time=3)
     self.play(
         Indicate(VGroup(self.rhombi, reflines), scale_factor=1.05),
         run_time=2,
     )
     self.wait()
     self.reflines = reflines
コード例 #21
0
ファイル: composition.py プロジェクト: yuzhang49/manim
 def __init__(self, AnimationClass, mobject, arg_creator=None, **kwargs):
     digest_config(self, kwargs)
     for key in "rate_func", "run_time", "lag_ratio":
         if key in kwargs:
             kwargs.pop(key)
     if arg_creator is None:
         def arg_creator(mobject):
             return (mobject,)
     self.subanimations = [
         AnimationClass(
             *arg_creator(submob),
             run_time=self.run_time,
             rate_func=squish_rate_func(
                 self.rate_func, beta, beta + self.lag_ratio
             ),
             **kwargs
         )
         for submob, beta in zip(
             mobject,
             np.linspace(0, 1 - self.lag_ratio, len(mobject))
         )
     ]
     Animation.__init__(self, mobject, **kwargs)
コード例 #22
0
    def construct(self):
        number_line = NumberLine(x_min=-2, x_max=2)
        triangle = RegularPolygon(3, start_angle=-PI / 2) \
            .scale(0.2) \
            .next_to(number_line.get_left(), UP, buff=SMALL_BUFF)
        text_1 = TextMobject("1") \
            .next_to(number_line.get_tick(-1), DOWN)
        text_2 = TextMobject("2") \
            .next_to(number_line.get_tick(0), DOWN)
        text_3 = TextMobject("3") \
            .next_to(number_line.get_tick(1), DOWN)
        text_4 = TextMobject("4") \
            .next_to(number_line.get_tick(2), DOWN)

        self.add(number_line)
        self.play(ShowCreation(triangle))
        self.wait(0.3)

        self.play(
            ApplyMethod(triangle.shift,
                        RIGHT * 4,
                        rate_func=linear,
                        run_time=4),
            AnimationGroup(Animation(Mobject(), run_time=1),
                           Write(text_1),
                           lag_ratio=1),
            AnimationGroup(Animation(Mobject(), run_time=2),
                           Write(text_2),
                           lag_ratio=1),
            AnimationGroup(Animation(Mobject(), run_time=3),
                           Write(text_3),
                           lag_ratio=1),
            AnimationGroup(Animation(Mobject(), run_time=4),
                           Write(text_4),
                           lag_ratio=1))

        self.wait()
コード例 #23
0
 def __init__(self, text_mobject, **kwargs):
     if not hasattr(self, "args"):
         self.args = serialize_args([text_mobject])
     if not hasattr(self, "config"):
         self.config = serialize_config({
             **kwargs,
         })
     digest_config(self, kwargs)
     tpc = self.time_per_char
     anims = it.chain(*[
         [
             ShowIncreasingSubsets(word, run_time=tpc * len(word)),
             Animation(word, run_time=0.005 * len(word)**1.5),
         ]
         for word in text_mobject
     ])
     super().__init__(*anims, **kwargs)
コード例 #24
0
    def get_logo_animations(self, logo):
        layers = logo.spike_layers

        for j, layer in enumerate(layers):
            for i, spike in enumerate(layer):
                spike.angle = (13 * (i + 1) * (j + 1) * TAU / 28) % TAU
                if spike.angle > PI:
                    spike.angle -= TAU
                spike.save_state()
                spike.rotate(spike.angle, about_point=ORIGIN)
                # spike.get_points()[1] = rotate_vector(
                #     spike.get_points()[1], TAU/28,
                # )
                # spike.get_points()[-1] = rotate_vector(
                #     spike.get_points()[-1], -TAU/28,
                # )

        def get_spike_animation(spike, **kwargs):
            return Restore(spike, path_arc=-spike.angle, **kwargs)

        logo.iris_background.save_state()
        # logo.iris_background.scale(0.49)
        logo.iris_background.set_fill(GREY_D, 0.5)

        return [
            Restore(
                logo.iris_background,
                rate_func=squish_rate_func(smooth, 2.0 / 3, 1),
                run_time=3,
            ),
            AnimationGroup(*[
                LaggedStartMap(
                    get_spike_animation,
                    layer,
                    run_time=2,
                    # rate_func=squish_rate_func(smooth, a / 3.0, (a + 0.9) / 3.0),
                    lag_ratio=0.2,
                ) for layer, a in zip(layers, [0, 2, 1, 0])
            ]),
            Animation(logo.pupil),
        ]
コード例 #25
0
    def show_border_and_reflines(self):
        self.set_camera_orientation(*DIAG_POS)
        self.wait()

        init_ct = self.cts[0]
        border = init_ct.get_border()
        reflines = Reflines(init_ct)
        self.play(ShowCreation(border))
        self.wait(3)
        self.play(
            Write(reflines, rate_func=smooth, submobject_mode="lagged_start"),
            Animation(border),
            run_time=3,
        )
        self.play(
            Indicate(VGroup(border, reflines), scale_factor=1.05),
            run_time=2,
        )
        self.wait()

        self.border = border
        self.reflines = reflines
コード例 #26
0
    def construct(self):
        number_line = NumberLine(x_min=-2, x_max=2)
        text = TextMobject("Text") \
            .next_to(number_line, DOWN)
        dashed_line = DashedLine(
            number_line.get_left(),
            number_line.get_right(),
            color=YELLOW,
        ).set_stroke(width=11)

        self.add(number_line)
        self.wait(0.3)

        self.play(
            LaggedStart(*[
                ShowCreationThenDestruction(dashed_segment)
                for dashed_segment in dashed_line
            ],
                        run_time=5),
            AnimationGroup(Animation(Mobject(), run_time=2.1),
                           Write(text),
                           lag_ratio=1))
        self.wait()
コード例 #27
0
 def __init__(self, mobject, path, **kwargs):
     digest_config(self, kwargs, locals())
     Animation.__init__(self, mobject, **kwargs)
コード例 #28
0
ファイル: arithmetic.py プロジェクト: coallaoh/manim
 def __init__(self, tex_list, **kwargs):
     mobject = TexMobject(self.curr_tex).shift(start_center)
     Animation.__init__(self, mobject, **kwargs)
コード例 #29
0
    def __init__(self, *args, **kwargs):
        """
        Each arg will either be an animation, or an animation class
        followed by its arguments (and potentially a dict for
        configuration).
        """
        animations = []
        state = {
            "animations": animations,
            "curr_class": None,
            "curr_class_args": [],
            "curr_class_config": {},
        }

        def invoke_curr_class(state):
            if state["curr_class"] is None:
                return
            anim = state["curr_class"](
                *state["curr_class_args"],
                **state["curr_class_config"]
            )
            state["animations"].append(anim)
            anim.update(1)
            state["curr_class"] = None
            state["curr_class_args"] = []
            state["curr_class_config"] = {}

        for arg in args:
            if isinstance(arg, Animation):
                animations.append(arg)
                arg.update(1)
                invoke_curr_class(state)
            elif isinstance(arg, type) and issubclass(arg, Animation):
                invoke_curr_class(state)
                state["curr_class"] = arg
            elif isinstance(arg, dict):
                state["curr_class_config"] = arg
            else:
                state["curr_class_args"].append(arg)
        invoke_curr_class(state)
        for anim in animations:
            anim.update(0)

        animations = [x for x in animations if not(x.empty)]

        self.run_times = [anim.run_time for anim in animations]
        if "run_time" in kwargs:
            run_time = kwargs.pop("run_time")
            warnings.warn(
                "Succession doesn't currently support explicit run_time.")
        run_time = sum(self.run_times)
        self.num_anims = len(animations)
        if self.num_anims == 0:
            self.empty = True
        self.animations = animations
        # Have to keep track of this run_time, because Scene.play
        # might very well mess with it.
        self.original_run_time = run_time

        # critical_alphas[i] is the start alpha of self.animations[i]
        # critical_alphas[i + 1] is the end alpha of self.animations[i]
        critical_times = np.concatenate(([0], np.cumsum(self.run_times)))
        self.critical_alphas = [np.true_divide(
            x, run_time) for x in critical_times] if self.num_anims > 0 else [0.0]

        # self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i]
        # self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i]
        self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)]
        self.scene_mobjects_at_time[0] = Group()
        for i in range(self.num_anims):
            self.scene_mobjects_at_time[i + 1] = self.scene_mobjects_at_time[i].copy()
            self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1])

        self.current_alpha = 0
        # If self.num_anims == 0, this is an invalid index, but so it goes
        self.current_anim_index = 0
        if self.num_anims > 0:
            self.mobject = self.scene_mobjects_at_time[0]
            self.mobject.add(self.animations[0].mobject)
        else:
            self.mobject = Group()

        Animation.__init__(self, self.mobject, run_time=run_time, **kwargs)
コード例 #30
0
 def __init__(self, *args, **kwargs):
     return Animation.__init__(self, Group(), *args, **kwargs)
コード例 #31
0
 def __init__(self, vmobject, **kwargs):
     if not isinstance(vmobject, VMobject):
         raise Exception("DrawBorderThenFill only works for VMobjects")
     self.reached_halfway_point_before = False
     Animation.__init__(self, vmobject, **kwargs)
コード例 #32
0
ファイル: fading.py プロジェクト: unAlpha/AgManim
 def __init__(self, word, **kwargs):
     assert (isinstance(word, SingleStringTexMobject))
     digest_config(self, kwargs)
     run_time = kwargs.pop("run_time", self.time_per_char * len(word))
     Animation.__init__(self, word, run_time=run_time, **kwargs)
コード例 #33
0
 def update_config(self, **kwargs):
     Animation.update_config(self, **kwargs)
     if "path_arc" in kwargs:
         self.path_func = path_along_arc(kwargs["path_arc"],
                                         kwargs.get("path_arc_axis", OUT))
コード例 #34
0
ファイル: end_screen.py プロジェクト: uedzen/videos
    def construct(self):
        # Add title
        title = self.title = TextMobject("Clicky Stuffs")
        title.scale(1.5)
        title.to_edge(UP, buff=MED_SMALL_BUFF)

        pi_creatures = VGroup(Randolph(), Mortimer())
        for pi, vect in zip(pi_creatures, [LEFT, RIGHT]):
            pi.set_height(title.get_height())
            pi.change_mode("thinking")
            pi.look(DOWN)
            pi.next_to(title, vect, buff=MED_LARGE_BUFF)
        self.add(title, pi_creatures)

        # Set the top of the screen
        logo_box = Square(side_length=2.5)
        logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF)

        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)

        # Add thanks
        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)

        # Build name list
        file_name = os.path.join(get_directories()["data"], "patrons.txt")
        with open(file_name, "r") as fp:
            names = [
                self.modify_patron_name(name.strip())
                for name in fp.readlines()
            ]

        if self.randomize_order:
            random.shuffle(names)
        else:
            names.sort()

        name_labels = VGroup(*map(TextMobject, names))
        name_labels.scale(self.patron_scale_val)
        for label in name_labels:
            if label.get_width() > self.max_patron_width:
                label.set_width(self.max_patron_width)
        columns = VGroup(*[
            VGroup(*name_labels[i::self.n_patron_columns])
            for i in range(self.n_patron_columns)
        ])
        column_x_spacing = 0.5 + max([c.get_width() for c in columns])

        for i, column in enumerate(columns):
            for n, name in enumerate(column):
                name.shift(n * self.name_y_spacing * DOWN)
                name.align_to(ORIGIN, LEFT)
            column.move_to(i * column_x_spacing * RIGHT, UL)
        columns.center()

        max_width = FRAME_WIDTH - 1
        if columns.get_width() > max_width:
            columns.set_width(max_width)
        underline.match_width(columns)
        columns.next_to(underline, DOWN, buff=3)

        # Set movement
        columns.generate_target()
        distance = columns.get_height() + 2
        wait_time = self.scroll_time
        frame = self.camera.frame
        frame_shift = ApplyMethod(
            frame.shift,
            distance * DOWN,
            run_time=wait_time,
            rate_func=linear,
        )
        blink_anims = []
        blank_mob = Mobject()
        for x in range(wait_time):
            if random.random() < 0.25:
                blink_anims.append(Blink(random.choice(pi_creatures)))
            else:
                blink_anims.append(Animation(blank_mob))
        blinks = Succession(*blink_anims)

        static_group = VGroup(black_rect, line, thanks, pi_creatures, title)
        static_group.fix_in_frame()
        self.add(columns, static_group)
        self.play(frame_shift, blinks)