Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion manim/mobject/mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -2558,10 +2558,25 @@ def arrange(
direction: Vector3DLike = RIGHT,
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
center: bool = True,
aligned_to: int | None = None,
**kwargs: Any,
) -> Self:
"""Sorts :class:`~.Mobject` next to each other on screen.

Parameters
----------
direction
The direction along which submobjects are arranged.
buff
The distance between adjacent submobjects.
center
Whether to center the arranged group. Ignored when
``aligned_to`` is set.
aligned_to
If given, the index of the submobject that should remain
at its current position after arranging. All other
submobjects are rearranged around it. Overrides ``center``.

Examples
--------

Expand All @@ -2577,9 +2592,15 @@ def construct(self):
x = VGroup(s1, s2, s3, s4).set_x(0).arrange(buff=1.0)
self.add(x)
"""
if aligned_to is not None and self.submobjects:
anchor_pos = self.submobjects[aligned_to].get_center().copy()

for m1, m2 in zip(self.submobjects[:-1], self.submobjects[1:], strict=True):
m2.next_to(m1, direction, buff, **kwargs)
if center:

if aligned_to is not None and self.submobjects:
self.shift(anchor_pos - self.submobjects[aligned_to].get_center())
elif center:
self.center()
return self

Expand Down
79 changes: 78 additions & 1 deletion tests/module/mobject/mobject/test_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@
import numpy as np
import pytest

from manim import DL, PI, UR, Circle, Mobject, Rectangle, Square, Triangle, VGroup
from manim import (
DL,
DOWN,
LEFT,
PI,
RIGHT,
UR,
Circle,
Mobject,
Rectangle,
Square,
Triangle,
VGroup,
)


def test_mobject_add():
Expand Down Expand Up @@ -243,3 +256,67 @@ def test_apply_matrix_about_vertex_view():
# The first vertex should remain in the same position (within numerical precision)
transformed_vertices = triangle.get_vertices()
np.testing.assert_allclose(transformed_vertices[0], first_vertex, atol=1e-6)


def test_arrange_default():
"""Test that arrange() with default args still works."""
s1 = Square().move_to([-5, 3, 0])
s2 = Square().move_to([7, -2, 0])
group = VGroup(s1, s2)
group.arrange(RIGHT, buff=0.5)
# s2 should be to the right of s1
assert s2.get_center()[0] > s1.get_center()[0]


def test_arrange_aligned_to():
"""Test that arrange(aligned_to=i) keeps the i-th submobject in place."""
s1 = Square()
s2 = Square()
s3 = Square()

# Place s3 at a known position
s3.move_to([4, 4, 0])
original_s3_center = s3.get_center().copy()

group = VGroup(s1, s2, s3)
group.arrange(RIGHT, buff=0.5, aligned_to=2)

# s3 should remain at its original position
np.testing.assert_allclose(
group.submobjects[2].get_center(), original_s3_center, atol=1e-6
)
# s1 and s2 should be to the left of s3
assert group.submobjects[0].get_center()[0] < group.submobjects[2].get_center()[0]
assert group.submobjects[1].get_center()[0] < group.submobjects[2].get_center()[0]


def test_arrange_aligned_to_first():
"""Test that arrange(aligned_to=0) keeps the first submobject in place."""
s1 = Square().move_to([-3, 2, 0])
s2 = Square()
s3 = Square()

original_s1_center = s1.get_center().copy()

group = VGroup(s1, s2, s3)
group.arrange(DOWN, buff=1.0, aligned_to=0)

np.testing.assert_allclose(
group.submobjects[0].get_center(), original_s1_center, atol=1e-6
)


def test_arrange_aligned_to_negative_index():
"""Test that arrange(aligned_to=-1) works with negative indexing."""
s1 = Square()
s2 = Square()
s3 = Square().move_to([5, 5, 0])

original_last_center = s3.get_center().copy()

group = VGroup(s1, s2, s3)
group.arrange(LEFT, buff=0.5, aligned_to=-1)

np.testing.assert_allclose(
group.submobjects[-1].get_center(), original_last_center, atol=1e-6
)
Loading