📄 updaters-and-trackers.md

← Vault

Updaters and Value Trackers

The problem updaters solve

Normal animations are discrete: self.play() goes from state A to state B. But what if you need continuous relationships — a label that always hovers above a moving dot, or a line that always connects two points?

Without updaters, you'd manually reposition every dependent object before every self.play(). Five animations that move a dot means five manual repositioning calls for the label. Miss one and it freezes in the wrong spot.

Updaters let you declare a relationship ONCE. Manim calls the updater function EVERY FRAME (15-60 fps depending on quality) to enforce that relationship, no matter what else is happening.

ValueTracker: an invisible steering wheel

A ValueTracker is an invisible Mobject that holds a single float. It never appears on screen. It exists so you can ANIMATE it while other objects REACT to its value.

Think of it as a slider: drag the slider from 0 to 5, and every object wired to it responds in real time.

`python

tracker = ValueTracker(0) # invisible, stores 0.0

tracker.get_value() # read: 0.0

tracker.set_value(5) # write: jump to 5.0 instantly

tracker.animate.set_value(5) # animate: smoothly interpolate to 5.0

`

The three-step pattern

Every ValueTracker usage follows this:

1. Create the tracker (the invisible slider)

2. Create visible objects that READ the tracker via updaters

3. Animate the tracker — all dependents update automatically

`python

Step 1: Create tracker

x_tracker = ValueTracker(1)

Step 2: Create dependent objects

dot = always_redraw(lambda: Dot(axes.c2p(x_tracker.get_value(), 0), color=YELLOW))

v_line = always_redraw(lambda: axes.get_vertical_line(

axes.c2p(x_tracker.get_value(), func(x_tracker.get_value())), color=BLUE

))

label = always_redraw(lambda: DecimalNumber(x_tracker.get_value(), font_size=24)

.next_to(dot, UP))

self.add(dot, v_line, label)

Step 3: Animate the tracker — everything follows

self.play(x_tracker.animate.set_value(5), run_time=3)

`

Types of updaters

Lambda updater (most common)

Runs a function every frame, passing the mobject itself:

`python

Label always stays above the dot

label.add_updater(lambda m: m.next_to(dot, UP, buff=0.2))

Line always connects two points

line.add_updater(lambda m: m.put_start_and_end_on(

point_a.get_center(), point_b.get_center()

))

`

Time-based updater (with dt)

The second argument dt is the time since the last frame (~0.017s at 60fps):

`python

Continuous rotation

square.add_updater(lambda m, dt: m.rotate(0.5 * dt))

Continuous rightward drift

dot.add_updater(lambda m, dt: m.shift(RIGHT 0.3 dt))

Oscillation

dot.add_updater(lambda m, dt: m.move_to(

axes.c2p(m.get_center()[0], np.sin(self.time))

))

`

Use dt updaters for physics simulations, continuous motion, and time-dependent effects.

always_redraw: full rebuild every frame

Creates a new mobject from scratch each frame. More expensive than add_updater but handles cases where the mobject's structure changes (not just position/color):

`python

Brace that follows a resizing square

brace = always_redraw(Brace, square, UP)

Area under curve that updates as function changes

area = always_redraw(lambda: axes.get_area(

graph, x_range=[0, x_tracker.get_value()], color=BLUE, opacity=0.3

))

Label that reconstructs its text

counter = always_redraw(lambda: Text(

f"n = {int(x_tracker.get_value())}", font_size=24, font="Menlo"

).to_corner(UR))

`

When to use which:

DecimalNumber: showing live values

`python

Counter that tracks a ValueTracker

tracker = ValueTracker(0)

number = DecimalNumber(0, font_size=48, num_decimal_places=1, color=PRIMARY)

number.add_updater(lambda m: m.set_value(tracker.get_value()))

number.add_updater(lambda m: m.next_to(dot, RIGHT, buff=0.3))

self.add(number)

self.play(tracker.animate.set_value(100), run_time=3)

`

Variable: the labeled version

`python

var = Variable(0, Text("x", font_size=24, font="Menlo"), num_decimal_places=2)

self.add(var)

self.play(var.tracker.animate.set_value(PI), run_time=2)

Displays: x = 3.14

`

Removing updaters

`python

Remove all updaters

mobject.clear_updaters()

Suspend temporarily (during an animation that would fight the updater)

mobject.suspend_updating()

self.play(mobject.animate.shift(RIGHT))

mobject.resume_updating()

Remove specific updater (if you stored a reference)

def my_updater(m):

m.next_to(dot, UP)

label.add_updater(my_updater)

... later ...

label.remove_updater(my_updater)

`

Animation-based updaters

UpdateFromFunc / UpdateFromAlphaFunc

These are ANIMATIONS (passed to self.play), not persistent updaters:

`python

Call a function on each frame of the animation

self.play(UpdateFromFunc(mobject, lambda m: m.next_to(moving_target, UP)), run_time=3)

With alpha (0 to 1) — useful for custom interpolation

self.play(UpdateFromAlphaFunc(circle, lambda m, a: m.set_fill(opacity=a)), run_time=2)

`

turn_animation_into_updater

Convert a one-shot animation into a continuous updater:

`python

from manim import turn_animation_into_updater

This would normally play once — now it loops forever

turn_animation_into_updater(Rotating(gear, rate=PI/4))

self.add(gear)

self.wait(5) # gear rotates for 5 seconds

`

Practical patterns

Pattern 1: Dot tracing a function

`python

tracker = ValueTracker(0)

graph = axes.plot(np.sin, x_range=[0, 2*PI], color=PRIMARY)

dot = always_redraw(lambda: Dot(

axes.c2p(tracker.get_value(), np.sin(tracker.get_value())),

color=YELLOW

))

tangent = always_redraw(lambda: axes.get_secant_slope_group(

x=tracker.get_value(), graph=graph, dx=0.01,

secant_line_color=HIGHLIGHT, secant_line_length=3

))

self.add(graph, dot, tangent)

self.play(tracker.animate.set_value(2*PI), run_time=6, rate_func=linear)

`

Pattern 2: Live area under curve

`python

tracker = ValueTracker(0.5)

area = always_redraw(lambda: axes.get_area(

graph, x_range=[0, tracker.get_value()],

color=PRIMARY, opacity=0.3

))

area_label = always_redraw(lambda: DecimalNumber(

# Numerical integration

sum(func(x) * 0.01 for x in np.arange(0, tracker.get_value(), 0.01)),

font_size=24

).next_to(axes, RIGHT))

self.add(area, area_label)

self.play(tracker.animate.set_value(4), run_time=5)

`

Pattern 3: Connected diagram

`python

Nodes that can be moved, with edges that auto-follow

node_a = Dot(LEFT * 2, color=PRIMARY)

node_b = Dot(RIGHT * 2, color=SECONDARY)

edge = Line().add_updater(lambda m: m.put_start_and_end_on(

node_a.get_center(), node_b.get_center()

))

label = Text("edge", font_size=18, font="Menlo").add_updater(

lambda m: m.move_to(edge.get_center() + UP * 0.3)

)

self.add(node_a, node_b, edge, label)

self.play(node_a.animate.shift(UP * 2), run_time=2)

self.play(node_b.animate.shift(DOWN + RIGHT), run_time=2)

Edge and label follow automatically

`

Pattern 4: Parameter exploration

`python

Explore how a parameter changes a curve

a_tracker = ValueTracker(1)

curve = always_redraw(lambda: axes.plot(

lambda x: a_tracker.get_value() * np.sin(x),

x_range=[0, 2*PI], color=PRIMARY

))

param_label = always_redraw(lambda: Text(

f"a = {a_tracker.get_value():.1f}", font_size=24, font="Menlo"

).to_corner(UR))

self.add(curve, param_label)

self.play(a_tracker.animate.set_value(3), run_time=3)

self.play(a_tracker.animate.set_value(0.5), run_time=2)

self.play(a_tracker.animate.set_value(1), run_time=1)

`

Common mistakes

1. Updater fights animation: If a mobject has an updater that sets its position, and you try to animate it elsewhere, the updater wins every frame. Suspend updating first.

2. always_redraw for simple moves: If you only need to reposition, use add_updater. always_redraw reconstructs the entire mobject every frame — expensive and unnecessary for position tracking.

3. Forgetting to add to scene: Updaters only run on mobjects that are in the scene. always_redraw creates the mobject but you still need self.add().

4. Updater creates new mobjects without cleanup: If your updater creates Text objects every frame, they accumulate. Use always_redraw (which handles cleanup) or update properties in-place.