Updaters: Things That Follow

An updater keeps one thing in step with another: a label that follows a dot, a line that always joins two points, a number that counts up as something moves.

A label that follows

from pymations import *

dot = Dot(LEFT * 4, color=YELLOW)
label = Text("me", font_size=32)
label.add_updater(lambda m: m.next_to(dot, UP))

add(dot, label)
play(dot.animate.shift(RIGHT * 8), run_time=3)
play(dot.animate.shift(DOWN * 2))

m.add_updater(function) runs function(m) on every frame, so whatever it does to m is redone as things move. Here the label is put above the dot on every frame.

always(label.next_to, dot, UP) is a shorter way to write the same thing.

A shape that is rebuilt

always_redraw makes a new shape on every frame from a function:

from pymations import *

a = Dot(LEFT * 3, color=BLUE)
b = Dot(RIGHT * 3, color=ORANGE)
line = always_redraw(lambda: Line(a.get_center(), b.get_center()))

add(line, a, b)
play(a.animate.shift(UP * 2), b.animate.shift(DOWN * 2), run_time=2)
play(Rotate(VGroup(a, b), angle=PI), run_time=2)

A number to animate: ValueTracker

A ValueTracker holds a number that you can animate. Other things follow it with updaters:

from pymations import *

x = ValueTracker(0)
axes = Axes(x_range=[0, 6, 1], y_range=[-1.5, 1.5, 1], x_length=10, y_length=4, tips=False)
curve = axes.plot(lambda t: np.sin(t), color=BLUE)
dot = always_redraw(lambda: Dot(axes.c2p(x.get_value(), np.sin(x.get_value())), color=YELLOW))

add(axes, curve, dot)
play(x.animate.set_value(6), run_time=4, rate_func=linear)
play(x.animate.set_value(PI / 2))
TrackerWhat it does
ValueTracker(0)A number, starting at 0
t.get_value()Its value (inside an updater, the value on that frame)
t.animate.set_value(5)Plays it changing to 5
t.animate.increment_value(1)Plays it going up by 1
t.set_value(5)Sets it at once

Variable(0, "x") shows x = 0.00 with a tracker of its own, var.tracker: animate the tracker and the number counts.

Steady movement

An updater that takes a second argument, dt, is given the time since the last frame. Multiply a speed by it for steady movement:

from pymations import *

square = Square(color=TEAL)
square.add_updater(lambda m, dt: m.rotate(dt))

add(square)
wait(4)

always_rotate(m) and always_shift(m, RIGHT) are ready-made versions.

Trails

TracedPath(dot.get_center) draws the path a point leaves behind as it moves:

from pymations import *

dot = Dot(RIGHT * 2, color=YELLOW)
trail = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=3)

add(trail, dot)
play(Rotate(dot, angle=TAU, about_point=ORIGIN), run_time=3, rate_func=linear)

Stopping an updater

m.remove_updater(function) takes one off, m.clear_updaters() takes them all off, and m.suspend_updating() / m.resume_updating() pause and restart them.

Scrubbing and recording still work

Pymations reads an updater once, when the file runs, and turns it into a rule the preview can work out for any moment. So scrubbing back and forth shows the right picture, and a recorded video is exact.

A few updaters can't be read that way: one with an if that depends on a moving value, or one that uses math.sin and the other math functions. Those run in Python on every frame instead. They still work, but the program stays running while the preview plays, and the output under the preview prints a note naming the line.

Use np.sin, np.cos, np.sqrt and the other np functions in updaters instead of math. They keep the updater in the preview, where it is faster and scrubs back and forth exactly:

from pymations import *

t = ValueTracker(0)
dot = Dot(color=YELLOW)
dot.add_updater(lambda m: m.move_to([2 * np.cos(t.get_value()), 2 * np.sin(t.get_value()), 0]))

add(dot)
play(t.animate.set_value(TAU), run_time=3, rate_func=linear)