Animation engine for explanatory math videos with programmatic mathematical visualization capabilities
—
ManimGL's value tracking system provides powerful tools for managing numeric parameters that change over time during animations. Value trackers are invisible mobjects that store numeric data and integrate seamlessly with the animation system, enabling complex parameter-dependent animations and interactive controls.
Store and animate numeric values that can drive parameter-dependent animations and link multiple objects to shared data sources.
class ValueTracker(Mobject):
value_type: type = np.float64
def __init__(self, value: float | complex | np.ndarray = 0, **kwargs):
"""
Initialize a value tracker with a numeric value.
Parameters:
- value: Initial numeric value (scalar, complex, or array)
- kwargs: Additional Mobject keyword arguments
"""
def get_value(self) -> float | complex | np.ndarray:
"""
Retrieve the current tracked value.
Returns:
Current value (scalar if single value, array if multiple values)
"""
def set_value(self, value: float | complex | np.ndarray) -> Self:
"""
Update the tracked value.
Parameters:
- value: New numeric value to store
Returns:
Self for method chaining
"""
def increment_value(self, d_value: float | complex) -> None:
"""
Add the specified amount to the current value.
Parameters:
- d_value: Amount to add to current value
"""
def init_uniforms(self) -> None:
"""Initialize internal uniform data structure for GPU integration."""Specialized tracker for exponential interpolation, storing values in logarithmic space for natural exponential animation behavior.
class ExponentialValueTracker(ValueTracker):
def __init__(self, value: float | complex = 1, **kwargs):
"""
Initialize tracker for exponential value changes.
Parameters:
- value: Initial value (stored as log internally)
- kwargs: Additional ValueTracker arguments
"""
def get_value(self) -> float | complex:
"""
Get the exponential of the stored value.
Returns:
np.exp(internal_value) - the actual exponential value
"""
def set_value(self, value: float | complex):
"""
Set value by storing its logarithm internally.
Parameters:
- value: Actual value to represent (stored as log)
"""Optimized tracker for complex number values with high-precision complex number operations.
class ComplexValueTracker(ValueTracker):
value_type: type = np.complex128
def __init__(self, value: complex = 0+0j, **kwargs):
"""
Initialize tracker for complex number values.
Parameters:
- value: Initial complex number value
- kwargs: Additional ValueTracker arguments
"""from manimlib import *
class BasicValueTracking(Scene):
def construct(self):
# Create a value tracker for controlling opacity
opacity_tracker = ValueTracker(0)
# Create objects that respond to the tracker
circle = Circle(radius=2, color=BLUE, fill_opacity=1)
square = Square(side_length=3, color=RED, fill_opacity=1)
# Link objects to the tracker value
circle.add_updater(
lambda c: c.set_fill_opacity(opacity_tracker.get_value())
)
square.add_updater(
lambda s: s.set_fill_opacity(1 - opacity_tracker.get_value())
)
self.add(circle, square)
# Animate the tracked value
self.play(opacity_tracker.animate.set_value(1), run_time=3)
self.play(opacity_tracker.animate.set_value(0.5), run_time=2)
self.play(opacity_tracker.animate.set_value(0), run_time=3)class DynamicFunctionPlot(Scene):
def construct(self):
# Create coordinate system
axes = Axes(x_range=[-3, 3], y_range=[-2, 2])
self.add(axes)
# Create value trackers for function parameters
amplitude = ValueTracker(1)
frequency = ValueTracker(1)
phase = ValueTracker(0)
# Create function that depends on trackers
def get_sine_graph():
return axes.plot(
lambda x: (
amplitude.get_value() *
np.sin(frequency.get_value() * x + phase.get_value())
),
color=YELLOW
)
# Create initial graph
graph = get_sine_graph()
# Add updater to redraw graph when parameters change
graph.add_updater(lambda g: g.become(get_sine_graph()))
self.add(graph)
# Animate parameters
self.play(amplitude.animate.set_value(1.5), run_time=2)
self.play(frequency.animate.set_value(2), run_time=2)
self.play(phase.animate.set_value(PI/2), run_time=2)
# Multiple parameters simultaneously
self.play(
amplitude.animate.set_value(0.5),
frequency.animate.set_value(3),
phase.animate.set_value(0),
run_time=3
)class ExponentialAnimation(Scene):
def construct(self):
# Create exponential value tracker for smooth exponential growth
scale_tracker = ExponentialValueTracker(1)
# Create object that will grow exponentially
circle = Circle(radius=0.5, color=BLUE, fill_opacity=0.7)
# Link circle size to exponential tracker
def update_circle(c):
scale_factor = scale_tracker.get_value()
c.set_width(scale_factor)
c.set_height(scale_factor)
# Color intensity based on size
opacity = min(1, scale_factor / 5)
c.set_fill_opacity(opacity)
circle.add_updater(update_circle)
self.add(circle)
# Animate exponential growth (linear interpolation in log space)
self.play(scale_tracker.animate.set_value(10), run_time=4)
self.wait()
# Exponential decay
self.play(scale_tracker.animate.set_value(0.1), run_time=3)class ComplexValueDemo(Scene):
def construct(self):
# Use complex tracker for rotation and scaling combined
complex_tracker = ComplexValueTracker(1+0j)
# Create shape to transform
arrow = Arrow(ORIGIN, RIGHT, color=RED, buff=0)
# Transform arrow based on complex number
def update_arrow(a):
complex_val = complex_tracker.get_value()
# Complex multiplication = rotation + scaling
magnitude = abs(complex_val)
angle = np.angle(complex_val)
a.become(Arrow(ORIGIN, RIGHT, buff=0, color=RED))
a.scale(magnitude)
a.rotate(angle)
arrow.add_updater(update_arrow)
self.add(arrow)
# Animate in complex plane
self.play(
complex_tracker.animate.set_value(2 * np.exp(1j * PI/4)),
run_time=2
)
self.play(
complex_tracker.animate.set_value(3 * np.exp(1j * PI)),
run_time=2
)
self.play(
complex_tracker.animate.set_value(0.5 * np.exp(1j * 2*PI)),
run_time=3
)class CoordinatedParameters(Scene):
def construct(self):
# Create multiple coordinated trackers
x_pos = ValueTracker(-3)
y_pos = ValueTracker(0)
size = ValueTracker(0.5)
hue = ValueTracker(0)
# Create object that depends on all parameters
circle = Circle(radius=0.5, fill_opacity=0.8)
def update_circle(c):
# Position
c.move_to([x_pos.get_value(), y_pos.get_value(), 0])
# Size
c.set_width(2 * size.get_value())
c.set_height(2 * size.get_value())
# Color based on hue value
import colorsys
rgb = colorsys.hsv_to_rgb(hue.get_value(), 1, 1)
c.set_fill(rgb_to_color(rgb))
circle.add_updater(update_circle)
self.add(circle)
# Coordinate multiple parameter changes
self.play(
x_pos.animate.set_value(3),
y_pos.animate.set_value(2),
size.animate.set_value(1.5),
hue.animate.set_value(0.3),
run_time=4
)
# Create circular motion with changing properties
self.play(
x_pos.animate.set_value(-3),
y_pos.animate.set_value(-2),
size.animate.set_value(0.3),
hue.animate.set_value(0.8),
run_time=3
)from manimlib.mobject.interactive import LinearNumberSlider
class InteractiveValueTracking(Scene):
def setup(self):
# Create sliders that are themselves value trackers
self.amplitude_slider = LinearNumberSlider(
value=1, min_value=0, max_value=3, step=0.1
)
self.frequency_slider = LinearNumberSlider(
value=1, min_value=0.1, max_value=5, step=0.1
)
# Position sliders
self.amplitude_slider.to_edge(DOWN).shift(UP * 0.5)
self.frequency_slider.to_edge(DOWN)
self.add(self.amplitude_slider, self.frequency_slider)
def construct(self):
# Create responsive visualization
axes = Axes(x_range=[-3, 3], y_range=[-3, 3])
def get_wave():
return axes.plot(
lambda x: (
self.amplitude_slider.get_value() *
np.sin(self.frequency_slider.get_value() * x)
),
color=YELLOW
)
wave = get_wave()
wave.add_updater(lambda w: w.become(get_wave()))
self.add(axes, wave)
# Create labels that show current values
amplitude_label = Text("Amplitude: 1.0", font_size=24)
frequency_label = Text("Frequency: 1.0", font_size=24)
amplitude_label.to_edge(UP).shift(DOWN * 0.5)
frequency_label.to_edge(UP)
def update_amp_label(label):
val = self.amplitude_slider.get_value()
label.become(Text(f"Amplitude: {val:.1f}", font_size=24))
label.to_edge(UP).shift(DOWN * 0.5)
def update_freq_label(label):
val = self.frequency_slider.get_value()
label.become(Text(f"Frequency: {val:.1f}", font_size=24))
label.to_edge(UP)
amplitude_label.add_updater(update_amp_label)
frequency_label.add_updater(update_freq_label)
self.add(amplitude_label, frequency_label)
# Interactive exploration time
self.wait(10)# Value trackers support method chaining
tracker = ValueTracker(0)
tracker.set_value(5).increment_value(2) # Final value: 7
# Animation chaining
self.play(
tracker.animate.set_value(10),
other_object.animate.shift(UP)
)# Time-based updaters (receive dt parameter)
def time_updater(mob, dt):
mob.rotate(dt * tracker.get_value())
mob.add_updater(time_updater)
# Non-time-based updaters (called every frame)
def position_updater(mob):
mob.move_to([tracker.get_value(), 0, 0])
mob.add_updater(position_updater, call_updater=True)# Multiple trackers with different rate functions
self.play(
tracker1.animate.set_value(5).set_rate_func(smooth),
tracker2.animate.set_value(10).set_rate_func(linear),
tracker3.animate.set_value(2).set_rate_func(there_and_back),
run_time=3
)
# Staggered animations
self.play(
AnimationGroup(
tracker1.animate.set_value(5),
tracker2.animate.set_value(3),
tracker3.animate.set_value(8),
lag_ratio=0.3
),
run_time=4
)Value trackers store data in self.uniforms["value"] as numpy arrays, enabling:
.animate syntax for smooth transitionsclear_updaters() when no longer needed to prevent memory leaksThe value tracking system provides the foundation for sophisticated parameter-dependent animations in ManimGL, enabling smooth interpolation of any numeric parameter while maintaining full integration with the animation and rendering pipeline.
Install with Tessl CLI
npx tessl i tessl/pypi-manimgldocs