"""Contains classes to construct an array."""
from copy import deepcopy
import numpy as np
from manim import *
from .m_enum import MArrayDirection, MArrayElementComp
[docs]class MArrayElement(VGroup):
"""A class that represents an array element.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element label.
index_pos
Specifies the position of :attr:`__mob_index` w.r.t :attr:`__mob_square`
index_gap
Specifies the distance between :attr:`__mob_index` and :attr:`__mob_square`.
label_pos
Specifies the position of :attr:`__mob_label` w.r.t :attr:`__mob_square`.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_square`.
next_to_mob
Specifies the placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
next_to_dir
Specifies the direction of placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
Attributes
----------
__scene : :class:`~manim.scene.scene.Scene`
The scene where the object is to be rendered.
__mob_square_props : :class:`dict`
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
__mob_value_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
__mob_index_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
__mob_label_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element label.
__index_pos : :class:`np.ndarray`
The position of :attr:`__mob_index` w.r.t :attr:`__mob_square`
__index_gap : :class:`float`
The distance between :attr:`__mob_index` and :attr:`__mob_square`.
__label_pos : :class:`np.ndarray`
The position of :attr:`__mob_label` w.r.t :attr:`__mob_square`.
__label_gap : :class:`float`
The distance between :attr:`__mob_label` and :attr:`__mob_square`.
__mob_square : :class:`~manim.mobject.geometry.polygram.Square`
Represents the body of the element.
__mob_value : :class:`~manim.mobject.text.text_mobject.Text`
Represents the value of the element.
__mob_index : :class:`~manim.mobject.text.text_mobject.Text`
Represents the index of the element.
__mob_label : :class:`~manim.mobject.text.text_mobject.Text`
Represents the label of the element.
"""
def __init_props(
self,
scene: Scene,
index_pos: np.ndarray,
index_gap: float,
label_pos: np.ndarray,
label_gap: float,
) -> None:
"""Initializes the attributes for the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
index_pos
Specifies the position of :attr:`__mob_index` w.r.t :attr:`__mob_square`
index_gap
Specifies the distance between :attr:`__mob_index` and :attr:`__mob_square`.
label_pos
Specifies the position of :attr:`__mob_label` w.r.t :attr:`__mob_square`.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_square`.
"""
self.__mob_square_props: dict = {
"color": BLUE_B,
"fill_color": BLUE_D,
"fill_opacity": 1,
"side_length": 1,
}
self.__mob_value_props: dict = {"text": "", "color": WHITE, "weight": BOLD}
self.__mob_index_props: dict = {"text": "", "color": BLUE_D, "font_size": 32}
self.__mob_label_props: dict = {"text": "", "color": BLUE_A, "font_size": 38}
self.__scene: Scene = scene
self.__index_pos: np.ndarray = index_pos
self.__index_gap: float = index_gap
self.__label_pos: np.ndarray = label_pos
self.__label_gap: float = label_gap
def __update_props(
self,
mob_square_args: dict = {},
mob_value_args: dict = {},
mob_index_args: dict = {},
mob_label_args: dict = {},
) -> None:
"""Updates the attributes of the class.
Parameters
----------
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element label.
"""
self.__mob_square_props.update(mob_square_args)
self.__mob_value_props.update(mob_value_args)
self.__mob_index_props.update(mob_index_args)
self.__mob_label_props.update(mob_label_args)
if type(self.__mob_value_props["text"]) != str:
self.__mob_value_props["text"] = str(self.__mob_value_props["text"])
if type(self.__mob_index_props["text"]) != str:
self.__mob_index_props["text"] = str(self.__mob_index_props["text"])
if type(self.__mob_label_props["text"]) != str:
self.__mob_label_props["text"] = str(self.__mob_label_props["text"])
def __init_mobs(
self,
init_square: bool = False,
init_value: bool = False,
init_index: bool = False,
init_label: bool = False,
next_to_mob: "MArrayElement" = None,
next_to_dir: np.ndarray = RIGHT,
) -> None:
"""Initializes the mobjects for the class.
Parameters
----------
init_square
If `True`, instantiates a :class:`~manim.mobject.geometry.polygram.Square` and assigns it to :attr:`__mob_square`.
init_value
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_value`.
init_index
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_index`.
init_label
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_label`.
next_to_mob
Specifies placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
next_to_dir
Specifies direction of placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
"""
if init_square:
self.__mob_square: Square = Square(**self.__mob_square_props)
if next_to_mob is not None:
self.__mob_square.next_to(
next_to_mob.fetch_mob_square(), next_to_dir, 0
)
self.add(self.__mob_square)
if init_value:
self.__mob_value: Text = Text(**self.__mob_value_props)
self.__mob_value.next_to(self.__mob_square, np.array([0, 0, 0]), 0)
self.add(self.__mob_value)
if init_index:
self.__mob_index: Text = Text(**self.__mob_index_props)
self.__mob_index.next_to(
self.__mob_square, self.__index_pos, self.__index_gap
)
self.add(self.__mob_index)
if init_label:
self.__mob_label: Text = Text(**self.__mob_label_props)
self.__mob_label.next_to(
self.__mob_square, self.__label_pos, self.__label_gap
)
self.add(self.__mob_label)
def __deepcopy__(self, memo):
"""Deepcopy that excludes attributes specified in `exclude_list`."""
exclude_list = ["_MArrayElement__scene"]
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k not in exclude_list:
setattr(result, k, deepcopy(v, memo))
return result
def __init__(
self,
scene: Scene,
mob_square_args: dict = {},
mob_value_args: dict = {},
mob_index_args: dict = {},
mob_label_args: dict = {},
index_pos: np.ndarray = UP,
index_gap: float = 0.25,
label_pos: np.ndarray = LEFT,
label_gap: float = 0.5,
next_to_mob: "MArrayElement" = None,
next_to_dir: np.ndarray = RIGHT,
**kwargs
) -> None:
"""Initializes the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element label.
index_pos
Specifies the position of :attr:`__mob_index` w.r.t :attr:`__mob_square`
index_gap
Specifies the distance between :attr:`__mob_index` and :attr:`__mob_square`.
label_pos
Specifies the position of :attr:`__mob_label` w.r.t :attr:`__mob_square`.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_square`.
next_to_mob
Specifies the placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
next_to_dir
Specifies the direction of placement for :attr:`__mob_square` w.r.t another :class:`MArrayElement`.
"""
super().__init__(**kwargs)
# Initialize props
self.__init_props(scene, index_pos, index_gap, label_pos, label_gap)
# Update props
self.__update_props(
mob_square_args, mob_value_args, mob_index_args, mob_label_args
)
# Initialize mobjects
self.__init_mobs(True, True, True, True, next_to_mob, next_to_dir)
[docs] def fetch_mob_square(self) -> Square:
"""Fetches the square mobject.
Returns
-------
:class:`~manim.mobject.geometry.polygram.Square`
:attr:`__mob_square`.
"""
return self.__mob_square
[docs] def fetch_mob_value(self) -> Text:
"""Fetches the value mobject.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_value`.
"""
return self.__mob_value
[docs] def fetch_mob_index(self) -> Text:
"""Fetches the index mobject.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_index`.
"""
return self.__mob_index
[docs] def fetch_mob_label(self) -> Text:
"""Fetches the label mobject.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_label`.
"""
return self.__mob_label
[docs] def fetch_mob(self, mob_target: MArrayElementComp) -> Mobject:
"""Fetches the mobject based on the specified enum.
Parameters
----------
mob_target
Specifies the :class:`~manim.mobject.mobject.Mobject` to fetch.
Returns
-------
:class:`~manim.mobject.mobject.Mobject`
Mobject of the class.
"""
if mob_target == MArrayElementComp.BODY:
return self.fetch_mob_square()
elif mob_target == MArrayElementComp.VALUE:
return self.fetch_mob_value()
elif mob_target == MArrayElementComp.INDEX:
return self.fetch_mob_index()
elif mob_target == MArrayElementComp.LABEL:
return self.fetch_mob_label()
else:
return self
[docs] def update_mob_value(
self,
mob_value_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Re-intializes the value mobject.
Parameters
----------
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
update_anim
Animation to be applied to the updated :attr:`__mob_value`.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_value`.
"""
# Update props of mob_value
self.__update_props(mob_value_args=mob_value_args)
# Remove current mob_value
self.remove(self.__mob_value)
# Initialize new mob_value
self.__init_mobs(init_value=True)
# Add new mob_value to group
self.add(self.__mob_value)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_value, **update_anim_args), **play_anim_args
)
return self.__mob_value
[docs] def update_mob_index(
self,
mob_index_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Re-intializes the index mobject.
Parameters
----------
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
update_anim
Animation to be applied to the updated :attr:`__mob_index`.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_index`.
"""
# Update props of mob_index
self.__update_props(mob_index_args=mob_index_args)
# Remove current mob_index
self.remove(self.__mob_index)
# Initialize new mob_index
self.__init_mobs(init_index=True)
# Add new mob_index to group
self.add(self.__mob_index)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_index, **update_anim_args), **play_anim_args
)
return self.__mob_index
[docs] def update_mob_label(
self,
mob_label_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Re-intializes the label mobject.
Parameters
----------
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element label.
update_anim
Animation to be applied to the updated :attr:`__mob_label`.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_label`.
"""
# Update props of mob_label
self.__update_props(mob_label_args=mob_label_args)
# Remove current mob_label
self.remove(self.__mob_label)
# Initialize new mob_label
self.__init_mobs(init_label=True)
# Add new mob_label to group
self.add(self.__mob_label)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_label, **update_anim_args), **play_anim_args
)
return self.__mob_label
[docs] def animate_mob_square(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over square mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_square`.
"""
return self.__mob_square.animate
[docs] def animate_mob_value(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over value mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_value`.
"""
return self.__mob_value.animate
[docs] def animate_mob_index(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over index mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_index`.
"""
return self.__mob_index.animate
[docs] def animate_mob_label(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over label mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_label`.
"""
return self.__mob_label.animate
[docs]class MArray(VGroup):
"""A class that represents an array.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to represent.
label
Specifies the value of the array label.
index_offset
Specifies the difference between successive displayable indices.
index_start
Specifies the starting value of displayable index.
index_hex_display
If `True`, displays indices in hex.
hide_index
If `True`, doesn't display indices.
arr_dir
Specifies the growth direction of the array.
arr_label_pos
Specifies the position of :attr:`__mob_arr_label` w.r.t :attr:`__mob_arr`.
arr_label_gap
Specifies the distance between :attr:`__mob_arr_label` and :attr:`__mob_arr`.
mob_arr_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the array label.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
**kwargs
Forwarded to constructor of the parent.
Attributes
----------
__scene : :class:`~manim.scene.scene.Scene`
The scene where the object is to be rendered.
__arr : :class:`list`
The array to represent.
__label : :class:`str`
The value of the array label.
__index_offset : :class:`int`
The difference between successive displayable indices.
__index_start : :class:`int`
The starting value of displayable index.
__index_hex_display : :class:`bool`
If `True`, displays indices in hex.
__hide_index : :class:`bool`
If `True`, doesn't display indices.
__arr_dir : :class:`~.m_enum.MArrayDirection`
The growth direction of the array.
__arr_label_pos : :class:`~.m_enum.MArrayDirection`
The position of :attr:`__mob_arr_label` w.r.t :attr:`__mob_arr`.
__arr_label_gap : :class:`float`
The distance between :attr:`__mob_arr_label` and :attr:`__mob_arr`.
__mob_arr_label_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the array label.
__mob_arr : :class:`~typing.List`\0[:class:`MArrayElement`]
Represents the array.
__mob_arr_label : :class:`~manim.mobject.text.text_mobject.Text`
Represents the array label.
"""
__dir_map = [
{"arr": UP, "index": RIGHT},
{"arr": DOWN, "index": RIGHT},
{"arr": RIGHT, "index": UP},
{"arr": LEFT, "index": UP},
]
"""Maps :class:`~.m_enum.MArrayDirection` to :class:`np.ndarray`."""
def __sum_elem_len(self, index_start: int, index_end: int) -> int:
"""Sums the side_length of all elements' square mobject present in the array between the specified range.
Parameters
----------
index_start
Starting index of the range (inclusive).
index_end
Ending index of the range (inclusive).
Returns
-------
:class:`int`
Sum of `side_length`\0s of all :class:`~manim.mobject.geometry.polygram.Square` present inside :attr:`__mob_arr` in the specified range.
"""
if (
index_start < 0
or index_end < 0
or index_start > len(self.__mob_arr)
or index_end > len(self.__mob_arr)
):
raise Exception("Index out of bounds!")
total_len = 0
for i in range(index_start, index_end + 1):
total_len += self.__mob_arr[i].fetch_mob_square().side_length
return total_len
def __calc_label_pos_and_mob(self) -> typing.Tuple[Square, np.ndarray]:
"""Calculates the position of the array label relative to one of the element's square mobjects.
Returns
-------
:class:`~manim.mobject.geometry.polygram.Square`
Square mobject next to which the array label is positioned.
:class:`np.ndarray`
The relative position of the array label.
"""
# Label position is parallel to array growth direction
if np.array_equal(
self.__dir_map[self.__arr_label_pos.value]["arr"],
self.__dir_map[self.__arr_dir.value]["arr"],
):
return (
self.__mob_arr[-1].fetch_mob_square(),
self.__dir_map[self.__arr_label_pos.value]["arr"],
)
elif np.array_equal(
self.__dir_map[self.__arr_label_pos.value]["arr"],
-self.__dir_map[self.__arr_dir.value]["arr"],
):
return (
self.__mob_arr[0].fetch_mob_square(),
self.__dir_map[self.__arr_label_pos.value]["arr"],
)
# Label position is perpendicular to array growth direction
else:
middle_index = len_before = len_after = 0
if len(self.__mob_arr) > 1:
odd_indices = len(self.__mob_arr) % 2 == 1
middle_index = int(len(self.__mob_arr) / 2)
len_before = self.__sum_elem_len(0, middle_index - 1)
len_after = self.__sum_elem_len(
middle_index + 1 if odd_indices else middle_index,
len(self.__mob_arr) - 1,
)
return (
self.__mob_arr[middle_index].fetch_mob_square(),
self.__dir_map[self.__arr_label_pos.value]["arr"]
+ self.__dir_map[self.__arr_dir.value]["arr"]
* ((len_after - len_before) / 2),
)
def __calc_index(self, index: int) -> typing.Union[int, str]:
"""Calculates the displayable index of the specified element based on attributes set at initialization.
Parameters
----------
index
Specifies the index of the element for which to compute the displayable index.
Returns
-------
:data:`~typing.Union`\0[:class:`int`, :class:`str`]
Displayable index.
"""
return (
""
if self.__hide_index
else (
self.__index_start + self.__index_offset * index
if self.__index_hex_display is False
else hex(self.__index_start + self.__index_offset * index)
)
)
def __calc_index_pos(self) -> np.ndarray:
"""Calculates the index position of all elements based on attributes set at initialization.
Returns
-------
:class:`np.ndarray`
Index position.
"""
return (
self.__dir_map[self.__arr_dir.value]["index"]
if not self.__switch_index_pos
else self.__dir_map[self.__arr_dir.value]["index"] * -1
)
def __calc_label_shift_factor(self, mob: MArrayElement) -> float:
"""Calculates how much to shift the array label after insertion/removal of an element.
Parameters
----------
mob
Specifies the element that is inserted/removed.
Returns
-------
:class:`float`
Factor by which to shift the :attr:`__mob_arr_label`.
"""
if np.array_equal(
self.__dir_map[self.__arr_label_pos.value]["arr"],
self.__dir_map[self.__arr_dir.value]["arr"],
):
return mob.fetch_mob_square().side_length
elif not np.array_equal(
self.__dir_map[self.__arr_label_pos.value]["arr"],
-self.__dir_map[self.__arr_dir.value]["arr"],
):
return mob.fetch_mob_square().side_length / 2
return 0
def __append_elem(
self,
value,
shift_label: bool = True,
append_anim: Animation = Write,
append_anim_args: dict = {},
append_anim_target: MArrayElementComp = None,
mob_square_args: dict = {},
mob_value_args: dict = {},
mob_index_args: dict = {},
) -> typing.List[Animation]:
"""Creates and inserts a new element in the array.
Parameters
----------
value
Specifies the value of the new element.
shift_label
If `True`, shifts the :attr:`__mob_arr_label` to center of the array.
append_anim
Animation to be applied to the new element.
append_anim_args
Arguments for append :class:`~manim.animation.animation.Animation`.
append_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the append :class:`~manim.animation.animation.Animation` is to be played.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
Returns
-------
:data:`typing.List`\0[:class:`~manim.animation.animation.Animation`]
List of append animations.
"""
mob_value_args["text"] = value
mob_index_args["text"] = self.__calc_index(len(self.__mob_arr))
self.__mob_arr.append(
MArrayElement(
scene=self.__scene,
mob_square_args=mob_square_args,
mob_value_args=mob_value_args,
mob_index_args=mob_index_args,
index_pos=self.__calc_index_pos(),
next_to_mob=self.__mob_arr[-1] if len(self.__mob_arr) else None,
next_to_dir=self.__dir_map[self.__arr_dir.value]["arr"],
)
)
self.add(self.__mob_arr[-1])
anim_list = [
append_anim(
self.__mob_arr[-1].fetch_mob(append_anim_target), **append_anim_args
)
]
if shift_label:
label_shift_factor = self.__calc_label_shift_factor(self.__mob_arr[-1])
anim_list.append(
ApplyMethod(
self.__mob_arr_label.shift,
self.__dir_map[self.__arr_dir.value]["arr"] * label_shift_factor,
)
)
return anim_list
def __remove_elem(
self,
index: int,
removal_anim: Animation = FadeOut,
update_anim: Animation = Indicate,
removal_anim_args: dict = {},
update_anim_args: dict = {},
removal_anim_target: MArrayElementComp = None,
update_anim_target: MArrayElementComp = MArrayElementComp.INDEX,
) -> typing.Tuple[Succession, typing.Callable[[bool], typing.List[Animation]]]:
"""Removes the element from the array at the specified index.
Parameters
----------
index
Specifies the index of the element to remove.
removal_anim
Animation to be applied to the element being removed.
update_anim
Animation to be applied on remaining elements.
removal_anim_args
Arguments for removal :class:`~manim.animation.animation.Animation`.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
removal_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the removal :class:`~manim.animation.animation.Animation` is to be played.
update_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the update :class:`~manim.animation.animation.Animation` is to be played.
Returns
-------
:class:`~manim.animation.composition.Succession`
Contains :class:`~manim.animation.animation.Animation` played for removal and shifting of element(s).
:data:`~typing.Callable`\0[[:class:`bool`], :class:`~typing.List`\0[:class:`~manim.animation.animation.Animation`]]
Method that updates the indices of element(s) after the removed element and returns a list of update :class:`~manim.animation.animation.Animation`\0(s).
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
self.remove(self.__mob_arr[index])
removed_mob = self.__mob_arr[index]
self.__mob_arr = self.__mob_arr[0:index] + self.__mob_arr[index + 1 :]
anims_shift = []
for i in range(index, len(self.__mob_arr)):
anims_shift.append(
ApplyMethod(
self.__mob_arr[i].shift,
-(
self.__dir_map[self.__arr_dir.value]["arr"]
* removed_mob.fetch_mob_square().side_length
),
)
)
label_shift_factor = self.__calc_label_shift_factor(removed_mob)
if label_shift_factor != 0:
anims_shift.append(
ApplyMethod(
self.__mob_arr_label.shift,
-self.__dir_map[self.__arr_dir.value]["arr"] * label_shift_factor,
)
)
def update_indices(
play_anim: bool = True, play_anim_args: dict = {}
) -> typing.List[Animation]:
"""Updates the indices of :class:`MArrayElement`(s) that occur after the removal.
Parameters
----------
play_anim : :class:`bool`, default: `True`
Specifies whether to play the update :class:`manim.Animation`.
play_anim_args : :class:`dict, default: `{}`
Arguments for :meth:`manim.Scene.play`.
Returns
-------
List[:class:`manim.Animation`]
Represents :class:`Animation` for indices update.
"""
anims_index = []
for i in range(index, len(self.__mob_arr)):
self.__mob_arr[i].update_mob_index(
mob_index_args={"text": self.__calc_index(i)}, play_anim=False
)
anims_index.append(
update_anim(
(self.__mob_arr[i].fetch_mob(update_anim_target)),
**update_anim_args
)
)
if play_anim:
self.__scene.play(*anims_index, **play_anim_args)
return anims_index
return (
Succession(
removal_anim(
removed_mob.fetch_mob(removal_anim_target), **removal_anim_args
),
AnimationGroup(*anims_shift),
),
update_indices,
)
def __init_props(
self,
scene: Scene,
arr: list,
label: str,
index_offset: int,
index_start: int,
index_hex_display: bool,
hide_index: bool,
arr_dir: MArrayDirection,
switch_index_pos: bool,
arr_label_pos: MArrayDirection,
arr_label_gap: float,
) -> None:
"""Initializes the attributes for the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to represent.
label
Specifies the value of the array label.
index_offset
Specifies the difference between successive displayable indices.
index_start
Specifies the starting value of displayable index.
index_hex_display
If `True`, displays indices in hex.
hide_index
If `True`, doesn't display indices.
arr_dir
Specifies the growth direction of the array.
arr_label_pos
Specifies the position of :attr:`__mob_arr_label` w.r.t :attr:`__mob_arr`.
arr_label_gap
Specifies the distance between :attr:`__mob_arr_label` and :attr:`__mob_arr`.
"""
self.__mob_arr_label_props: dict = {
"text": "",
"color": BLUE_A,
"font_size": 38,
}
self.__scene: Scene = scene
self.__arr: typing.List[Any] = arr
self.__label: str = label
self.__mob_arr: typing.List[MArrayElement] = []
self.__index_offset: int = index_offset
self.__index_start: int = index_start
self.__index_hex_display: bool = index_hex_display
self.__hide_index: int = hide_index
self.__arr_dir: MArrayDirection = arr_dir
self.__switch_index_pos: bool = switch_index_pos
self.__arr_label_pos: MArrayDirection = arr_label_pos
self.__arr_label_gap: float = arr_label_gap
def __update_props(
self,
mob_arr_label_args: dict = {},
) -> None:
"""Updates the attributes of the class.
Parameters
----------
mob_arr_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the array label.
"""
self.__mob_arr_label_props["text"] = self.__label
self.__mob_arr_label_props.update(mob_arr_label_args)
if type(self.__mob_arr_label_props["text"]) != str:
self.__mob_arr_label_props["text"] = str(self.__mob_arr_label_props["text"])
def __init_mobs(
self,
init_arr_label: bool = False,
) -> None:
"""Initializes the mobjects for the class.
Parameters
----------
init_arr_label
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_arr_label`.
"""
if init_arr_label:
self.__mob_arr_label = Text(**self.__mob_arr_label_props)
if len(self.__mob_arr):
(next_to_mob, label_pos) = self.__calc_label_pos_and_mob()
self.__mob_arr_label.next_to(
next_to_mob, label_pos, self.__arr_label_gap
)
if len(self.__mob_arr) % 2 == 0:
self.__mob_arr_label.shift(
-self.__dir_map[self.__arr_dir.value]["arr"]
* (next_to_mob.side_length / 2)
)
self.add(self.__mob_arr_label)
def __deepcopy__(self, memo):
"""Deepcopy that excludes attributes specified in `exclude_list`."""
exclude_list = ["_MArray__scene"]
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k not in exclude_list:
setattr(result, k, deepcopy(v, memo))
return result
def __init__(
self,
scene: Scene,
arr: list = [],
label: str = "",
index_offset: int = 1,
index_start: int = 0,
index_hex_display: bool = False,
hide_index: bool = False,
arr_dir: MArrayDirection = MArrayDirection.RIGHT,
switch_index_pos: bool = False,
arr_label_pos: MArrayDirection = MArrayDirection.LEFT,
arr_label_gap: float = 0.5,
mob_arr_label_args: dict = {},
mob_square_args: dict = {},
mob_value_args: dict = {},
mob_index_args: dict = {},
**kwargs
) -> None:
"""Initializes the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to represent.
label
Specifies the value of the array label.
index_offset
Specifies the difference between successive displayable indices.
index_start
Specifies the starting value of displayable index.
index_hex_display
If `True`, displays indices in hex.
hide_index
If `True`, doesn't display indices.
arr_dir
Specifies the growth direction of the array.
arr_label_pos
Specifies the position of :attr:`__mob_arr_label` w.r.t :attr:`__mob_arr`.
arr_label_gap
Specifies the distance between :attr:`__mob_arr_label` and :attr:`__mob_arr`.
mob_arr_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the array label.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
**kwargs
Forwarded to constructor of the parent.
"""
super().__init__(**kwargs)
# Initialize props
self.__init_props(
scene,
arr,
label,
index_offset,
index_start,
index_hex_display,
hide_index,
arr_dir,
switch_index_pos,
arr_label_pos,
arr_label_gap,
)
# Update props
self.__update_props(mob_arr_label_args)
# Append elements to __mob_arr
for v in arr:
self.__append_elem(
v,
False,
mob_square_args=mob_square_args,
mob_value_args=mob_value_args,
mob_index_args=mob_index_args,
)
# Initialize other mobjects (e.g. __arr_label)
self.__init_mobs(True)
[docs] def fetch_arr(self) -> list:
"""Fetches the original array.
Returns
-------
:class:`list`
:attr:`__arr`.
"""
return self.__arr
[docs] def fetch_mob_arr(self) -> typing.List[MArrayElement]:
"""Fetches the mobject array.
Returns
-------
:class:`~typing.List`
:attr:`__mob_arr`.
"""
return self.__mob_arr
[docs] def fetch_mob_arr_label(self) -> Text:
"""Fetches the label mobject of the array.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_arr_label`.
"""
return self.__mob_arr_label
[docs] def fetch_arr_dir(self) -> MArrayDirection:
"""Fetches the growth direction enum of the array.
Returns
-------
:class:`~.m_enum.MArrayDirection`
:attr:`__arr_dir`.
"""
return self.__arr_dir
[docs] def update_elem_value(
self,
index: int,
value,
mob_value_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Updates the elements value.
Parameters
----------
index
Specifies the index of element whose value to update.
value
New value to be assigned to the element.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
update_anim
Animation to be applied to the updated element.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated element's value mobject.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
self.__arr[index] = value
mob_value_args["text"] = value
return self.__mob_arr[index].update_mob_value(
mob_value_args, update_anim, update_anim_args, play_anim, play_anim_args
)
[docs] def update_elem_index(
self,
index: int,
value,
mob_index_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Updates the elements index.
Parameters
----------
index
Specifies the index of element whose index to update.
value
New value to be assigned to the index of the element.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
update_anim
Animation to be applied to the updated element.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated element's index mobject.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
mob_index_args["text"] = value
return self.__mob_arr[index].update_mob_index(
mob_index_args, update_anim, update_anim_args, play_anim, play_anim_args
)
[docs] def update_mob_arr_label(
self,
label: str,
mob_arr_label_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Updates the array label.
Parameters
----------
label
New value to be assigned to the array label.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the array label.
update_anim
Animation to be applied to the updated array label.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_arr_label`.
"""
self.__label = label
# Update props of mob_label
self.__update_props(mob_arr_label_args=mob_arr_label_args)
# Remove current mob_label
self.remove(self.__mob_arr_label)
# Initialize new mob_label
self.__init_mobs(init_arr_label=True)
# Add new mob_label to group
self.add(self.__mob_arr_label)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_arr_label, **update_anim_args), **play_anim_args
)
return self.__mob_arr_label
[docs] def animate_elem(self, index: int) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over element mobject specified.
Parameters
----------
index
Specifies the index of the element to animate.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :class:`MArrayElement`.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
return self.__mob_arr[index].animate
[docs] def animate_elem_square(self, index: int) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over square mobject of the specified element.
Parameters
----------
index
Specifies the index of the element who's square mobject to animate.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :class:`~manim.mobject.geometry.polygram.Square`.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
return self.__mob_arr[index].animate_mob_square()
[docs] def animate_elem_value(self, index: int) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over value mobject of the specified element.
Parameters
----------
index
Specifies the index of the element who's value mobject animate.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :class:`~manim.mobject.text.text_mobject.Text`.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
return self.__mob_arr[index].animate_mob_value()
[docs] def animate_elem_index(self, index: int) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over index mobject of the specified element.
Parameters
----------
index
Specifies the index of the element who's index mobject animate.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :class:`~manim.mobject.text.text_mobject.Text`.
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
return self.__mob_arr[index].animate_mob_index()
[docs] def append_elem(
self,
value: Any,
append_anim: Animation = Write,
append_anim_args: dict = {},
append_anim_target: MArrayElementComp = None,
mob_square_args: dict = {},
mob_value_args: dict = {},
mob_index_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> typing.List[Animation]:
"""Creates and inserts a new element in the array.
Parameters
----------
value
Specifies the value of the new element.
append_anim
Animation to be applied to the new element.
append_anim_args
Arguments for append :class:`~manim.animation.animation.Animation`.
append_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the append :class:`~manim.animation.animation.Animation` is to be played.
mob_square_args
Arguments for :class:`~manim.mobject.geometry.polygram.Square` that represents the element body.
mob_value_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element value.
mob_index_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the element index.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`typing.List`\0[:class:`~manim.animation.animation.Animation`]
List of append animations.
"""
self.__arr.append(value)
anim_list = self.__append_elem(
value,
mob_square_args=mob_square_args,
mob_value_args=mob_value_args,
mob_index_args=mob_index_args,
append_anim=append_anim,
append_anim_args=append_anim_args,
append_anim_target=append_anim_target,
)
if play_anim:
self.__scene.play(*anim_list, **play_anim_args)
return anim_list
[docs] def remove_elem(
self,
index: int,
removal_anim: Animation = FadeOut,
update_anim: Animation = Indicate,
removal_anim_args: dict = {},
update_anim_args: dict = {},
removal_anim_target: MArrayElementComp = None,
update_anim_target: MArrayElementComp = MArrayElementComp.INDEX,
play_anim: bool = True,
play_anim_args: dict = {},
) -> typing.Tuple[Succession, typing.Callable[[bool], typing.List[Animation]]]:
"""Removes the element from the array at the specified index.
Parameters
----------
index
Specifies the index of the element to remove.
removal_anim
Animation to be applied to the element being removed.
update_anim
Animation to be applied on remaining elements.
removal_anim_args
Arguments for removal :class:`~manim.animation.animation.Animation`.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
removal_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the removal :class:`~manim.animation.animation.Animation` is to be played.
update_anim_target
Specifies the target :class:`~manim.mobject.mobject.Mobject` of the :class:`MArrayElement` on which the update :class:`~manim.animation.animation.Animation` is to be played.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.animation.composition.Succession`
Contains :class:`~manim.animation.animation.Animation` played for removal and shifting of element(s).
:data:`~typing.Callable`\0[[:class:`bool`], :class:`~typing.List`\0[:class:`~manim.animation.animation.Animation`]]
Method that updates the indices of element(s) after the removed element and returns a list of update :class:`~manim.animation.animation.Animation`\0(s).
"""
if index < 0 or index > len(self.__mob_arr):
raise Exception("Index out of bounds!")
self.__arr = self.__arr[0:index] + self.__arr[index + 1 :]
(remove_anim, update_indices) = self.__remove_elem(
index,
removal_anim,
update_anim,
removal_anim_args,
update_anim_args,
removal_anim_target,
update_anim_target,
)
if play_anim:
self.__scene.play(remove_anim, **play_anim_args)
update_indices(play_anim_args=play_anim_args)
return (remove_anim, update_indices)
[docs]class MArrayPointer(VGroup):
"""A class that represents a pointer.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the pointer is to be attached.
index
Specifies the index of the element to which the pointer is to be attached.
label
Specifies the value of the pointer label.
arrow_len
Specifies the length of :attr:`__mob_arrow`.
arrow_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__arr`.
label_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__mob_label`.
pointer_pos
Specifies the position of the pointer w.r.t to :attr:`__arr`.
mob_arrow_args
Arguments for :class:`~manim.mobject.geometry.line.Arrow` that represents the pointer arrow.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the pointer label.
**kwargs
Forwarded to constructor of the parent.
Attributes
----------
__scene : :class:`~manim.scene.scene.Scene`
The scene where the object is to be rendered.
__arr : :class:`~typing.List`\0[:class:`MArrayElement`]
The array to which the pointer is attached to.
__index : :class:`int`
The index of the element to which the pointer is attached to.
__label : :class:`str`
The value of the pointer label.
__arrow_len : :class:`float`
The length of :attr:`__mob_arrow`.
__arrow_gap : :class:`float`
The distance between :attr:`__mob_arrow` and :attr:`__arr`.
__label_gap : :class:`float`
The distance between :attr:`__mob_arrow` and :attr:`__mob_label`.
__pointer_pos : :class:`.m_enum.MArrayDirection`
The position of the pointer w.r.t to :attr:`__arr`.
__mob_arrow_props : :class:`dict`
Arguments for :class:`~manim.mobject.geometry.line.Arrow` that represents the pointer arrow.
__mob_label_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the pointer label.
__mob_arrow : :class:`~manim.mobject.geometry.line.Arrow`
Represents the arrow of the element.
__mob_label : :class:`~manim.mobject.text.text_mobject.Text`
Represents the label of the element.
__updater_pos : :data:`typing.Callable`\0[[], None]
The updater function that keeps the pointer intact with the array.
"""
__dir_map = [
{"np": UP, "dir": MArrayDirection.UP},
{"np": DOWN, "dir": MArrayDirection.DOWN},
{"np": RIGHT, "dir": MArrayDirection.RIGHT},
{"np": LEFT, "dir": MArrayDirection.LEFT},
]
"""Maps :class:`~.m_enum.MArrayDirection` to :class:`np.ndarray`."""
def __calc_arrow_pos(self) -> np.ndarray:
"""Calculates direction vector for the arrow mobject.
Returns
-------
:class:`np.ndarray`
Position vector for :attr:`__mob_arrow`.
"""
arr_dir_np = self.__dir_map[self.__arr.fetch_arr_dir().value]["np"]
arrow_pos_np = np.copy(self.__dir_map[self.__pointer_pos.value]["np"])
# If array's direction and pointer's direction are not perpendicular to each other
if np.dot(arr_dir_np, arrow_pos_np):
# swap the x and y values of arrow_pos_np
arrow_pos_np[0], arrow_pos_np[1] = arrow_pos_np[1], arrow_pos_np[0]
# update the __pointer_pos accordingly
self.__pointer_pos = self.__dir_map[
(self.__pointer_pos.value + 2) % len(self.__dir_map)
]["dir"]
return arrow_pos_np
def __add_updater(self) -> None:
"""Attaches the position updater function with the pointer."""
def updater_pos(mob: Mobject) -> None:
self.__init_pos()
self.__updater_pos = updater_pos
self.add_updater(self.__updater_pos)
def __remove_updater(self) -> None:
"""Removes the attached position updater function from the pointer."""
self.remove_updater(self.__updater_pos)
def __calc_shift_np(self, new_index: int) -> np.ndarray:
"""Calculates how much the pointer should shift by to point to the new index.
Parameters
----------
new_index
Specifies the prospective index of element to which the pointer is to be attached.
Returns
-------
:class:`np.ndarray`
A vector that represents how much the pointer should shift.
"""
to_lesser_index = False
index_start = self.__index
index_end = new_index
if index_start > index_end:
index_start, index_end = index_end, index_start
to_lesser_index = True
return (
(
self.__arr._MArray__sum_elem_len(index_start, index_end)
- (
self.__arr.fetch_mob_arr()[self.__index]
.fetch_mob_square()
.side_length
)
)
* self.__dir_map[self.__arr.fetch_arr_dir().value]["np"]
* (-1 if to_lesser_index else 1)
)
def __init_props(
self,
scene: Scene,
arr: MArray,
index: int,
label: str,
arrow_len: float,
arrow_gap: float,
label_gap: float,
pointer_pos: MArrayDirection,
) -> None:
"""Initializes the attributes for the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the pointer is to be attached.
index
Specifies the index of the element to which the pointer is to be attached.
label
Specifies the value of the pointer label.
arrow_len
Specifies the length of :attr:`__mob_arrow`.
arrow_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__arr`.
label_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__mob_label`.
pointer_pos
Specifies the position of the pointer w.r.t to :attr:`__arr`.
"""
self.__mob_arrow_props: dict = {"color": GOLD_D}
self.__mob_label_props: dict = {"text": label, "color": GOLD_A, "font_size": 38}
self.__scene: Scene = scene
self.__arr: MArray = arr
if index >= len(self.__arr.fetch_mob_arr()) or index < 0:
raise Exception("Index out of bounds!")
self.__index: int = index
self.__label: str = label
self.__arrow_len: float = arrow_len
self.__arrow_gap: float = arrow_gap
self.__label_gap: float = label_gap
self.__pointer_pos: MArrayDirection = pointer_pos
def __update_props(
self, mob_arrow_args: dict = {}, mob_label_args: dict = {}
) -> None:
"""Updates the attributes of the class.
Parameters
----------
mob_arrow_args
Arguments for :class:`~manim.mobject.geometry.line.Arrow` that represents the pointer arrow.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the pointer label.
"""
self.__mob_arrow_props.update(mob_arrow_args)
self.__mob_label_props["text"] = self.__label
self.__mob_label_props.update(mob_label_args)
if type(self.__mob_label_props["text"]) != str:
self.__mob_label_props["text"] = str(self.__mob_label_props["text"])
def __init_mobs(self, init_arrow: bool = False, init_label: bool = False) -> None:
"""Initializes the mobjects for the class.
Parameters
----------
init_arrow
If `True`, instantiates a :class:`~manim.mobject.geometry.line.Arrow` and assigns it to :attr:`__mob_arrow`.
init_label
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_label`.
"""
if init_arrow:
arrow_pos_np = self.__calc_arrow_pos()
self.__mob_arrow = Arrow(
start=(-arrow_pos_np + (arrow_pos_np * self.__arrow_len)),
end=-arrow_pos_np,
**self.__mob_arrow_props
)
self.__mob_arrow.next_to(
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square(),
arrow_pos_np,
self.__arrow_gap,
)
self.add(self.__mob_arrow)
if init_label:
self.__mob_label = Text(**self.__mob_label_props)
self.__mob_label.next_to(
self.__mob_arrow,
self.__dir_map[self.__pointer_pos.value]["np"],
self.__label_gap,
)
self.add(self.__mob_label)
def __init_pos(self) -> None:
"""Initializes the position of the object"""
arrow_pos_np = self.__calc_arrow_pos()
self.next_to(
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square(),
arrow_pos_np,
self.__arrow_gap,
)
def __deepcopy__(self, memo):
"""Deepcopy that excludes attributes specified in `exclude_list`."""
exclude_list = ["_MArrayPointer__scene", "_MArrayPointer__arr"]
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k not in exclude_list:
setattr(result, k, deepcopy(v, memo))
return result
def __init__(
self,
scene: Scene,
arr: MArray,
index: int = 0,
label: str = "",
arrow_len: float = 1,
arrow_gap: float = 0.25,
label_gap: float = 0.25,
pointer_pos: MArrayDirection = MArrayDirection.DOWN,
mob_arrow_args: dict = {},
mob_label_args: dict = {},
**kwargs
) -> None:
"""Initializes the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the pointer is to be attached.
index
Specifies the index of the element to which the pointer is to be attached.
label
Specifies the value of the pointer label.
arrow_len
Specifies the length of :attr:`__mob_arrow`.
arrow_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__arr`.
label_gap
Specifies the distance between :attr:`__mob_arrow` and :attr:`__mob_label`.
pointer_pos
Specifies the position of the pointer w.r.t to :attr:`__arr`.
mob_arrow_args
Arguments for :class:`~manim.mobject.geometry.line.Arrow` that represents the pointer arrow.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the pointer label.
**kwargs
Forwarded to constructor of the parent.
"""
super().__init__(**kwargs)
# Initialize props
self.__init_props(
scene, arr, index, label, arrow_len, arrow_gap, label_gap, pointer_pos
)
# Update props
self.__update_props(mob_arrow_args, mob_label_args)
# Initialize mobjects
self.__init_mobs(True, True)
# Add updater
self.__add_updater()
[docs] def fetch_mob_arrow(self) -> Arrow:
"""Fetches the arrow mobject of the pointer.
Returns
-------
:class:`~manim.mobject.geometry.line.Arrow`
:attr:`__mob_arrow`.
"""
return self.__mob_arrow
[docs] def fetch_mob_label(self) -> Text:
"""Fetches the label mobject of the pointer.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_label`.
"""
return self.__mob_label
[docs] def fetch_index(self) -> int:
"""Fetches the index that the pointer is attached to.
Returns
-------
:class:`int`
:attr:`__index`.
"""
return self.__index
[docs] def update_mob_label(
self,
label: str,
mob_label_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Updates the pointer label.
Parameters
----------
label
New value to be assigned to the pointer label.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the pointer label.
update_anim
Animation to be applied to the updated pointer label.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_label`.
"""
self.__label = label
# Update props of mob_label
self.__update_props(mob_label_args=mob_label_args)
# Remove current mob_label
self.remove(self.__mob_label)
# Initialize new mob_label
self.__init_mobs(init_label=True)
# Add new mob_label to group
self.add(self.__mob_label)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_label, **update_anim_args), **play_anim_args
)
return self.__mob_label
[docs] def animate_mob_arrow(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over arrow mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_arrow`.
"""
return self.__mob_arrow.animate
[docs] def animate_mob_label(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over label mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_label`.
"""
return self.__mob_label.animate
[docs] def shift_to_elem(
self, index: int, play_anim: bool = True, play_anim_args: dict = {}
) -> ApplyMethod:
"""Shifts pointer to the specified element.
Parameters
----------
index
Specifies the index of the element to which the pointer is to be shifted.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.animation.transform.ApplyMethod`
Shift animation.
"""
if index < 0 or index > len(self.__arr.fetch_mob_arr()):
raise Exception("Index out of bounds!")
shift_anim = ApplyMethod(
self.shift, self.__calc_shift_np(index), suspend_mobject_updating=True
)
self.__index = index
if play_anim:
self.__scene.play(shift_anim, **play_anim_args)
return shift_anim
[docs] def attach_to_elem(self, index: int) -> None:
"""Attaches pointer to the specified element.
Parameters
----------
index
Specifies the index of the element to which the pointer is to be attached.
"""
if index < 0 or index > len(self.__arr.fetch_mob_arr()):
raise Exception("Index out of bounds!")
self.__index = index
self.__init_pos()
[docs]class MArraySlidingWindow(VGroup):
"""A class that represents a sliding window
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the sliding window is to be attached.
index
Specifies the index of the element to which the sliding window is to be attached.
size
Specifies the number of elements the sliding window should enclose.
label
Specifies the value of the sliding window label.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_window`.
label_pos
Specifies the position of the pointer w.r.t to :attr:`__mob_window`.
mob_window_args
Arguments for :class:`~manim.mobject.geometry.polygram.Rectangle` that represents the window.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the window label.
**kwargs
Forwarded to constructor of the parent.
Attributes
----------
__scene : :class:`~manim.scene.scene.Scene`
The scene where the object is to be rendered.
__arr : :class:`~typing.List`\0[:class:`MArrayElement`]
The array to which the sliding window is to be attached.
__index : :class:`int`
The index of the element to which the sliding window is to be attached.
__size : :class:`int`
The number of elements the sliding window should enclose.
__label : :class:`str`
The value of the sliding window label.
__label_gap : :class:`float`
The distance between :attr:`__mob_label` and :attr:`__mob_window`.
__label_pos : :class:`.m_enum.MArrayDirection`
The position of the pointer w.r.t to :attr:`__mob_window`.
__mob_window_props : :class:`dict`
Arguments for :class:`~manim.mobject.geometry.polygram.Rectangle` that represents the window.
__mob_label_props : :class:`dict`
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the window label.
__mob_window : :class:`~manim.mobject.geometry.polygram.Rectangle`
Represents the window of the sliding window.
__mob_label : :class:`~manim.mobject.text.text_mobject.Text`
Represents the label of the sliding window.
__updater_pos : :data:`typing.Callable`\0[[], None]
The updater function that keeps the sliding window intact with the array.
"""
__dir_map = [
{"np": UP, "dir": MArrayDirection.UP},
{"np": DOWN, "dir": MArrayDirection.DOWN},
{"np": RIGHT, "dir": MArrayDirection.RIGHT},
{"np": LEFT, "dir": MArrayDirection.LEFT},
]
"""Maps :class:`~.m_enum.MArrayDirection` to :class:`np.ndarray`."""
def __calc_window_dim(self) -> typing.Tuple[float, float]:
"""Calculates dimensions of window mobject.
Returns
-------
:class:`float`
Height of :attr:`__mob_window`.
:class:`float`
Width of :attr:`__mob_window`.
"""
height = self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square().side_length
width = self.__arr._MArray__sum_elem_len(
self.__index, self.__index + self.__size - 1
)
if self.__arr.fetch_arr_dir() in (MArrayDirection.UP, MArrayDirection.DOWN):
height, width = width, height
return (height, width)
def __calc_window_pos_np(self) -> typing.Tuple[np.ndarray, np.ndarray]:
"""Calculates position vector and align vector for the window mobject.
Returns
-------
:class:`np.ndarray`
Position vector for :attr:`__mob_window`
:class:`np.ndarray`
Align vector for :attr:`__mob_window`
"""
point_np = (
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square().get_left()
)
align_np = LEFT
arr_dir = self.__arr.fetch_arr_dir()
if arr_dir == MArrayDirection.LEFT:
point_np = (
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square().get_right()
)
align_np = RIGHT
elif arr_dir == MArrayDirection.UP:
point_np = (
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square().get_bottom()
)
align_np = DOWN
elif arr_dir == MArrayDirection.DOWN:
point_np = (
self.__arr.fetch_mob_arr()[self.__index].fetch_mob_square().get_top()
)
align_np = UP
return (point_np, align_np)
def __calc_label_pos_np(self) -> np.ndarray:
"""Calculates position vector for the label mobject.
Returns
-------
:class:`np.ndarray`
Position vector for :attr:`__mob_label`
"""
arr_dir = self.__arr.fetch_arr_dir()
# Label position is parallel to array growth direction
if np.array_equal(
self.__dir_map[self.__label_pos.value]["np"],
self.__dir_map[arr_dir.value]["np"],
) or np.array_equal(
self.__dir_map[self.__label_pos.value]["np"],
-self.__dir_map[arr_dir.value]["np"],
):
return self.__dir_map[(self.__label_pos.value + 2) % len(self.__dir_map)][
"np"
]
# Label position is perpendicular to array growth direction
else:
return self.__dir_map[self.__label_pos.value]["np"]
def __pos_mobs(self, pos_window: bool = False, pos_label: bool = False) -> None:
"""Positions mobjects of the class.
Parameters
----------
pos_window
If `True`, correctly positions :attr:`__mob_window`.
pos_label
If `True`, correctly positions :attr:`__mob_label`.
"""
if pos_window:
point_np, align_np = self.__calc_window_pos_np()
self.__mob_window.move_to(point_np, align_np)
if pos_label:
self.__mob_label.next_to(
self.__mob_window,
self.__calc_label_pos_np(),
self.__label_gap,
)
def __add_updater(self) -> None:
"""Attaches the position updater function with the pointer."""
def updater_pos(mob: Mobject) -> None:
self.__init_pos()
self.__updater_pos = updater_pos
self.add_updater(self.__updater_pos)
def __remove_updater(self) -> None:
"""Removes the attached position updater function from the pointer."""
self.remove_updater(self.__updater_pos)
def __init_props(
self,
scene: Scene,
arr: MArray,
index: int,
size: int,
label: str,
label_gap: float,
label_pos: MArrayDirection,
) -> None:
"""Initializes the attributes for the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the sliding window is to be attached.
index
Specifies the index of the element to which the sliding window is to be attached.
size
Specifies the number of elements the sliding window should enclose.
label
Specifies the value of the sliding window label.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_window`.
label_pos
Specifies the position of the pointer w.r.t to :attr:`__mob_window`.
"""
self.__mob_window_props: dict = {"color": RED_D, "stroke_width": 10}
self.__mob_label_props: dict = {"text": label, "color": RED_A, "font_size": 38}
self.__scene: Scene = scene
self.__arr: MArray = arr
if index >= len(self.__arr.fetch_mob_arr()) or index < 0:
raise Exception("Index out of bounds!")
self.__index: int = index
if size < 1 or index + size > len(self.__arr.fetch_mob_arr()):
raise Exception("Invalid window size!")
self.__size: int = size
self.__label: str = label
self.__label_gap: float = label_gap
self.__label_pos: MArrayDirection = label_pos
def __update_props(
self, mob_window_args: dict = {}, mob_label_args: dict = {}
) -> None:
"""Updates the attributes of the class.
Parameters
----------
mob_window_args
Arguments for :class:`~manim.mobject.geometry.polygram.Rectangle` that represents the window.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the window label.
"""
self.__mob_window_props.update(mob_window_args)
self.__mob_label_props["text"] = self.__label
self.__mob_label_props.update(mob_label_args)
if type(self.__mob_label_props["text"]) != str:
self.__mob_label_props["text"] = str(self.__mob_label_props["text"])
def __init_mobs(self, init_window: bool = False, init_label: bool = False) -> None:
"""Initializes the mobjects for the class.
Parameters
----------
init_arrow
If `True`, instantiates a :class:`~manim.mobject.geometry.polygram.Rectangle` and assigns it to :attr:`__mob_window`.
init_label
If `True`, instantiates a :class:`~manim.mobject.text.text_mobject.Text` and assigns it to :attr:`__mob_label`.
"""
if init_window:
height, width = self.__calc_window_dim()
self.__mob_window = Rectangle(
height=height, width=width, **self.__mob_window_props
)
self.__pos_mobs(pos_window=True)
self.add(self.__mob_window)
if init_label:
self.__mob_label = Text(**self.__mob_label_props)
self.__pos_mobs(pos_label=True)
self.add(self.__mob_label)
def __init_pos(self) -> None:
"""Initializes the position of the object"""
self.__pos_mobs(True, True)
def __deepcopy__(self, memo):
"""Deepcopy that excludes attributes specified in `exclude_list`."""
exclude_list = ["_MArraySlidingWindow__scene", "_MArraySlidingWindow__arr"]
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
if k not in exclude_list:
setattr(result, k, deepcopy(v, memo))
return result
def __init__(
self,
scene: Scene,
arr: MArray,
index: int = 0,
size: int = 1,
label: str = "",
label_gap: float = 0.5,
label_pos: MArrayDirection = MArrayDirection.DOWN,
mob_window_args: dict = {},
mob_label_args: dict = {},
**kwargs
) -> None:
"""Initializes the class.
Parameters
----------
scene
Specifies the scene where the object is to be rendered.
arr
Specifies the array to which the sliding window is to be attached.
index
Specifies the index of the element to which the sliding window is to be attached.
size
Specifies the number of elements the sliding window should enclose.
label
Specifies the value of the sliding window label.
label_gap
Specifies the distance between :attr:`__mob_label` and :attr:`__mob_window`.
label_pos
Specifies the position of the pointer w.r.t to :attr:`__mob_window`.
mob_window_args
Arguments for :class:`~manim.mobject.geometry.polygram.Rectangle` that represents the window.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the window label.
**kwargs
Forwarded to constructor of the parent.
"""
super().__init__(**kwargs)
# Initialize props
self.__init_props(scene, arr, index, size, label, label_gap, label_pos)
# Update props
self.__update_props(mob_window_args, mob_label_args)
# Initialize mobjects
self.__init_mobs(True, True)
# Add updater
self.__add_updater()
[docs] def fetch_mob_window(self) -> Rectangle:
"""Fetches the window mobject of the sliding window.
Returns
-------
:class:`~manim.mobject.geometry.polygram.Rectangle`
:attr:`__mob_window`.
"""
return self.__mob_window
[docs] def fetch_mob_label(self) -> Text:
"""Fetches the label mobject of the sliding window.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
:attr:`__mob_label`.
"""
return self.__mob_label
[docs] def update_mob_label(
self,
label: str,
mob_label_args: dict = {},
update_anim: Animation = Write,
update_anim_args: dict = {},
play_anim: bool = True,
play_anim_args: dict = {},
) -> Text:
"""Updates the window label.
Parameters
----------
label
New value to be assigned to the window label.
mob_label_args
Arguments for :class:`~manim.mobject.text.text_mobject.Text` that represents the window label.
update_anim
Animation to be applied to the updated window label.
update_anim_args
Arguments for update :class:`~manim.animation.animation.Animation`.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.mobject.text.text_mobject.Text`
Updated :attr:`__mob_label`.
"""
self.__label = label
# Update props of mob_label
self.__update_props(mob_label_args=mob_label_args)
# Remove current mob_label
self.remove(self.__mob_label)
# Initialize new mob_label
self.__init_mobs(init_label=True)
# Add new mob_label to group
self.add(self.__mob_label)
# Animate change
if play_anim:
self.__scene.play(
update_anim(self.__mob_label, **update_anim_args), **play_anim_args
)
return self.__mob_label
[docs] def animate_mob_window(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over window mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_window`.
"""
return self.__mob_window.animate
[docs] def animate_mob_label(self) -> "_AnimationBuilder": # type: ignore
"""Invokes the animate property over label mobject.
Returns
-------
:class:`_AnimationBuilder`
Animate property of :attr:`__mob_label`.
"""
return self.__mob_label.animate
[docs] def shift_to_elem(
self, index: int, play_anim: bool = True, play_anim_args: dict = {}
) -> ApplyFunction:
"""Shifts sliding window to the specified element.
Parameters
----------
index
Specifies the index of the element to which the sliding window is to be shifted.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.animation.transform.ApplyFunction`
Shift animation.
"""
if index >= len(self.__arr.fetch_mob_arr()) or index < 0:
raise Exception("Index out of bounds!")
if self.__size < 1 or index + self.__size > len(self.__arr.fetch_mob_arr()):
raise Exception("Invalid window size!")
self.__index = index
return self.resize_window(self.__size, play_anim, play_anim_args)
[docs] def attach_to_elem(self, index: int) -> None:
"""Attaches pointer to the specified element.
Parameters
----------
index
Specifies the index of the element to which the sliding window is to be attached.
"""
if index >= len(self.__arr.fetch_mob_arr()) or index < 0:
raise Exception("Index out of bounds!")
if self.__size < 1 or index + self.__size > len(self.__arr.fetch_mob_arr()):
raise Exception("Invalid window size!")
self.__index = index
self.__init_pos()
[docs] def resize_window(
self, size: int, play_anim: bool = True, play_anim_args: dict = {}
) -> ApplyFunction:
"""Expands or shrinks the window according to the specified size.
Parameters
----------
size
Specifies the number of elements the sliding window should enclose.
play_anim
If `True`, plays the animation(s).
play_anim_args
Arguments for :py:meth:`Scene.play() <manim.scene.scene.Scene.play>`.
Returns
-------
:class:`~manim.animation.transform.ApplyFunction`
Resize animation.
"""
if size < 1 or self.__index + size > len(self.__arr.fetch_mob_arr()):
raise Exception("Invalid window size!")
self.__size = size
# Variables for resize_and_shift method
arr_dir = self.__arr.fetch_arr_dir()
height, width = self.__calc_window_dim()
window_pos_np, window_align_np = self.__calc_window_pos_np()
label_pos_np = self.__calc_label_pos_np()
def resize_and_shift(mob: MArraySlidingWindow) -> MArraySlidingWindow:
"""Resizes and shifts the sliding window
Returns
-------
:class:`MArraySlidingWindow`
Represents the modified mobject.
"""
if arr_dir in (MArrayDirection.UP, MArrayDirection.DOWN):
mob.__mob_window.stretch_to_fit_height(height)
else:
mob.__mob_window.stretch_to_fit_width(width)
mob.__mob_window.move_to(window_pos_np, window_align_np)
mob.__mob_label.next_to(mob.__mob_window, label_pos_np, mob.__label_gap)
return mob
resize_anim = ApplyFunction(
resize_and_shift, self, suspend_mobject_updating=True
)
if play_anim:
self.__scene.play(resize_anim, **play_anim_args)
return resize_anim