Updater Usage¶
The Updater series of animation classes is a powerful feature in JAnim, including DataUpdater GroupUpdater ItemUpdater StepUpdater. We will introduce them one by one and cover several important features.
After learning DataUpdater, GroupUpdater, and ItemUpdater, you can “graduate” from the basic tutorials!
Warning
The Updater series of animation classes in JAnim differs significantly in concept from updater in Manim. Applying Manim concepts may lead to misunderstanding.
Usage of DataUpdater¶
DataUpdater is the most basic and widely applicable type of Updater. Its function is to modify items using time as a parameter:
square = Square()
self.play(
DataUpdater(
square,
lambda data, p: data.points.rotate(p.alpha * PI)
),
duration=3
)
Here, we pass a function to DataUpdater. This function rotates the vertices of the square to create an animation effect.
So how does this function work? Formally speaking, the format of DataUpdater is:
DataUpdater(<item>, lambda <initial_state>, <time_info>: <change initial state based on time info>)
Tip
You can choose to use either lambda functions or def functions depending on the specific use case.
For simple functionality, using lambda functions is more convenient.
Time information includes p.alpha representing the current animation progress, p.global_t representing the current global time, etc.
So, for lambda data, p: data.points.rotate(p.alpha * PI), the function’s purpose is:
Rotate the square from its initial animation state according to the animation progress. The more the animation progresses, the greater the rotation amount will be
Thus creating a rotating animation effect for the rectangle item.
Hint
You can use p.elapsed to know how long the animation has lasted until the current moment. This is shorthand for p.global_t - p.range.at.
Note that if the item passed to DataUpdater has child items, by default root_only=True only operates on the root item itself. If root_only=False is passed, the Updater effect will be applied to all its descendant items separately, but they will not be operated on as a whole.
To operate on an item and its descendant items as a whole, we need to introduce GroupUpdater, which we will cover in the next section.
Warning
In principle, functions passed to Updater such as DataUpdater and GroupUpdater should not produce “side effects”, meaning they should only change the state of data and avoid affecting other variables outside the function.
Usage of GroupUpdater¶
GroupUpdater is used in the same way as DataUpdater, both modifying items using time as a parameter.
But as mentioned in the previous section about DataUpdater, GroupUpdater focuses on operating on the passed item and its descendant items as a whole, which is practical when handling operations like “overall rotation” and “overall alignment”.
The following example demonstrates the difference between using DataUpdater and GroupUpdater for rotation:
squares1 = Square() * 2
squares1.points.arrange()
squares2 = squares1.copy()
group = Group(
Text('DataUpdater'), Text('GroupUpdater'),
squares1, squares2
).show()
group.points.arrange_in_grid(buff=LARGE_BUFF)
self.play(
DataUpdater(
squares1,
lambda data, p: data.points.rotate(p.alpha * PI),
root_only=False
),
GroupUpdater(
squares2,
lambda data, p: data.points.rotate(p.alpha * PI)
),
duration=4
)
Tip
When the same effect can be achieved (such as translation rather than rotation), DataUpdater will perform better than GroupUpdater.
Usage of current()¶
For functions passed to Updater, if you need to access the current state of other items that are animating during the animation process, you can add .current() after the corresponding item to get it.
Warning
If current() is not added, you will only get the final state of the corresponding item in the construct function, not the state during the animation process.
ArrowPointingExample ¶
dot1 = Dot(LEFT * 3)
dot2 = Dot()
arrow = Arrow(dot1, dot2, color=YELLOW)
self.show(dot1, dot2, arrow)
self.play(
dot2.update.points.rotate(TAU, about_point=RIGHT * 2),
GroupUpdater(
arrow,
lambda data, p:
data.set_start_and_end(
dot1.points.box.center,
dot2.current().points.box.center
)
),
duration=4
)
Hint
dot2.update.points.rotate(TAU, about_point=RIGHT * 2) is equivalent to
DataUpdater(
dot2,
lambda data, p: data.points.rotate(TAU * p.alpha, about_point=RIGHT * 2)
)
This is a simplified way of writing, but not all methods can be simplified this way.
In this example, we first make dot2 move around a circle.
Then in the Updater function of arrow, using .current() allows us to get the current position of dot2, so that the arrow always points to dot2.
Animation Combination¶
JAnim’s various Updater are not isolated. Not only can you use .current() to know the current animation state of other items, but you can also stack multiple Updater on one item, applying animation effects sequentially.
In the following example, we add a new Updater every two seconds to demonstrate the effect of “animation combination”:
CombineUpdatersExample ¶
square = Square()
square.points.to_border(LEFT)
# Here, a new Updater is accumulated for every `play`
# to show the effect of animation combination
self.play(
square.anim.points.to_border(RIGHT),
duration=2
)
###############################
square.points.to_border(LEFT)
self.play(
square.anim.points.to_border(RIGHT),
DataUpdater(
square,
lambda data, p: data.points.shift(UP * math.sin(p.alpha * 4 * PI)),
become_at_end=False
),
duration=2
)
###############################
square.points.to_border(LEFT)
self.play(
square.anim.points.to_border(RIGHT),
DataUpdater(
square,
lambda data, p: data.points.shift(UP * math.sin(p.alpha * 4 * PI)),
become_at_end=False
),
square.update(become_at_end=False).color.set(BLUE).r.points.rotate(-TAU),
duration=2
)
Tip
You can pass become_at_end=False to Updater to make the item return to its initial state after the animation.
But .anim does not have this parameter, so here we have square.points.to_border(LEFT) each time.
Warning
Animations created by .anim are overriding. When participating in “animation combination”, they should be placed at the beginning.
Here is another example of “animation combination”:
RotatingPieExample ¶
pie = Group(*[
Sector(start_angle=i * TAU / 4, angle=TAU / 4, radius=1.5, color=color, fill_alpha=1, stroke_alpha=0)
.points.shift(rotate_vector(UR * 0.05, i * TAU / 4))
.r
for i, color in enumerate([RED, PURPLE, MAROON, GOLD])
])
self.play(
GroupUpdater(
pie,
lambda data, p: data.points.rotate(p.alpha * TAU, about_point=ORIGIN),
duration=5
),
DataUpdater(
pie[0],
lambda data, p: data.points.shift(normalize(data.mark.get()) * p.alpha),
rate_func=there_and_back,
become_at_end=False,
at=2,
duration=2
)
)
See also:
Usage of ItemUpdater¶
ItemUpdater differs significantly from the two Updater introduced earlier. Functions passed to the previous two Updater receive two parameters data, p, but ItemUpdater only provides one parameter p, and directly renders the item returned by the function onto the frame.
The use case of ItemUpdater is to dynamically create items during animation for display, such as text with continuously changing values:
tr = ValueTracker(0)
txt = Text('0.00', font_size=40).show()
self.forward()
self.play(
Succession(
tr.anim.set_value(4),
tr.anim.set_value(2.5),
tr.anim.set_value(10)
),
ItemUpdater(
txt,
lambda p: Text(f'{tr.current().get_value():.2f}', font_size=40),
duration=3
)
)
self.forward()
UpdaterExample ¶
square = Square(fill_color=BLUE_E, fill_alpha=1).show()
brace = Brace(square, UP).show()
def text_updater(p: UpdaterParams):
cmpt = brace.current().points
return cmpt.create_text(f'Width = {cmpt.brace_length:.2f}')
self.prepare(
DataUpdater(
brace,
lambda data, p: data.points.match(square.current())
),
ItemUpdater(None, text_updater),
duration=10
)
self.forward()
self.play(square.anim.points.scale(2))
self.play(square.anim.points.scale(0.5))
self.play(square.anim.points.set_width(5, stretch=True))
w0 = square.points.box.width
self.play(
DataUpdater(
square,
lambda data, p: data.points.set_width(
w0 + 0.5 * w0 * math.sin(p.alpha * p.range.duration)
)
),
duration=5
)
self.forward()
See also:
Note
In principle, the item passed to ItemUpdater has no relationship with the animation process.
What ItemUpdater does, by default, is:
At the start of the animation, hide the passed item
During the animation, render the item returned by the function
After the animation ends, show the passed item and call the
become()method to change the passed item to the state at the last moment of the animation
So ItemUpdater can be used without passing an item, passing None is acceptable.
Usage of duration=FOREVER¶
We can use duration=FOREVER to create a continuously running Updater, for example:
square = Square().show()
self.forward()
self.prepare(
DataUpdater(
square,
lambda data, p: data.points.rotate(p.elapsed * 60 * DEGREES),
duration=FOREVER
)
)
self.prepare(
DataUpdater(
square,
lambda data, p: data.points.set_x(2 * math.sin(p.alpha * TAU)),
become_at_end=False
),
at=2,
)
self.forward(5)
Usage of StepUpdater¶
Update items step by step, suitable for scenarios “requiring updating the next state based on the previous state”, such as physics simulations or numerical demonstrations of differential equations.
The following is the simplest example (but also the least necessary to use StepUpdater):
NumberPlane(faded_line_ratio=1).show()
circle = Circle(0.5, color=YELLOW, fill_alpha=0.6).show()
self.forward()
self.play(
StepUpdater(
circle,
lambda data, p: data.points.shift(RIGHT / 50)
),
duration=2
)
self.forward()
In this example, the function of StepUpdater moves the circle to the right by 1/50 unit each time. Since StepUpdater executes 50 times per second by default, the circle moves 1 unit to the right per second, and after two seconds it moves 2 units to the right.
Note
Documentation needs to be improved