diff --git a/CHANGELOG.md b/CHANGELOG.md index 726acc6..511e29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.5] - 2022-12-10 + +### PRs + +- [#1](https://github.com/drageelr/manim-data-structures/pull/1): + - `MVariable` added. + - `MArray.remove_elem()` added. + - `MArray.append_elem()` changed. + - `MArray` label and fetch method added. + - Generic `MArrayElement` sub-mobjects fetch method added. + - `MArrayElementComp` enum added. + - Changed `array.py` to `m_array.py`. + - Documentation updated. + ## [0.1.4] - 2022-11-27 ### Added diff --git a/docs/source/guides/arrays.rst b/docs/source/guides/arrays.rst index 3893ba7..0576856 100644 --- a/docs/source/guides/arrays.rst +++ b/docs/source/guides/arrays.rst @@ -1,7 +1,7 @@ Animating Arrays ================ -.. currentmodule:: manim_data_structures.array +.. currentmodule:: manim_data_structures.m_array Manim Array - MArray -------------------- @@ -39,6 +39,7 @@ Animating MArray To animate the :py:class:`MArray`, simply invoke the ``animate`` property as shown below: .. code-block:: python + :linenos: self.play(arr.animate.shift(UP * 2 + LEFT * 5)) @@ -62,6 +63,7 @@ To animate the :py:class:`MArray`, simply invoke the ``animate`` property as sho Moreover, you can also use the :py:func:`MArray.animate_elem` method to animate a single element of the :py:class:`MArray` as well: .. code-block:: python + :linenos: self.play(arr.animate_elem(1).shift(DOWN)) @@ -85,6 +87,7 @@ Moreover, you can also use the :py:func:`MArray.animate_elem` method to animate Lastly, you can also animate the body, value and the index of any element using the :py:func:`MArray.animate_elem_square`, :py:func:`MArray.animate_elem_value` and :py:func:`MArray.animate_elem_index` respectively. .. code-block:: python + :linenos: self.play( arr.animate_elem_square(1).set_fill(BLACK), @@ -119,6 +122,7 @@ Customizing MArray The :py:class:`MArray` also allows you to alter the way your array looks. While creating your array pass arguments to ``Square`` (used to represent the element body) and ``Text`` (used to represent the element value and index) mobjects. .. code-block:: python + :linenos: arr = MArray( [1, 2, 3], @@ -206,7 +210,71 @@ To do this, simply pass your preferred direction enum from :py:class:`MArrayDire self.wait(1) -.. currentmodule:: manim_data_structures.array +Array Label +^^^^^^^^^^^ + +.. currentmodule:: manim_data_structures.m_array + +For an :py:class:`MArray`, you can also a label with the array via specifying the ``label`` argument. + +.. currentmodule:: manim_data_structures.m_enum + +Similar to how we specify the growth direction using :py:class:`MArrayDirection` enum, we can dictate the position of the label. + +.. code-block:: python + :linenos: + + class MyScene(Scene): + def construct(self): + arr_label_left = MArray([1, 2, 3], label='Arr') + arr_label_right = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.RIGHT) + arr_label_down = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.DOWN) + arr_label_up = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.UP, arr_label_gap=0.75) + + self.play(Create(arr_label_left)) + self.play(arr_label_left.animate.shift(UP * 2 + LEFT * 4)) + self.play(Create(arr_label_right)) + self.play(arr_label_right.animate.shift(DOWN * 2 + LEFT * 4)) + self.play(Create(arr_label_down)) + self.play(arr_label_down.animate.shift(UP * 2 + RIGHT)) + self.play(Create(arr_label_up)) + self.play(arr_label_up.animate.shift(DOWN * 2 + RIGHT)) + + self.wait(1) + +.. raw:: html + +
+ +.. manim:: MyScene + :hide_source: + :quality: low + + from manim_data_structures import * + + class MyScene(Scene): + def construct(self): + arr_label_left = MArray([1, 2, 3], label='Arr') + arr_label_right = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.RIGHT) + arr_label_down = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.DOWN) + arr_label_up = MArray([1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.UP, arr_label_gap=0.75) + + self.play(Create(arr_label_left)) + self.play(arr_label_left.animate.shift(UP * 2 + LEFT * 4)) + self.play(Create(arr_label_right)) + self.play(arr_label_right.animate.shift(DOWN * 2 + LEFT * 4)) + self.play(Create(arr_label_down)) + self.play(arr_label_down.animate.shift(UP * 2 + RIGHT)) + self.play(Create(arr_label_up)) + self.play(arr_label_up.animate.shift(DOWN * 2 + RIGHT)) + + self.wait(1) + +.. currentmodule:: manim_data_structures.m_array + +.. note:: + + The ``arr_label_gap`` argument specifies the distance between the :py:class:`MArrayElement` 's :py:class:`manim.Square` and the array label itself. Hex Indices ^^^^^^^^^^^ @@ -247,7 +315,7 @@ Lets say you want to show a 4-byte integer array with its addresses. You can sim self.wait(1) Hide Indices -^^^^^^^^^^^^^^^ +^^^^^^^^^^^^ Or if you don't want to show the indices at all, simply pass ``True`` as the ``hide_index`` argument to the constructor @@ -297,10 +365,10 @@ For an existing array, you can also append an element simply by invoking the :py class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) self.add(arr) self.wait(1) - self.play(Write(arr.append_elem(4))) + self.play(*arr.append_elem(4)) self.wait(1) .. raw:: html @@ -315,16 +383,118 @@ For an existing array, you can also append an element simply by invoking the :py class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) self.add(arr) self.wait(1) - self.play(Write(arr.append_elem(4))) + self.play(*arr.append_elem(4)) self.wait(1) .. note:: You can also pass ``mob_*_args`` to this method to customize the inserted element. +Did you notice the the ``*`` before we invoked the :py:func:`MArray.append_elem` method? Since the method returns a list of :py:class:`manim.Animation` therefore, we unpack it while feeding it to the ``self.play`` method of the ``Scene``. + +Moreover, you can also specify the animation that is played for the inserted element via the ``append_anim`` argument. The code snippet below passes the :py:class:`manim.GrowFromCenter` animation to the :py:class:`MArray.append_elem` method: + +.. code-block:: python + :linenos: + + self.play(*arr.append_elem(4, append_anim=GrowFromCenter)) + +.. raw:: html + +
+ +.. manim:: MyScene + :hide_source: + :quality: low + + from manim_data_structures import * + + class MyScene(Scene): + def construct(self): + arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + self.add(arr) + self.wait(1) + self.play(*arr.append_elem(4, append_anim=GrowFromCenter)) + self.wait(1) + +.. currentmodule:: manim_data_structures.m_enum + +.. note:: + + You can also specify arguments to the passed animation via the ``append_anim_args`` parameter and also set the target of the animation using the ``append_anim_target`` parameter that takes in :py:class:`MArrayElementComp` enum. + +Remove Element +^^^^^^^^^^^^^^ + +.. currentmodule:: manim_data_structures.m_array + +To remove an element simply invoke the :py:class:`MArray.remove_elem` method with the index of element you wish to remove. The method returns two the removal animation and a function that udpates the indices of the remaining elements. + +.. code-block:: python + :linenos: + + (remove_anim, update_indices) = arr.remove_elem(1) + self.play(remove_anim) + self.play(*update_indices()) + +.. raw:: html + +
+ +.. manim:: MyScene + :hide_source: + :quality: low + + from manim_data_structures import * + + class MyScene(Scene): + def construct(self): + arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + self.add(arr) + self.wait(1) + (remove_anim, update_indices) = arr.remove_elem(1) + self.play(remove_anim) + self.play(*update_indices()) + self.wait(1) + +Similar to how you were able to pass the append animation to the :py:class:`MArray.append_elem` function, you can specify two animations for the :py:class:`MArray.remove_elem` method: +1. Element removal animation via the ``removal_anim`` parameter. +2. Indices update animation via the ``update_anim`` parameter. + +The code snippet below provides an example: + +.. code-block:: python + :linenos: + + (remove_anim, update_indices) = arr.remove_elem(1, removal_anim=ShowPassingFlash , update_anim=Indicate) + +.. raw:: html + +
+ +.. manim:: MyScene + :hide_source: + :quality: low + + from manim_data_structures import * + + class MyScene(Scene): + def construct(self): + arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + self.add(arr) + self.wait(1) + (remove_anim, update_indices) = arr.remove_elem(1, removal_anim=ShowPassingFlash , update_anim=Indicate) + self.play(remove_anim) + self.play(*update_indices()) + self.wait(1) + +.. note:: + + You can also specify arguments to the passed animation via the ``*_anim_args`` parameter and also set the target of the animation using the ``*_anim_target`` parameter. + Update Element ^^^^^^^^^^^^^^ diff --git a/docs/source/index.rst b/docs/source/index.rst index 33ca3f4..9a27f02 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,6 +26,32 @@ To import the package in your script, add the following import statement: from manim_data_structures import * +Variables +~~~~~~~~~ + +.. manim:: VarScene + :save_last_frame: + + from manim_data_structures import * + + class VarScene(Scene): + def construct(self): + var = MVariable(10, 0, 'Var') + self.add(var) + +Arrays +~~~~~~ + +.. manim:: ArrayScene + :save_last_frame: + + from manim_data_structures import * + + class ArrayScene(Scene): + def construct(self): + arr = MArray([1, 2, 3], label='Arr') + self.add(arr) + Next Steps ---------- diff --git a/docs/source/reference/arrays.rst b/docs/source/reference/arrays.rst index ef7cf36..8b054cf 100644 --- a/docs/source/reference/arrays.rst +++ b/docs/source/reference/arrays.rst @@ -6,5 +6,5 @@ Arrays .. autosummary:: :toctree: generated - ~array.MArrayElement - ~array.MArray + ~m_array.MArrayElement + ~m_array.MArray diff --git a/docs/source/reference/enums.rst b/docs/source/reference/enums.rst index db73378..105e0f5 100644 --- a/docs/source/reference/enums.rst +++ b/docs/source/reference/enums.rst @@ -7,3 +7,4 @@ Enums :toctree: generated ~m_enum.MArrayDirection + ~m_enum.MArrayElementComp diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index 5618f02..c093185 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -9,5 +9,6 @@ Module Index .. toctree:: :maxdepth: 2 + variables arrays enums diff --git a/docs/source/reference/variables.rst b/docs/source/reference/variables.rst new file mode 100644 index 0000000..af05f0b --- /dev/null +++ b/docs/source/reference/variables.rst @@ -0,0 +1,9 @@ +Variables +========= + +.. currentmodule:: manim_data_structures + +.. autosummary:: + :toctree: generated + + ~m_variable.MVariable diff --git a/pyproject.toml b/pyproject.toml index 9c29220..7cc1eb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "manim-data-structures" -version = "0.1.4" +version = "0.1.5" description = "A Manim implementation for data structures" authors = ["Hammad Nasir "] readme = "README.md" diff --git a/src/manim_data_structures/__init__.py b/src/manim_data_structures/__init__.py index bc8e73b..e8d6b71 100644 --- a/src/manim_data_structures/__init__.py +++ b/src/manim_data_structures/__init__.py @@ -1,6 +1,13 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" -from .array import * +from .m_array import * from .m_enum import * +from .m_variable import * -__all__ = ["MArrayElement", "MArray", "MArrayDirection"] +__all__ = [ + "MArrayElement", + "MArray", + "MArrayDirection", + "MArrayElementComp", + "MVariable", +] diff --git a/src/manim_data_structures/array.py b/src/manim_data_structures/m_array.py similarity index 52% rename from src/manim_data_structures/array.py rename to src/manim_data_structures/m_array.py index 226dd60..ef555bd 100644 --- a/src/manim_data_structures/array.py +++ b/src/manim_data_structures/m_array.py @@ -3,7 +3,7 @@ import numpy as np from manim import * -from .m_enum import MArrayDirection +from .m_enum import MArrayDirection, MArrayElementComp class MArrayElement(VGroup): @@ -21,6 +21,10 @@ class MArrayElement(VGroup): Specifies the position of :attr:`__mob_index` index_gap : :class:`float`, default: `0.25` Specifies the distance between :attr:`__mob_square` and :attr:`__mob_index` + label_pos : :class:`np.ndarray`, default: `LEFT` + Specifies the position of :attr:`__mob_label` + label_gap : :class:`float`, default: `0.5` + Specifies the distance between :attr:`__mob_square` and :attr:`__mob_label` next_to_mob : :class:`MArrayElement`, default: `None` Specifies placement for :attr:`__mob_square` next_to_dir : :class:`np.ndarray`, default: `RIGHT` @@ -44,9 +48,19 @@ class MArrayElement(VGroup): Specifies the position of :attr:`__mob_index` __index_gap : :class:`float` Specifies the distance between :attr:`__mob_square` and :attr:`__mob_index` + __label_pos : :class:`np.ndarray`, default: `LEFT` + Specifies the position of :attr:`__mob_label` + __label_gap : :class:`float`, default: `0.5` + Specifies the distance between :attr:`__mob_square` and :attr:`__mob_label` """ - def __init_props(self, index_pos: np.ndarray, index_gap: float) -> None: + def __init_props( + self, + index_pos: np.ndarray, + index_gap: float, + label_pos: np.ndarray, + label_gap: float, + ) -> None: """Initializes the attributes for the class. Parameters @@ -55,6 +69,10 @@ def __init_props(self, index_pos: np.ndarray, index_gap: float) -> None: Specifies the position of :attr:`__mob_index` index_gap : :class:`float` Specifies the distance between :attr:`__mob_square` and :attr:`__mob_index` + label_pos : :class:`np.ndarray` + Specifies the position of :attr:`__mob_label` + label_gap : :class:`float` + Specifies the distance between :attr:`__mob_square` and :attr:`__mob_label` """ self.__mob_square_props = { @@ -65,14 +83,18 @@ def __init_props(self, index_pos: np.ndarray, index_gap: float) -> None: } self.__mob_value_props = {"text": "", "color": WHITE, "weight": BOLD} self.__mob_index_props = {"text": "", "color": BLUE_D, "font_size": 32} + self.__mob_label_props = {"text": "", "color": BLUE_A, "font_size": 38} self.__index_pos = index_pos self.__index_gap = index_gap + self.__label_pos = label_pos + self.__label_gap = 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. @@ -84,11 +106,14 @@ def __update_props( Arguments for :class:`manim.Text` that represents the element value. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.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"]) @@ -96,11 +121,15 @@ def __update_props( 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: @@ -114,6 +143,8 @@ def __init_mobs( Instantiates a :class:`manim.Text` and adds it to :attr:`__mob_value`. init_index : :class:`bool`, default: `False` Instantiates a :class:`manim.Text` and adds it to :attr:`__mob_index`. + init_label : :class:`bool`, default: `False` + Instantiates a :class:`manim.Text` and adds it to :attr:`__mob_label`. next_to_mob : :class:`MArrayElement`, default: `None` Specifies placement for :attr:`__mob_square` next_to_dir : :class:`np.ndarray`, default: `RIGHT` @@ -140,13 +171,23 @@ def __init_mobs( ) self.add(self.__mob_index) + if init_label: + self.__mob_label = 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 __init__( self, 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 @@ -161,26 +202,34 @@ def __init__( Arguments for :class:`manim.Text` that represents the element value. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element label. index_pos : :class:`np.ndarray`, default: `UP` - Specifies the position of :attr:`__mob_index` + Specifies the position of :attr:`__mob_index`. index_gap : :class:`float`, default: `0.25` - Specifies the distance between :attr:`__mob_square` and :attr:`__mob_index` + Specifies the distance between :attr:`__mob_square` and :attr:`__mob_index`. + label_pos : :class:`np.ndarray`, default: `LEFT` + Specifies the position of :attr:`__mob_label`. + label_gap : :class:`float`, default: `0.5` + Specifies the distance between :attr:`__mob_square` and :attr:`__mob_label` next_to_mob : :class:`MArrayElement`, default: `None` - Specifies placement for :attr:`__mob_square` + Specifies placement for :attr:`__mob_square`. next_to_dir : :class:`np.ndarray`, default: `RIGHT` - Specifies direction of placement for :attr:`__mob_square` + Specifies direction of placement for :attr:`__mob_square`. """ super().__init__(**kwargs) # Initialize props - self.__init_props(index_pos, index_gap) + self.__init_props(index_pos, index_gap, label_pos, label_gap) # Update props - self.__update_props(mob_square_args, mob_value_args, mob_index_args) + self.__update_props( + mob_square_args, mob_value_args, mob_index_args, mob_label_args + ) # Initialize mobjects - self.__init_mobs(True, True, True, next_to_mob, next_to_dir) + self.__init_mobs(True, True, True, True, next_to_mob, next_to_dir) def fetch_mob_square(self) -> Square: """Fetches the :class:`manim.Square` that represents the element body. @@ -215,6 +264,42 @@ def fetch_mob_index(self) -> Text: return self.__mob_index + def fetch_mob_label(self) -> Text: + """Fetches the :class:`manim.Text` that represents the element label. + + Returns + ------- + :class:`manim.Text` + Represents the element label. + """ + + return self.__mob_label + + def fetch_mob(self, mob_target: MArrayElementComp) -> Mobject: + """Fetches :class:`manim.Mobject` based on enum :class:`m_enum.MArrayElementComp`. + + Parameters + ---------- + mob_target : :class:`m_enum.MArrayElementComp` + Specifies the component of :class:`MArrayElement` to fetch. + + Returns + ------- + :class:`manim.Mobject` + Represents the component of :class:`MArrayElement`. + """ + + 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 + def update_mob_value(self, mob_value_args: dict = {}) -> Text: """Re-intializes the :class:`manim.Text` that represents the element value. @@ -255,6 +340,26 @@ def update_mob_index(self, mob_index_args: dict = {}) -> Text: self.add(self.__mob_index) return self.__mob_index + def update_mob_label(self, mob_label_args: dict = {}) -> Text: + """Re-intializes the :class:`manim.Text` that represents the element label. + + Parameters + ---------- + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element label. + + Returns + ------- + :class:`manim.Text` + Represents the updated element label. + """ + + self.__update_props(mob_label_args=mob_label_args) + self.remove(self.__mob_label) + self.__init_mobs(init_label=True) + self.add(self.__mob_label) + return self.__mob_label + def animate_mob_square(self) -> "_AnimationBuilder": # type: ignore """Invokes the :meth:`manim.Square.animate` property of :class:`manim.Square` for the element body. @@ -288,6 +393,17 @@ def animate_mob_index(self) -> "_AnimationBuilder": # type: ignore return self.__mob_index.animate + def animate_mob_label(self) -> "_AnimationBuilder": # type: ignore + """Invokes the :meth:`manim.Text.animate` property of :class:`manim.Text` for the element label. + + Returns + ------- + :class:`_AnimationBuilder` + Value returned by :meth:`manim.Text.animate` property of :class:`manim.Text`. + """ + + return self.__mob_label.animate + class MArray(VGroup): """A class that represents an array. @@ -331,6 +447,7 @@ class MArray(VGroup): Specifies whether to display indices or not. __arr_dir : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.RIGHT` Specifies the growing direction of array. + __mob_arr_label_props """ __dir_map = [ @@ -341,6 +458,127 @@ class MArray(VGroup): ] """Maps :class:`.m_enum.MArrayDirection` to correct :class:`MArrayElement` placement.""" + def __init_props(self) -> None: + """Initializes the attributes for the class.""" + + self.__mob_arr_label_props = {"text": "", "color": BLUE_A, "font_size": 38} + + def __update_props( + self, + label: str = "", + mob_arr_label_args: dict = {}, + ) -> None: + """Updates the attributes of the class. + + Parameters + ---------- + label : :class:`str`, default `''` + Specifies the textual value for :attr:`__mob_arr_label` + mob_arr_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the array label. + """ + + self.__mob_arr_label_props["text"] = 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 __sum_elem_len(self, index_start: int, index_end: int) -> int: + """Sums the length of :class:`manim.Square` elements between the specified bound. + + Parameters + ---------- + index_start : :class:`int` + Starting index of the bound (inclusive). + index_end : :class:`int` + Ending index of the bound (inclusive). + + Returns + ------- + :class:`int` + Total length of the elements. + """ + + 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 label relative to :class:`MArrayElement` 's :class:`manim.Square` and returns them. + + Returns + ------- + :class:`Manim.Square` + Represents the :class:`manim.Mobject` next to which the label is positioned. + :class:`np.ndarray` + Represents the relative label's position. + """ + + # 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: + 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, 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 __init_mobs( + self, + init_arr_label: bool = False, + ) -> None: + """Initializes the :class:`Mobject`s for the class. + + Parameters + ---------- + init_arr_label : :class:`bool`, default: `False` + Instantiates a :class:`manim.Text` and adds 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 + ) + self.add(self.__mob_arr_label) + def __calc_index(self, index: int) -> typing.Union[int, str]: """Calculates and returns the index based on attributes set at initialization. @@ -380,25 +618,68 @@ def __calc_index_pos(self) -> np.ndarray: 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 :attr:`__mob_arr_label` after insertion/removal of an :class:`MArrayElement`. + + Parameters + ---------- + mob : :class:`MArrayElement` + The element that has been inserted or removed. + + Returns + ------- + :class:`int` + 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 = {}, - ) -> None: + ) -> typing.List[Animation]: """Creates a new :class:`MArrayElement` and appends it to :attr:`__mob_arr`. Parameters ---------- value Value to append. + shift_label: :class:`bool`, default: `True` + Specifies whether to shift the :class:`__mob_arr_label` or not. + append_anim : :class:`manim.Animation`, default: :class:`manim.Write` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement` being appended. + append_anim_args : :class:`dict`, default: `{}` + Arguments for append :class:`manim.Animation`. + append_anim_target : :class:`.m_enum.MArrayElementComp`, default: `None` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the append :class:`manim.Animation` is to be played. mob_square_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Square` that represents the element body of :class:`MArrayElement`. mob_value_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element value of :class:`MArrayElement`. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index of :class:`MArrayElement`. + + Returns + ------- + List[:class:`manim.Animation`] + List of animations for appending. """ mob_value_args["text"] = value @@ -415,15 +696,135 @@ def __append_elem( ) 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 = Write, + removal_anim_args: dict = {}, + update_anim_args: dict = {}, + removal_anim_target: MArrayElementComp = None, + update_anim_target: MArrayElementComp = MArrayElementComp.INDEX, + ) -> typing.Tuple[Succession, typing.Callable[[], typing.List[Animation]]]: + """Removes the :class:`MArrayElement` from :attr:`__mob_arr` at the specified index. + + Parameters + ---------- + index : :class:`int` + Index of :class:`MArrayElement` to remove. + removal_anim : :class:`manim.Animation`, default: :class:`manim.FadeOut` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement` being removed. + update_anim : :class:`manim.Animation`, default: :class:`manim.Write` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement`(s) after the removed element. + removal_anim_args : :class:`dict`, default: `{}` + Arguments for removal :class:`manim.Animation`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + removal_anim_target : :class:`.m_enum.MArrayElementComp`, default: `None` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the removal :class:`manim.Animation` is to be played. + update_anim_target : :class:`.m_enum.MArrayElementComp`, default: :attr:`.m_enum.MArrayElementComp.INDEX` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the update :class:`manim.Animation` is to be played. + + Returns + ------- + :class:`manim.Succession` + Contains :class:`manim.Animations` played for removal and shifting of :class:`MArrayElement`. + Callable[[], List[:class:`manim.Animation`]] + Method that updates the indices of :class:`MArrayElement`(s) that occur after the removal and returns a list of update :class:`manim.Animation`(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() -> typing.List[Animation]: + """Updates the indices of :class:`MArrayElement`(s) that occur after the removal. + + 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)} + ) + anims_index.append( + update_anim( + (self.__mob_arr[i].fetch_mob(update_anim_target)), + **update_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__( self, arr: list = [], + label="", 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 = {}, @@ -445,6 +846,12 @@ def __init__( Specifies whether to display indices or not. arr_dir : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.RIGHT` Specifies the growing direction of array. + arr_label_pos : :class:`.enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.LEFT` + Specifies the position of :attr:`__mob_arr_label`. + arr_label_gap : :class:`float`, default: `0.5` + Specifies the distance between :attr:`__mob_arr` and :attr:`__mob_arr_label`. + mob_arr_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the label for :class:`MArray`. mob_square_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Square` that represents the element body of :class:`MArrayElement`. mob_value_args : :class:`dict`, default: `{}` @@ -464,9 +871,22 @@ def __init__( self.__hide_index = hide_index self.__arr_dir = arr_dir self.__switch_index_pos = switch_index_pos + self.__arr_label_pos = arr_label_pos + self.__arr_label_gap = arr_label_gap + + self.__init_props() + self.__update_props(label=label, mob_arr_label_args=mob_arr_label_args) for v in arr: - self.__append_elem(v, mob_square_args, mob_value_args, mob_index_args) + self.__append_elem( + v, + False, + mob_square_args=mob_square_args, + mob_value_args=mob_value_args, + mob_index_args=mob_index_args, + ) + + self.__init_mobs(True) def update_elem_value(self, index: int, value, mob_value_args: dict = {}) -> Text: """Updates the elements value. @@ -596,31 +1016,102 @@ def animate_elem_index(self, index: int) -> "_AnimationBuilder": # type: ignore def append_elem( self, value, + 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 = {}, - ) -> MArrayElement: + ) -> typing.List[Animation]: """Appends the `value` to :attr:`__arr` and creates a new :class:`MArrayElement` and appends it to :attr:`__mob_arr`. Parameters ---------- value Value to append. + append_anim : :class:`manim.Animation`, default: :class:`manim.Write` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement` being appended. + append_anim_args : :class:`dict`, default: `{}` + Arguments for append :class:`manim.Animation`. + append_anim_target : :class:`.m_enum.MArrayElementComp`, default: `None` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the append :class:`manim.Animation` is to be played. mob_square_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Square` that represents the element body of :class:`MArrayElement`. mob_value_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element value of :class:`MArrayElement`. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index of :class:`MArrayElement`. + shift_label: :class:`bool`, default: `True` + Specifies whether to shift the :class:`__mob_arr_label` or not. Returns ------- - :class:`MArrayElement` - Represents the appended element. + List[:class:`manim.Animation`] + List of animations for appending. """ + self.__arr.append(value) - self.__append_elem(value, mob_square_args, mob_value_args, mob_index_args) - return self.__mob_arr[-1] + return 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, + ) + + def remove_elem( + self, + index, + removal_anim: Animation = FadeOut, + update_anim: Animation = Write, + removal_anim_args: dict = {}, + update_anim_args: dict = {}, + removal_anim_target: MArrayElementComp = None, + update_anim_target: MArrayElementComp = MArrayElementComp.INDEX, + ) -> typing.Tuple[Succession, typing.Callable[[], typing.List[Animation]]]: + """Removes the element from :attr:`__arr` and removes :class:`MArrayElement` from :attr:`__mob_arr` at the specified index. + + Parameters + ---------- + index : :class:`int` + Index of :class:`MArrayElement` to remove. + removal_anim : :class:`manim.Animation`, default: :class:`manim.FadeOut` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement` being removed. + update_anim : :class:`manim.Animation`, default: :class:`manim.Write` + Specifies the :class:`manim.Animation` to be played on the :class:`MArrayElement`(s) after the removed element. + removal_anim_args : :class:`dict`, default: `{}` + Arguments for removal :class:`manim.Animation`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + removal_anim_target : :class:`.m_enum.MArrayElementComp`, default: `None` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the removal :class:`manim.Animation` is to be played. + update_anim_target : :class:`.m_enum.MArrayElementComp`, default: :attr:`.m_enum.MArrayElementComp.INDEX` + Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the update :class:`manim.Animation` is to be played. + + Returns + ------- + :class:`manim.Succession` + Contains :class:`manim.Animations` played for removal and shifting of :class:`MArrayElement`. + Callable[[], List[:class:`manim.Animation`]] + Method that updates the indices of :class:`MArrayElement`(s) that occur after the removal and returns a list of update :class:`manim.Animation`(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 :] + + return self.__remove_elem( + index, + removal_anim, + update_anim, + removal_anim_args, + update_anim_args, + removal_anim_target, + update_anim_target, + ) def fetch_arr(self) -> list: """Fetches :attr:`__arr`. @@ -643,3 +1134,14 @@ def fetch_mob_arr(self) -> typing.List[MArrayElement]: """ return self.__mob_arr + + def fetch_arr_label(self) -> Text: + """Fetches the :class:`manim.Text` that represents the array label. + + Returns + ------- + :class:`manim.Text` + Represents the array label. + """ + + return self.__mob_arr_label diff --git a/src/manim_data_structures/m_enum.py b/src/manim_data_structures/m_enum.py index e5f3ecc..d4b1015 100644 --- a/src/manim_data_structures/m_enum.py +++ b/src/manim_data_structures/m_enum.py @@ -3,6 +3,22 @@ from enum import Enum +class MArrayElementComp(Enum): + """Refers to the individual component :class:`manim.Mobject` of :class:`MArrayElement`.""" + + BODY = 0 + """Body :class:`manim.Square`""" + + VALUE = 1 + """Value :class:`manim.Text`""" + + INDEX = 2 + """Index :class:`manim.Text`""" + + LABEL = 3 + """Label :class:`manim.Text`""" + + class MArrayDirection(Enum): """Specifies the growth direction of the :class:`MArray`.""" diff --git a/src/manim_data_structures/m_variable.py b/src/manim_data_structures/m_variable.py new file mode 100644 index 0000000..a5eeca9 --- /dev/null +++ b/src/manim_data_structures/m_variable.py @@ -0,0 +1,173 @@ +"""Contains classes to construct variable.""" + +from manim import * + +from .m_array import MArrayElement + + +class MVariable(MArrayElement): + """A class that represents a variable. + + Parameters + ---------- + value + Specifies the value of the variable. + index + Specifies the index of the variable. + label + Specifies the label of the variable. + mob_square_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Square` that represents the element body. + mob_value_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element value. + mob_index_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element index. + + Attributes + ---------- + __value + Specifies the value of the variable. + __index + Specifies the index of the variable. + __label + Specifies the label of the variable. + """ + + def __init__( + self, + value="", + index="", + label="", + mob_value_args: dict = {}, + mob_index_args: dict = {}, + mob_label_args: dict = {}, + **kwargs + ) -> None: + """Initializes the class. + + Parameters + ---------- + value + Specifies the value of the variable. + index + Specifies the index of the variable. + label + Specifies the label of the variable. + mob_square_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Square` that represents the element body. + mob_value_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element value. + mob_index_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element index. + + Attributes + ---------- + __value + Specifies the value of the variable. + __index + Specifies the index of the variable. + __label + Specifies the label of the variable. + """ + + self.__value = value + self.__index = index + self.__label = label + + mob_value_args["text"] = value + mob_index_args["text"] = index + mob_label_args["text"] = label + + super().__init__( + mob_value_args=mob_value_args, + mob_index_args=mob_index_args, + mob_label_args=mob_label_args, + **kwargs + ) + + def fetch_value(self): + """Fetches :attr:`__value`. + + Returns + ------- + Any + Value of :class:`MVariable`. + """ + + return self.__value + + def fetch_index(self): + """Fetches :attr:`__index`. + + Returns + ------- + Any + Index of :class:`MVariable`. + """ + + return self.__index + + def fetch_label(self): + """Fetches :attr:`__label`. + + Returns + ------- + Any + Label of :class:`MVariable`. + """ + + return self.__label + + def update_value(self, value, mob_value_args: dict = {}) -> Text: + """Updates :attr:`__value` and the :class:`manim.Text` that represents the element value. + + Parameters + ---------- + mob_value_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element value. + + Returns + ------- + :class:`manim.Text` + Represents the updated element value. + """ + + self.__value = value + mob_value_args["text"] = value + return self.update_mob_value(mob_value_args) + + def update_index(self, index, mob_index_args: dict = {}) -> Text: + """Updates :attr:`__index` and the :class:`manim.Text` that represents the element index. + + Parameters + ---------- + mob_index_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element index. + + Returns + ------- + :class:`manim.Text` + Represents the updated element index. + """ + + self.__index = index + mob_index_args["text"] = index + return self.update_mob_index(mob_index_args) + + def update_label(self, label, mob_label_args: dict = {}) -> Text: + """Updates :attr:`__label` and the :class:`manim.Text` that represents the element label. + + Parameters + ---------- + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element label. + + Returns + ------- + :class:`manim.Text` + Represents the updated element label. + """ + + self.__value = label + mob_label_args["text"] = label + return self.update_mob_label(mob_label_args)