📄 decorations.md

← Vault

Decorations and Visual Polish

Decorations are mobjects that annotate, highlight, or frame other mobjects. They turn a technically correct animation into a visually polished one.

SurroundingRectangle

Draws a rectangle around any mobject. The go-to for highlighting:

`python

highlight = SurroundingRectangle(

equation[2], # the term to highlight

color=YELLOW,

buff=0.15, # padding between content and border

corner_radius=0.1, # rounded corners

stroke_width=2

)

self.play(Create(highlight))

self.wait(1)

self.play(FadeOut(highlight))

`

Around part of an equation

`python

eq = MathTex(r"E", r"=", r"m", r"c^2")

box = SurroundingRectangle(eq[2:], color=YELLOW, buff=0.1) # highlight "mc²"

label = Text("mass-energy", font_size=18, font="Menlo", color=YELLOW)

label.next_to(box, DOWN, buff=0.2)

self.play(Create(box), FadeIn(label))

`

BackgroundRectangle

Semi-transparent background behind text for readability over complex scenes:

`python

bg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2, color=BLACK)

self.play(FadeIn(bg), Write(equation))

Or using set_stroke for a "backdrop" effect on the text itself:

label.set_stroke(BLACK, width=5, background=True)

`

The set_stroke(background=True) approach is cleaner for text labels over graphs/diagrams.

Brace and BraceLabel

Curly braces that annotate sections of a diagram or equation:

`python

brace = Brace(equation[2:4], DOWN, color=YELLOW)

brace_label = brace.get_text("these terms", font_size=20)

self.play(GrowFromCenter(brace), FadeIn(brace_label))

Between two specific points

brace = BraceBetweenPoints(point_a, point_b, direction=UP)

`

Brace placement

`python

Below a group

Brace(group, DOWN)

Above a group

Brace(group, UP)

Left of a group

Brace(group, LEFT)

Right of a group

Brace(group, RIGHT)

`

Arrows for Annotation

Straight arrows pointing to mobjects

`python

arrow = Arrow(

start=label.get_bottom(),

end=target.get_top(),

color=YELLOW,

stroke_width=2,

buff=0.1, # gap between arrow tip and target

max_tip_length_to_length_ratio=0.15 # small arrowhead

)

self.play(GrowArrow(arrow), FadeIn(label))

`

Curved arrows

`python

arrow = CurvedArrow(

start_point=source.get_right(),

end_point=target.get_left(),

angle=PI/4, # curve angle

color=PRIMARY

)

`

Labeling with arrows

`python

LabeledArrow: arrow with built-in text label

arr = LabeledArrow(

Text("gradient", font_size=16, font="Menlo"),

start=point_a, end=point_b, color=RED

)

`

DashedLine and DashedVMobject

`python

Dashed line (for asymptotes, construction lines, implied connections)

asymptote = DashedLine(

axes.c2p(2, -3), axes.c2p(2, 3),

color=YELLOW, dash_length=0.15

)

Make any VMobject dashed

dashed_circle = DashedVMobject(Circle(radius=2, color=BLUE), num_dashes=30)

`

Angle and RightAngle Markers

`python

line1 = Line(ORIGIN, RIGHT * 2)

line2 = Line(ORIGIN, UP * 2 + RIGHT)

Angle arc between two lines

angle = Angle(line1, line2, radius=0.5, color=YELLOW)

angle_value = angle.get_value() # radians

Right angle marker (the small square)

right_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)

`

Cross (strikethrough)

Mark something as wrong or deprecated:

`python

cross = Cross(old_equation, color=RED, stroke_width=4)

self.play(Create(cross))

Then show the correct version

`

Underline

`python

underline = Underline(important_text, color=ACCENT, stroke_width=3)

self.play(Create(underline))

`

Color Highlighting Workflow

Method 1: At creation with t2c

`python

text = Text("The gradient is negative here", t2c={"gradient": BLUE, "negative": RED})

`

Method 2: set_color_by_tex after creation

`python

eq = MathTex(r"\nabla L = -\frac{\partial L}{\partial w}")

eq.set_color_by_tex(r"\nabla", BLUE)

eq.set_color_by_tex(r"\partial", RED)

`

Method 3: Index into submobjects

`python

eq = MathTex(r"a", r"+", r"b", r"=", r"c")

eq[0].set_color(RED) # "a"

eq[2].set_color(BLUE) # "b"

eq[4].set_color(GREEN) # "c"

`

Combining Annotations

Layer multiple annotations for emphasis:

`python

Highlight a term, add a brace, and an arrow — in sequence

box = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)

brace = Brace(eq[2], DOWN, color=YELLOW)

label = brace.get_text("learning rate", font_size=18)

self.play(Create(box))

self.wait(0.5)

self.play(FadeOut(box), GrowFromCenter(brace), FadeIn(label))

self.wait(1.5)

self.play(FadeOut(brace), FadeOut(label))

`

The annotation lifecycle

Annotations should follow a rhythm:

1. Appear — draw attention (Create, GrowFromCenter)

2. Hold — viewer reads and understands (self.wait)

3. Disappear — clear the stage for the next thing (FadeOut)

Never leave annotations on screen indefinitely — they become visual noise once their purpose is served.