memlnaut-nisps/assets/media/src/s2_xy_pad.py

134 lines
5.3 KiB
Python

"""Manifold explainer 2 — XY pad projects to two numbers.
A 2-D pad is two independent 0-1 readings. Moving the dot moves A (horizontal)
and B (vertical) independently, each shown as a slider.
Render:
manim -qh s2_xy_pad.py XYPad
"""
from manim import *
BG = "#1C1C1C"
PRIMARY = "#58C4DD" # pad / control
A_COL = "#FFFF00" # A axis (horizontal)
B_COL = "#83C167" # B axis (vertical)
DIM = 0.4
MONO = "Monospace"
def slider(value_tracker, length, colour, horizontal=True):
"""A track + a knob that tracks value in [0,1] along `length`."""
if horizontal:
track = Line(LEFT * length / 2, RIGHT * length / 2, color=colour,
stroke_width=5).set_opacity(DIM)
knob = always_redraw(lambda: Dot(
track.get_start() + RIGHT * value_tracker.get_value() * length,
radius=0.13, color=colour))
fill = always_redraw(lambda: Line(
track.get_start(),
track.get_start() + RIGHT * value_tracker.get_value() * length,
color=colour, stroke_width=5))
else:
track = Line(DOWN * length / 2, UP * length / 2, color=colour,
stroke_width=5).set_opacity(DIM)
knob = always_redraw(lambda: Dot(
track.get_start() + UP * value_tracker.get_value() * length,
radius=0.13, color=colour))
fill = always_redraw(lambda: Line(
track.get_start(),
track.get_start() + UP * value_tracker.get_value() * length,
color=colour, stroke_width=5))
return VGroup(track), fill, knob
class XYPad(Scene):
def construct(self):
self.camera.background_color = BG
title = Text("One pad, two numbers", font_size=44, color=PRIMARY,
weight=BOLD, font=MONO)
self.play(Write(title), run_time=1.5)
self.wait(1.0)
self.play(title.animate.scale(0.62).to_edge(UP, buff=0.5), run_time=1.0)
self.wait(0.3)
# the pad
side = 3.4
ax = ValueTracker(0.5)
bx = ValueTracker(0.5)
pad = Square(side_length=side, color=PRIMARY, stroke_width=5)
pad.set_fill(PRIMARY, opacity=0.05)
pad.shift(LEFT * 3.0)
bl = pad.get_corner(DL)
grid = VGroup()
for f in (0.25, 0.5, 0.75):
grid.add(Line(bl + RIGHT * f * side, bl + RIGHT * f * side + UP * side,
color=PRIMARY, stroke_width=1).set_opacity(0.15))
grid.add(Line(bl + UP * f * side, bl + UP * f * side + RIGHT * side,
color=PRIMARY, stroke_width=1).set_opacity(0.15))
dot = always_redraw(lambda: Dot(
bl + RIGHT * ax.get_value() * side + UP * bx.get_value() * side,
radius=0.13, color=WHITE))
# guide lines from the dot to each axis
vline = always_redraw(lambda: DashedLine(
bl + RIGHT * ax.get_value() * side,
bl + RIGHT * ax.get_value() * side + UP * bx.get_value() * side,
color=A_COL, stroke_width=2).set_opacity(DIM))
hline = always_redraw(lambda: DashedLine(
bl + UP * bx.get_value() * side,
bl + RIGHT * ax.get_value() * side + UP * bx.get_value() * side,
color=B_COL, stroke_width=2).set_opacity(DIM))
self.play(Create(pad), Create(grid), run_time=1.2)
self.add(dot)
pad_label = Text("XY pad", font_size=24, color=PRIMARY, font=MONO)
pad_label.next_to(pad, DOWN, buff=0.4)
self.play(FadeIn(pad_label), run_time=0.6)
self.wait(0.5)
self.add(vline, hline)
# A slider (horizontal) and B slider (vertical) on the right
a_track, a_fill, a_knob = slider(ax, 3.0, A_COL, horizontal=True)
a_group = VGroup(a_track, a_fill, a_knob)
a_group.shift(RIGHT * 3.0 + UP * 1.4)
a_lab = always_redraw(lambda: Text(
f"A = {ax.get_value():.2f}", font_size=28, color=A_COL, font=MONO
).next_to(a_track, UP, buff=0.3))
b_track, b_fill, b_knob = slider(bx, 3.0, B_COL, horizontal=False)
b_group = VGroup(b_track, b_fill, b_knob)
b_group.shift(RIGHT * 3.6 + DOWN * 1.4)
b_lab = always_redraw(lambda: Text(
f"B = {bx.get_value():.2f}", font_size=28, color=B_COL, font=MONO
).next_to(b_track, RIGHT, buff=0.4))
self.play(Create(a_track), Create(b_track), run_time=1.0)
self.add(a_fill, a_knob, a_lab, b_fill, b_knob, b_lab)
self.wait(0.5)
note = Text("A reads across, B reads up -- independently",
font_size=24, color=WHITE, font=MONO).to_edge(DOWN, buff=0.55)
self.play(FadeIn(note), run_time=0.8)
self.wait(1.0)
# move horizontally only: A changes, B holds
self.play(ax.animate.set_value(0.9), run_time=1.6, rate_func=smooth)
self.wait(0.6)
# move vertically only: B changes, A holds
self.play(bx.animate.set_value(0.85), run_time=1.6, rate_func=smooth)
self.wait(0.6)
# a diagonal sweep: both change together
self.play(ax.animate.set_value(0.15), bx.animate.set_value(0.2),
run_time=2.0, rate_func=smooth)
self.wait(0.6)
self.play(ax.animate.set_value(0.65), bx.animate.set_value(0.7),
run_time=1.6, rate_func=smooth)
self.wait(1.5)
self.play(FadeOut(Group(*self.mobjects)), run_time=0.6)
self.wait(0.3)