From ce01a9b763a82374a4e03c80b314cb80c95cfa26 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Tue, 20 Dec 2022 14:45:37 +0500 Subject: [PATCH 1/8] perf: MArrayElement automatically plays animations on update calls --- src/manim_data_structures/m_array.py | 93 ++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index ef555bd..be00901 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -11,6 +11,8 @@ class MArrayElement(VGroup): Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. mob_square_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Square` that represents the element body. mob_value_args : :class:`dict`, default: `{}` @@ -32,6 +34,8 @@ class MArrayElement(VGroup): Attributes ---------- + __scene : :class:`manim.Scene` + The scene where the object should exist. __mob_square_props : :class:`dict` Default arguments passed to :class:`manim.Square` that represents the element body. __mob_value_props : :class:`dict` @@ -56,6 +60,7 @@ class MArrayElement(VGroup): def __init_props( self, + scene: Scene, index_pos: np.ndarray, index_gap: float, label_pos: np.ndarray, @@ -84,6 +89,7 @@ def __init_props( 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.__scene = scene self.__index_pos = index_pos self.__index_gap = index_gap self.__label_pos = label_pos @@ -180,6 +186,7 @@ def __init_mobs( def __init__( self, + scene: Scene, mob_square_args: dict = {}, mob_value_args: dict = {}, mob_index_args: dict = {}, @@ -196,6 +203,8 @@ def __init__( Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. mob_square_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Square` that represents the element body. mob_value_args : :class:`dict`, default: `{}` @@ -221,7 +230,7 @@ def __init__( super().__init__(**kwargs) # Initialize props - self.__init_props(index_pos, index_gap, label_pos, label_gap) + self.__init_props(scene, index_pos, index_gap, label_pos, label_gap) # Update props self.__update_props( @@ -300,13 +309,25 @@ def fetch_mob(self, mob_target: MArrayElementComp) -> Mobject: else: return self - def update_mob_value(self, mob_value_args: dict = {}) -> Text: + def update_mob_value( + self, + mob_value_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> Text: """Re-intializes 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -314,19 +335,43 @@ def update_mob_value(self, mob_value_args: dict = {}) -> Text: Represents the updated element 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)) + return self.__mob_value - def update_mob_index(self, mob_index_args: dict = {}) -> Text: + def update_mob_index( + self, + mob_index_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> Text: """Re-intializes 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -334,19 +379,43 @@ def update_mob_index(self, mob_index_args: dict = {}) -> Text: Represents the updated element index. """ + # Update props of mob_index self.__update_props(mob_index_args=mob_index_args) + + # Remove current mob_index self.remove(self.__mob_index) - self.__init_mobs(init_index=True) + + # Initialize new mob_index + self.__init_mobs(init_value=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)) + return self.__mob_index - def update_mob_label(self, mob_label_args: dict = {}) -> Text: + def update_mob_label( + self, + mob_label_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -354,10 +423,22 @@ def update_mob_label(self, mob_label_args: dict = {}) -> Text: Represents the updated element label. """ + # Update props of mob_label self.__update_props(mob_label_args=mob_label_args) + + # Remove current mob_label self.remove(self.__mob_label) - self.__init_mobs(init_label=True) + + # Initialize new mob_label + self.__init_mobs(init_value=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)) + return self.__mob_label def animate_mob_square(self) -> "_AnimationBuilder": # type: ignore From 2ef6bd70463a940390494e56abedd5fe4eb875ba Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Tue, 20 Dec 2022 15:28:18 +0500 Subject: [PATCH 2/8] perf: MArray can now play animations + refactored functions to improve readability --- src/manim_data_structures/m_array.py | 269 +++++++++++++++++++-------- 1 file changed, 188 insertions(+), 81 deletions(-) diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index be00901..ba33faf 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -70,6 +70,8 @@ def __init_props( Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. index_pos : :class:`np.ndarray` Specifies the position of :attr:`__mob_index` index_gap : :class:`float` @@ -491,8 +493,12 @@ class MArray(VGroup): Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. arr : :class:`list`, default: `[]` Array to represent. Elements must be convertible to :class:`str`. + label : :class:`str`, default: `''` + Specifies the label of the array. index_offset : :class:`int`, default: `1` Difference between successive indices. index_start : :class:`int`, default: `0` @@ -514,8 +520,12 @@ class MArray(VGroup): Attributes ---------- + __scene : :class:`manim.Scene` + The scene where the object should exist. __arr : :class:`list` Array to represent. Elements must be convertible to :class:`str`. + __label : :class:`str`, default: `''` + Specifies the label of the array. __mob_arr : List[:class:`MArrayElement`] Array containing the manim objects. __index_offset : :class:`int` @@ -539,32 +549,6 @@ 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. @@ -639,27 +623,6 @@ def __calc_label_pos_and_mob(self) -> typing.Tuple[Square, np.ndarray]: * ((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. @@ -767,6 +730,7 @@ def __append_elem( 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, @@ -798,7 +762,7 @@ def __remove_elem( self, index: int, removal_anim: Animation = FadeOut, - update_anim: Animation = Write, + update_anim: Animation = Indicate, removal_anim_args: dict = {}, update_anim_args: dict = {}, removal_anim_target: MArrayElementComp = None, @@ -893,10 +857,106 @@ def update_indices() -> typing.List[Animation]: 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 : :class:`manim.Scene` + The scene where the object should exist. + arr : :class:`list` + Array to represent. Elements must be convertible to :class:`str`. + label : :class:`str` + Specifies the label of the array. + index_offset : :class:`int` + Difference between successive indices. + index_start : :class:`int` + Starting value of index. + index_hex_display : :class:`bool` + Displays indices in hex if `True` otherwise in decimal. + hide_index : :class:`bool` + Specifies whether to display indices or not. + arr_dir : :class:`.m_enum.MArrayDirection` + Specifies the growing direction of array. + arr_label_pos : :class:`.enum.MArrayDirection` + Specifies the position of :attr:`__mob_arr_label`. + arr_label_gap : :class:`float` + Specifies the distance between :attr:`__mob_arr` and :attr:`__mob_arr_label`. + """ + + self.__mob_arr_label_props = {"text": "", "color": BLUE_A, "font_size": 38} + self.__scene = scene + self.__arr = arr + self.__label = label + self.__mob_arr = [] + self.__index_offset = index_offset + self.__index_start = index_start + self.__index_hex_display = index_hex_display + 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 + + def __update_props( + self, + 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"] = 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 :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 __init__( self, + scene: Scene, arr: list = [], - label="", + label: str = "", index_offset: int = 1, index_start: int = 0, index_hex_display: bool = False, @@ -915,8 +975,12 @@ def __init__( Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. arr : :class:`list`, default: `[]` Array to represent. Elements must be convertible to :class:`str`. + label : :class:`str`, default: `''` + Specifies the label of the array. index_offset : :class:`int`, default: `1` Difference between successive indices. index_start : :class:`int`, default: `0` @@ -944,20 +1008,26 @@ def __init__( """ super().__init__(**kwargs) - self.__arr = arr - self.__mob_arr = [] - self.__index_offset = index_offset - self.__index_start = index_start - self.__index_hex_display = index_hex_display - 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) + # 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, @@ -967,9 +1037,18 @@ def __init__( mob_index_args=mob_index_args, ) + # Initialize other mobjects (e.g. __arr_label) self.__init_mobs(True) - def update_elem_value(self, index: int, value, mob_value_args: dict = {}) -> Text: + def update_elem_value( + self, + index: int, + value, + mob_value_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> Text: """Updates the elements value. Parameters @@ -980,6 +1059,12 @@ def update_elem_value(self, index: int, value, mob_value_args: dict = {}) -> Tex New value to be assigned. mob_value_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element value of :class:`MArrayElement`. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -992,9 +1077,19 @@ def update_elem_value(self, index: int, value, mob_value_args: dict = {}) -> Tex self.__arr[index] = value mob_value_args["text"] = value - return self.__mob_arr[index].update_mob_value(mob_value_args) + return self.__mob_arr[index].update_mob_value( + mob_value_args, update_anim, update_anim_args, play_anim + ) - def update_elem_index(self, index: int, value, mob_index_args: dict = {}) -> Text: + def update_elem_index( + self, + index: int, + value, + mob_index_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> Text: """Updates the elements index. Parameters @@ -1005,6 +1100,12 @@ def update_elem_index(self, index: int, value, mob_index_args: dict = {}) -> Tex New value to be assigned. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index of :class:`MArrayElement`. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -1016,7 +1117,9 @@ def update_elem_index(self, index: int, value, mob_index_args: dict = {}) -> Tex raise Exception("Index out of bounds!") mob_index_args["text"] = value - return self.__mob_arr[index].update_mob_index(mob_index_args) + return self.__mob_arr[index].update_mob_index( + mob_index_args, update_anim, update_anim_args, play_anim + ) def animate_elem(self, index: int) -> "_AnimationBuilder": # type: ignore """Invokes the :meth:`MArrayElement.animate` property of :class:`MArrayElement` on specified index of :attr:`__mob_arr`. @@ -1100,10 +1203,11 @@ def append_elem( append_anim: Animation = Write, append_anim_args: dict = {}, append_anim_target: MArrayElementComp = None, + play_anim: bool = True, mob_square_args: dict = {}, mob_value_args: dict = {}, mob_index_args: dict = {}, - ) -> typing.List[Animation]: + ) -> None: """Appends the `value` to :attr:`__arr` and creates a new :class:`MArrayElement` and appends it to :attr:`__mob_arr`. Parameters @@ -1116,6 +1220,8 @@ def append_elem( 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. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the :class:`manim.Animation`. 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: `{}` @@ -1124,15 +1230,11 @@ def append_elem( 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 - ------- - List[:class:`manim.Animation`] - List of animations for appending. """ self.__arr.append(value) - return self.__append_elem( + + anim_list = self.__append_elem( value, mob_square_args=mob_square_args, mob_value_args=mob_value_args, @@ -1142,6 +1244,9 @@ def append_elem( append_anim_target=append_anim_target, ) + if play_anim: + self.__scene.play(*anim_list) + def remove_elem( self, index, @@ -1151,7 +1256,8 @@ def remove_elem( update_anim_args: dict = {}, removal_anim_target: MArrayElementComp = None, update_anim_target: MArrayElementComp = MArrayElementComp.INDEX, - ) -> typing.Tuple[Succession, typing.Callable[[], typing.List[Animation]]]: + play_anim: bool = True, + ) -> None: """Removes the element from :attr:`__arr` and removes :class:`MArrayElement` from :attr:`__mob_arr` at the specified index. Parameters @@ -1170,13 +1276,8 @@ def remove_elem( 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). + play_anim : :class:`bool`, default: `True` + Specifies whether to play the :class:`manim.Animation`. """ if index < 0 or index > len(self.__mob_arr): @@ -1184,7 +1285,7 @@ def remove_elem( self.__arr = self.__arr[0:index] + self.__arr[index + 1 :] - return self.__remove_elem( + (remove_anim, update_indices) = self.__remove_elem( index, removal_anim, update_anim, @@ -1194,6 +1295,12 @@ def remove_elem( update_anim_target, ) + if play_anim: + self.__scene.play(remove_anim) + self.__scene.play(*update_indices()) + else: + update_indices() + def fetch_arr(self) -> list: """Fetches :attr:`__arr`. From 66b0c5765fa49d5bad61b1dbbf6201d2c167d2b6 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Tue, 20 Dec 2022 16:20:47 +0500 Subject: [PATCH 3/8] perf: MVaraible can now play animations --- src/manim_data_structures/m_variable.py | 63 ++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/manim_data_structures/m_variable.py b/src/manim_data_structures/m_variable.py index a5eeca9..195cb29 100644 --- a/src/manim_data_structures/m_variable.py +++ b/src/manim_data_structures/m_variable.py @@ -10,6 +10,8 @@ class MVariable(MArrayElement): Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. value Specifies the value of the variable. index @@ -35,6 +37,7 @@ class MVariable(MArrayElement): def __init__( self, + scene: Scene, value="", index="", label="", @@ -47,6 +50,8 @@ def __init__( Parameters ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. value Specifies the value of the variable. index @@ -79,6 +84,7 @@ def __init__( mob_label_args["text"] = label super().__init__( + scene=scene, mob_value_args=mob_value_args, mob_index_args=mob_index_args, mob_label_args=mob_label_args, @@ -118,13 +124,26 @@ def fetch_label(self): return self.__label - def update_value(self, value, mob_value_args: dict = {}) -> Text: + def update_value( + self, + value, + mob_value_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -134,15 +153,30 @@ def update_value(self, value, mob_value_args: dict = {}) -> Text: self.__value = value mob_value_args["text"] = value - return self.update_mob_value(mob_value_args) + return self.update_mob_value( + mob_value_args, update_anim, update_anim_args, play_anim + ) - def update_index(self, index, mob_index_args: dict = {}) -> Text: + def update_index( + self, + index, + mob_index_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -152,15 +186,30 @@ def update_index(self, index, mob_index_args: dict = {}) -> Text: self.__index = index mob_index_args["text"] = index - return self.update_mob_index(mob_index_args) + return self.update_mob_index( + mob_index_args, update_anim, update_anim_args, play_anim + ) - def update_label(self, label, mob_label_args: dict = {}) -> Text: + def update_label( + self, + label, + mob_label_args: dict = {}, + update_anim: Animation = Indicate, + update_anim_args: dict = {}, + play_anim: bool = True, + ) -> 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. + update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the update :class:`manim.Animation`. Returns ------- @@ -170,4 +219,6 @@ def update_label(self, label, mob_label_args: dict = {}) -> Text: self.__value = label mob_label_args["text"] = label - return self.update_mob_label(mob_label_args) + return self.update_mob_label( + mob_label_args, update_anim, update_anim_args, play_anim + ) From 2c9415f9ff8f7ddd26d6d75d1a8d5f94b83335b4 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Sun, 25 Dec 2022 18:58:42 +0500 Subject: [PATCH 4/8] fix: deepcopy implemented for MArrayElement & MArray + MArray label for even elements resolved --- src/manim_data_structures/m_array.py | 145 ++++++++++++++++++++------- 1 file changed, 110 insertions(+), 35 deletions(-) diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index ba33faf..204c7c5 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -1,5 +1,7 @@ """Contains classes to construct an array.""" +from copy import deepcopy + import numpy as np from manim import * @@ -186,6 +188,19 @@ def __init_mobs( ) 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, @@ -317,6 +332,7 @@ def update_mob_value( update_anim: Animation = Indicate, update_anim_args: dict = {}, play_anim: bool = True, + play_anim_args: dict = {}, ) -> Text: """Re-intializes the :class:`manim.Text` that represents the element value. @@ -330,6 +346,8 @@ def update_mob_value( Arguments for update :class:`manim.Animation`. 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 ------- @@ -351,7 +369,9 @@ def update_mob_value( # Animate change if play_anim: - self.__scene.play(update_anim(self.__mob_value, **update_anim_args)) + self.__scene.play( + update_anim(self.__mob_value, **update_anim_args), **play_anim_args + ) return self.__mob_value @@ -361,6 +381,7 @@ def update_mob_index( update_anim: Animation = Indicate, update_anim_args: dict = {}, play_anim: bool = True, + play_anim_args: dict = {}, ) -> Text: """Re-intializes the :class:`manim.Text` that represents the element index. @@ -374,6 +395,8 @@ def update_mob_index( Arguments for update :class:`manim.Animation`. 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 ------- @@ -388,14 +411,16 @@ def update_mob_index( self.remove(self.__mob_index) # Initialize new mob_index - self.__init_mobs(init_value=True) + 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)) + self.__scene.play( + update_anim(self.__mob_index, **update_anim_args), **play_anim_args + ) return self.__mob_index @@ -405,6 +430,7 @@ def update_mob_label( update_anim: Animation = Indicate, update_anim_args: dict = {}, play_anim: bool = True, + play_anim_args: dict = {}, ) -> Text: """Re-intializes the :class:`manim.Text` that represents the element label. @@ -418,6 +444,8 @@ def update_mob_label( Arguments for update :class:`manim.Animation`. 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 ------- @@ -432,14 +460,16 @@ def update_mob_label( self.remove(self.__mob_label) # Initialize new mob_label - self.__init_mobs(init_value=True) + 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)) + self.__scene.play( + update_anim(self.__mob_label, **update_anim_args), **play_anim_args + ) return self.__mob_label @@ -611,10 +641,12 @@ def __calc_label_pos_and_mob(self) -> typing.Tuple[Square, np.ndarray]: 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, len(self.__mob_arr) - 1 + middle_index + 1 if odd_indices else middle_index, + len(self.__mob_arr) - 1, ) return ( self.__mob_arr[middle_index].fetch_mob_square(), @@ -767,7 +799,7 @@ def __remove_elem( update_anim_args: dict = {}, removal_anim_target: MArrayElementComp = None, update_anim_target: MArrayElementComp = MArrayElementComp.INDEX, - ) -> typing.Tuple[Succession, typing.Callable[[], typing.List[Animation]]]: + ) -> typing.Tuple[Succession, typing.Callable[[bool], typing.List[Animation]]]: """Removes the :class:`MArrayElement` from :attr:`__mob_arr` at the specified index. Parameters @@ -791,7 +823,7 @@ def __remove_elem( ------- :class:`manim.Succession` Contains :class:`manim.Animations` played for removal and shifting of :class:`MArrayElement`. - Callable[[], List[:class:`manim.Animation`]] + Callable[[bool], 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). """ @@ -824,9 +856,18 @@ def __remove_elem( ) ) - def update_indices() -> typing.List[Animation]: + 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`] @@ -836,7 +877,7 @@ def update_indices() -> typing.List[Animation]: 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)} + mob_index_args={"text": self.__calc_index(i)}, play_anim=False ) anims_index.append( update_anim( @@ -845,6 +886,9 @@ def update_indices() -> typing.List[Animation]: ) ) + if play_anim: + self.__scene.play(*anims_index, **play_anim_args) + return anims_index return ( @@ -897,19 +941,23 @@ def __init_props( Specifies the distance between :attr:`__mob_arr` and :attr:`__mob_arr_label`. """ - self.__mob_arr_label_props = {"text": "", "color": BLUE_A, "font_size": 38} - self.__scene = scene - self.__arr = arr - self.__label = label - self.__mob_arr = [] - self.__index_offset = index_offset - self.__index_start = index_start - self.__index_hex_display = index_hex_display - 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.__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, @@ -950,8 +998,25 @@ def __init_mobs( self.__mob_arr_label.next_to( next_to_mob, label_pos, self.__arr_label_gap ) + 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, @@ -1048,6 +1113,7 @@ def update_elem_value( update_anim: Animation = Indicate, update_anim_args: dict = {}, play_anim: bool = True, + play_anim_args: dict = {}, ) -> Text: """Updates the elements value. @@ -1065,6 +1131,8 @@ def update_elem_value( Arguments for update :class:`manim.Animation`. 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 ------- @@ -1078,7 +1146,7 @@ def update_elem_value( 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 + mob_value_args, update_anim, update_anim_args, play_anim, play_anim_args ) def update_elem_index( @@ -1089,6 +1157,7 @@ def update_elem_index( update_anim: Animation = Indicate, update_anim_args: dict = {}, play_anim: bool = True, + play_anim_args: dict = {}, ) -> Text: """Updates the elements index. @@ -1106,6 +1175,8 @@ def update_elem_index( Arguments for update :class:`manim.Animation`. 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 ------- @@ -1118,7 +1189,7 @@ def update_elem_index( mob_index_args["text"] = value return self.__mob_arr[index].update_mob_index( - mob_index_args, update_anim, update_anim_args, play_anim + mob_index_args, update_anim, update_anim_args, play_anim, play_anim_args ) def animate_elem(self, index: int) -> "_AnimationBuilder": # type: ignore @@ -1204,6 +1275,7 @@ def append_elem( append_anim_args: dict = {}, append_anim_target: MArrayElementComp = None, play_anim: bool = True, + play_anim_args: dict = {}, mob_square_args: dict = {}, mob_value_args: dict = {}, mob_index_args: dict = {}, @@ -1222,14 +1294,14 @@ def append_elem( Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the append :class:`manim.Animation` is to be played. play_anim : :class:`bool`, default: `True` Specifies whether to play the :class:`manim.Animation`. + play_anim_args : :class:`dict, default: `{}` + Arguments for :meth:`manim.Scene.play`. 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. """ self.__arr.append(value) @@ -1245,19 +1317,20 @@ def append_elem( ) if play_anim: - self.__scene.play(*anim_list) + self.__scene.play(*anim_list, **play_anim_args) def remove_elem( self, index, removal_anim: Animation = FadeOut, - update_anim: Animation = Write, + 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, - ) -> None: + play_anim_args: dict = {}, + ) -> typing.Tuple[Succession, typing.Callable[[bool], typing.List[Animation]]]: """Removes the element from :attr:`__arr` and removes :class:`MArrayElement` from :attr:`__mob_arr` at the specified index. Parameters @@ -1266,7 +1339,7 @@ def remove_elem( 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` + update_anim : :class:`manim.Animation`, default: :class:`manim.Indicate` 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`. @@ -1278,6 +1351,8 @@ def remove_elem( Specifies the :class:`manim.Mobject` of the :class:`MArrayElement` on which the update :class:`manim.Animation` is to be played. play_anim : :class:`bool`, default: `True` Specifies whether to play the :class:`manim.Animation`. + play_anim_args : :class:`dict, default: `{}` + Arguments for :meth:`manim.Scene.play`. """ if index < 0 or index > len(self.__mob_arr): @@ -1296,10 +1371,10 @@ def remove_elem( ) if play_anim: - self.__scene.play(remove_anim) - self.__scene.play(*update_indices()) - else: - update_indices() + self.__scene.play(remove_anim, **play_anim_args) + update_indices(play_anim_args=play_anim_args) + + return (remove_anim, update_indices) def fetch_arr(self) -> list: """Fetches :attr:`__arr`. From ec6c46cf49b288608b2b4a39ebfb4fbbe2cb064f Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Sun, 25 Dec 2022 19:42:48 +0500 Subject: [PATCH 5/8] docs: updated docs according to recent changes --- docs/source/guides/arrays.rst | 121 +++++++++++++++------------ docs/source/index.rst | 4 +- src/manim_data_structures/m_array.py | 35 +++++--- 3 files changed, 94 insertions(+), 66 deletions(-) diff --git a/docs/source/guides/arrays.rst b/docs/source/guides/arrays.rst index 0576856..7990f50 100644 --- a/docs/source/guides/arrays.rst +++ b/docs/source/guides/arrays.rst @@ -13,7 +13,7 @@ The most basic data structure this package provides is the :py:class:`MArray` (s class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.play(Create(arr)) self.wait(1) @@ -29,7 +29,7 @@ The most basic data structure this package provides is the :py:class:`MArray` (s class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.play(Create(arr)) self.wait(1) @@ -55,7 +55,7 @@ To animate the :py:class:`MArray`, simply invoke the ``animate`` property as sho class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.play(Create(arr)) self.play(arr.animate.shift(UP * 2 + LEFT * 5)) self.wait(1) @@ -79,7 +79,7 @@ Moreover, you can also use the :py:func:`MArray.animate_elem` method to animate class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.play(Create(arr)) self.play(arr.animate_elem(1).shift(DOWN)) self.wait(1) @@ -107,7 +107,7 @@ Lastly, you can also animate the body, value and the index of any element using class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.play(Create(arr)) self.play( arr.animate_elem_square(1).set_fill(BLACK), @@ -125,6 +125,7 @@ The :py:class:`MArray` also allows you to alter the way your array looks. While :linenos: arr = MArray( + self, [1, 2, 3], mob_square_args={'fill_color': RED_D}, mob_value_args={'color': BLACK}, @@ -144,6 +145,7 @@ The :py:class:`MArray` also allows you to alter the way your array looks. While class MyScene(Scene): def construct(self): arr = MArray( + self, [1, 2, 3], mob_square_args={'fill_color': RED_D}, mob_value_args={'color': BLACK}, @@ -166,10 +168,10 @@ To do this, simply pass your preferred direction enum from :py:class:`MArrayDire class MyScene(Scene): def construct(self): - arr_up = MArray([1, 2], arr_dir=MArrayDirection.UP) - arr_right = MArray([3, 4], arr_dir=MArrayDirection.RIGHT) - arr_down = MArray([5, 6], arr_dir=MArrayDirection.DOWN) - arr_left = MArray([7, 8], arr_dir=MArrayDirection.LEFT) + arr_up = MArray(self, [1, 2], arr_dir=MArrayDirection.UP) + arr_right = MArray(self, [3, 4], arr_dir=MArrayDirection.RIGHT) + arr_down = MArray(self, [5, 6], arr_dir=MArrayDirection.DOWN) + arr_left = MArray(self, [7, 8], arr_dir=MArrayDirection.LEFT) self.play(Create(arr_up)) self.play(arr_up.animate.shift(UP * 2)) @@ -194,10 +196,10 @@ To do this, simply pass your preferred direction enum from :py:class:`MArrayDire class MyScene(Scene): def construct(self): - arr_up = MArray([1, 2], arr_dir=MArrayDirection.UP) - arr_right = MArray([3, 4], arr_dir=MArrayDirection.RIGHT) - arr_down = MArray([5, 6], arr_dir=MArrayDirection.DOWN) - arr_left = MArray([7, 8], arr_dir=MArrayDirection.LEFT) + arr_up = MArray(self, [1, 2], arr_dir=MArrayDirection.UP) + arr_right = MArray(self, [3, 4], arr_dir=MArrayDirection.RIGHT) + arr_down = MArray(self, [5, 6], arr_dir=MArrayDirection.DOWN) + arr_left = MArray(self, [7, 8], arr_dir=MArrayDirection.LEFT) self.play(Create(arr_up)) self.play(arr_up.animate.shift(UP * 2)) @@ -226,10 +228,10 @@ Similar to how we specify the growth direction using :py:class:`MArrayDirection` 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) + arr_label_left = MArray(self, [1, 2, 3], label='Arr') + arr_label_right = MArray(self, [1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.RIGHT) + arr_label_down = MArray(self, [1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.DOWN) + arr_label_up = MArray(self, [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)) @@ -254,10 +256,10 @@ Similar to how we specify the growth direction using :py:class:`MArrayDirection` 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) + arr_label_left = MArray(self, [1, 2, 3], label='Arr') + arr_label_right = MArray(self, [1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.RIGHT) + arr_label_down = MArray(self, [1, 2, 3], label='Arr', arr_label_pos=MArrayDirection.DOWN) + arr_label_up = MArray(self, [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)) @@ -287,6 +289,7 @@ Lets say you want to show a 4-byte integer array with its addresses. You can sim class MyScene(Scene): def construct(self): arr = MArray( + self, [1, 2, 3, 4], index_hex_display=True, index_offset=4 @@ -307,6 +310,7 @@ Lets say you want to show a 4-byte integer array with its addresses. You can sim class MyScene(Scene): def construct(self): arr = MArray( + self, [1, 2, 3, 4], index_hex_display=True, index_offset=4 @@ -325,6 +329,7 @@ Or if you don't want to show the indices at all, simply pass ``True`` as the ``h class MyScene(Scene): def construct(self): arr = MArray( + self, [1, 2, 3, 4], hide_index=True ) @@ -344,6 +349,7 @@ Or if you don't want to show the indices at all, simply pass ``True`` as the ``h class MyScene(Scene): def construct(self): arr = MArray( + self, [1, 2, 3, 4], hide_index=True ) @@ -365,10 +371,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], label='Array', arr_label_pos=MArrayDirection.DOWN) + arr = MArray(self, [1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) self.add(arr) self.wait(1) - self.play(*arr.append_elem(4)) + arr.append_elem(4) self.wait(1) .. raw:: html @@ -383,24 +389,22 @@ 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], label='Array', arr_label_pos=MArrayDirection.DOWN) + arr = MArray(self, [1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) self.add(arr) self.wait(1) - self.play(*arr.append_elem(4)) + 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: +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:func:`MArray.append_elem` method: .. code-block:: python :linenos: - self.play(*arr.append_elem(4, append_anim=GrowFromCenter)) + arr.append_elem(4, append_anim=GrowFromCenter) .. raw:: html @@ -414,10 +418,10 @@ Moreover, you can also specify the animation that is played for the inserted ele class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + arr = MArray(self, [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)) + arr.append_elem(4, append_anim=GrowFromCenter) self.wait(1) .. currentmodule:: manim_data_structures.m_enum @@ -426,19 +430,26 @@ Moreover, you can also specify the animation that is played for the inserted ele 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. +Did you notice that in both snippets, we didn't pass any animation to our :py:class:`manim.Scene` but the append animation still played? This is thanks to the ``self`` that we pass as the first argument to our :py:class:`MArray` constructor, which is basically a reference to the current :py:class:`manim.Scene`. + +However, if you'd like to play the animation yourself, we have got you covered! The :py:func:`MArrayElement` method returns a list of :py:class:`manim.Animation` that you can pass to the :py:func:`manim.Scene.play` method as follows: + +.. code-block:: python + :linenos: + + self.play(*arr.append_elem(4, play_anim=False)) + 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. +To remove an element simply invoke the :py:class:`MArray.remove_elem` method with the index of element you wish to remove. .. code-block:: python :linenos: - (remove_anim, update_indices) = arr.remove_elem(1) - self.play(remove_anim) - self.play(*update_indices()) + arr.remove_elem(1) .. raw:: html @@ -452,15 +463,14 @@ To remove an element simply invoke the :py:class:`MArray.remove_elem` method wit class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + arr = MArray(self, [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()) + arr.remove_elem(1) 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. @@ -469,7 +479,7 @@ 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) + arr.remove_elem(1, removal_anim=ShowPassingFlash , update_anim=Write) .. raw:: html @@ -483,18 +493,25 @@ The code snippet below provides an example: class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3], label='Array', arr_label_pos=MArrayDirection.DOWN) + arr = MArray(self, [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()) + arr.remove_elem(1, removal_anim=ShowPassingFlash , update_anim=Write) 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. +Lastly, as the :py:func:`MArray.append_elem` returns a list of :py:class:`manim.Animation`, the :py:func:`MArray.remove_elem` returns two objects; a removal animation and a function that udpates the indices of the remaining elements and returns their animations. Hence, you can animate this as follows: + +.. code-block:: python + :linenos: + + (remove_anim, update_indices) = arr.remove_elem(1, removal_anim=ShowPassingFlash , update_anim=Write, play_anim=False) + self.play(remove_anim) # Play removal animation first + self.play(*update_indices(play_anim=False)) # Then play the update_indices animation + Update Element ^^^^^^^^^^^^^^ @@ -505,13 +522,11 @@ You can also update the value and the index of an existing array using the :py:c class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.add(arr) self.wait(1) - self.play( - Write(arr.update_elem_value(1, 20)), - Write(arr.update_elem_index(1, -2)) - ) + arr.update_elem_value(1, 20) + arr.update_elem_index(1, -2) self.wait(1) .. raw:: html @@ -526,13 +541,11 @@ You can also update the value and the index of an existing array using the :py:c class MyScene(Scene): def construct(self): - arr = MArray([1, 2, 3]) + arr = MArray(self, [1, 2, 3]) self.add(arr) self.wait(1) - self.play( - Write(arr.update_elem_value(1, 20)), - Write(arr.update_elem_index(1, -2)) - ) + arr.update_elem_value(1, 20) + arr.update_elem_index(1, -2) self.wait(1) .. note:: diff --git a/docs/source/index.rst b/docs/source/index.rst index 9a27f02..ffeccfb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -36,7 +36,7 @@ Variables class VarScene(Scene): def construct(self): - var = MVariable(10, 0, 'Var') + var = MVariable(self, 10, 0, 'Var') self.add(var) Arrays @@ -49,7 +49,7 @@ Arrays class ArrayScene(Scene): def construct(self): - arr = MArray([1, 2, 3], label='Arr') + arr = MArray(self, [1, 2, 3], label='Arr') self.add(arr) Next Steps diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index 204c7c5..fae9504 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -329,7 +329,7 @@ def fetch_mob(self, mob_target: MArrayElementComp) -> Mobject: def update_mob_value( self, mob_value_args: dict = {}, - update_anim: Animation = Indicate, + update_anim: Animation = Write, update_anim_args: dict = {}, play_anim: bool = True, play_anim_args: dict = {}, @@ -378,7 +378,7 @@ def update_mob_value( def update_mob_index( self, mob_index_args: dict = {}, - update_anim: Animation = Indicate, + update_anim: Animation = Write, update_anim_args: dict = {}, play_anim: bool = True, play_anim_args: dict = {}, @@ -427,7 +427,7 @@ def update_mob_index( def update_mob_label( self, mob_label_args: dict = {}, - update_anim: Animation = Indicate, + update_anim: Animation = Write, update_anim_args: dict = {}, play_anim: bool = True, play_anim_args: dict = {}, @@ -998,10 +998,11 @@ def __init_mobs( self.__mob_arr_label.next_to( next_to_mob, label_pos, self.__arr_label_gap ) - self.__mob_arr_label.shift( - -self.__dir_map[self.__arr_dir.value]["arr"] - * (next_to_mob.side_length / 2) - ) + 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): @@ -1110,7 +1111,7 @@ def update_elem_value( index: int, value, mob_value_args: dict = {}, - update_anim: Animation = Indicate, + update_anim: Animation = Write, update_anim_args: dict = {}, play_anim: bool = True, play_anim_args: dict = {}, @@ -1154,7 +1155,7 @@ def update_elem_index( index: int, value, mob_index_args: dict = {}, - update_anim: Animation = Indicate, + update_anim: Animation = Write, update_anim_args: dict = {}, play_anim: bool = True, play_anim_args: dict = {}, @@ -1279,7 +1280,7 @@ def append_elem( mob_square_args: dict = {}, mob_value_args: dict = {}, mob_index_args: dict = {}, - ) -> None: + ) -> typing.List[Animation]: """Appends the `value` to :attr:`__arr` and creates a new :class:`MArrayElement` and appends it to :attr:`__mob_arr`. Parameters @@ -1302,6 +1303,11 @@ def append_elem( 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. """ self.__arr.append(value) @@ -1319,6 +1325,8 @@ def append_elem( if play_anim: self.__scene.play(*anim_list, **play_anim_args) + return anim_list + def remove_elem( self, index, @@ -1353,6 +1361,13 @@ def remove_elem( Specifies whether to play the :class:`manim.Animation`. play_anim_args : :class:`dict, default: `{}` Arguments for :meth:`manim.Scene.play`. + + Returns + ------- + :class:`manim.Succession` + Contains :class:`manim.Animations` played for removal and shifting of :class:`MArrayElement`. + Callable[[bool], 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): From 59170bb138bf41acfa109b6bc3c2d506a94a5861 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Mon, 26 Dec 2022 12:00:55 +0500 Subject: [PATCH 6/8] feat: MArrayPointer added --- src/manim_data_structures/__init__.py | 1 + src/manim_data_structures/m_array.py | 545 +++++++++++++++++++++++++- 2 files changed, 532 insertions(+), 14 deletions(-) diff --git a/src/manim_data_structures/__init__.py b/src/manim_data_structures/__init__.py index e8d6b71..647d53e 100644 --- a/src/manim_data_structures/__init__.py +++ b/src/manim_data_structures/__init__.py @@ -7,6 +7,7 @@ __all__ = [ "MArrayElement", "MArray", + "MArrayPointer", "MArrayDirection", "MArrayElementComp", "MVariable", diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index fae9504..36db6b6 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -54,9 +54,9 @@ 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` + __label_pos : :class:`np.ndarray` Specifies the position of :attr:`__mob_label` - __label_gap : :class:`float`, default: `0.5` + __label_gap : :class:`float` Specifies the distance between :attr:`__mob_square` and :attr:`__mob_label` """ @@ -340,7 +340,7 @@ def update_mob_value( ---------- mob_value_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element value. - update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + update_anim : :class:`manim.Animation`, default `manim.Write` Animation to be applied to the updated :class:`manim.Text`. update_anim_args : :class:`dict`, default: `{}` Arguments for update :class:`manim.Animation`. @@ -389,7 +389,7 @@ def update_mob_index( ---------- mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index. - update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + update_anim : :class:`manim.Animation`, default `manim.Write` Animation to be applied to the updated :class:`manim.Text`. update_anim_args : :class:`dict`, default: `{}` Arguments for update :class:`manim.Animation`. @@ -438,7 +438,7 @@ def update_mob_label( ---------- mob_label_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element label. - update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + update_anim : :class:`manim.Animation`, default `manim.Write` Animation to be applied to the updated :class:`manim.Text`. update_anim_args : :class:`dict`, default: `{}` Arguments for update :class:`manim.Animation`. @@ -539,6 +539,12 @@ 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. + 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: `{}` @@ -554,21 +560,26 @@ class MArray(VGroup): The scene where the object should exist. __arr : :class:`list` Array to represent. Elements must be convertible to :class:`str`. - __label : :class:`str`, default: `''` - Specifies the label of the array. __mob_arr : List[:class:`MArrayElement`] Array containing the manim objects. + __label : :class:`str` + Specifies the label of the array. __index_offset : :class:`int` Difference between successive indices. __index_start : :class:`int` Starting value of index. __index_hex_display : :class:`bool` Displays indices in hex if `True` otherwise in decimal. - __hide_index : :class:`bool`, default: `False` + __hide_index : :class:`bool` Specifies whether to display indices or not. - __arr_dir : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.RIGHT` + __arr_dir : :class:`.m_enum.MArrayDirection` Specifies the growing direction of array. - __mob_arr_label_props + __arr_label_pos : :class:`.enum.MArrayDirection` + 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_props : :class:`dict` + Arguments for :class:`manim.Text` that represents the label for :class:`MArray`. """ __dir_map = [ @@ -967,8 +978,6 @@ def __update_props( 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. """ @@ -1126,7 +1135,7 @@ def update_elem_value( New value to be assigned. mob_value_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element value of :class:`MArrayElement`. - update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + update_anim : :class:`manim.Animation`, default `manim.Write` Animation to be applied to the updated :class:`manim.Text`. update_anim_args : :class:`dict`, default: `{}` Arguments for update :class:`manim.Animation`. @@ -1170,7 +1179,7 @@ def update_elem_index( New value to be assigned. mob_index_args : :class:`dict`, default: `{}` Arguments for :class:`manim.Text` that represents the element index of :class:`MArrayElement`. - update_anim : :class:`manim.Animation`, default `{manim.Indicate}` + update_anim : :class:`manim.Animation`, default `manim.Write` Animation to be applied to the updated :class:`manim.Text`. update_anim_args : :class:`dict`, default: `{}` Arguments for update :class:`manim.Animation`. @@ -1193,6 +1202,58 @@ def update_elem_index( mob_index_args, update_anim, update_anim_args, play_anim, play_anim_args ) + 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: + """Re-intializes the :class:`manim.Text` that represents the array label. + + Parameters + ---------- + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the array label. + update_anim : :class:`manim.Animation`, default `manim.Write` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + 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 + ------- + :class:`manim.Text` + Represents the updated array 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 + def animate_elem(self, index: int) -> "_AnimationBuilder": # type: ignore """Invokes the :meth:`MArrayElement.animate` property of :class:`MArrayElement` on specified index of :attr:`__mob_arr`. @@ -1423,3 +1484,459 @@ def fetch_arr_label(self) -> Text: """ return self.__mob_arr_label + + def fetch_arr_dir(self) -> MArrayDirection: + """Fetches the :class:`MArrayDirection` that represents the array's growth direction. + + Returns + ------- + :class:`MArrayDirection` + Represents the array's growth direction. + """ + + return self.__arr_dir + + +class MArrayPointer(VGroup): + """A class that represents a pointer. + + Parameters + ---------- + scene : :class:`manim.Scene` + The scene where the object should exist. + arr : typing.List[:class:`MArray`] + Array to attach the pointer to. + index : :class:`int`, default = `0` + Index of the array to attach the pointer to. + label : :class:`str`, default: `''` + Specifies the label of the pointer. + arrow_len : :class:`float`, default: `1` + Specifies the length of the arrow. + arrow_gap : :class:`float`, default: `0.25` + Specifies the distance between the array and the arrow head. + label_gap : :class:`float`, default: `0.25` + Specifies the distance betweem the label and the arrow tail. + pointer_pos : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.DOWN` + Specifies the poistion of the pointer w.r.t the array. + mob_arrow_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Arrow` that represents the arrow for :class:`MArrayPointer`. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the label for :class:`MArrayPointer`. + **kwargs + Forwarded to constructor of the parent. + + Attributes + ---------- + __scene : :class:`manim.Scene` + The scene where the object should exist. + __arr : :class:`list` + Array to attach the pointer to. + __index : :class:`int`, default = `0` + Index of the array to attach the pointer to. + __label : :class:`str`, default: `''` + Specifies the label of the pointer. + __arrow_len : :class:`float`, default: `1` + Specifies the length of the arrow. + __arrow_gap : :class:`float`, default: `0.25` + Specifies the distance between the array and the arrow head. + __label_gap : :class:`float`, default: `0.25` + Specifies the distance betweem the label and the arrow tail. + __pointer_pos : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.DOWN` + Specifies the poistion of the pointer w.r.t the array. + __mob_arrow_props : :class:`dict`, default: `{}` + Arguments for :class:`manim.Arrow` that represents the arrow for :class:`MArrayPointer`. + __mob_label_props : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the label for :class:`MArrayPointer`. + __updater_pos : typing.Callable[[], None] + Updater function to keep 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}, + ] + + def __calc_arrow_pos(self) -> np.ndarray: + """Calculates direction vector for :class:`manim.Arrow`. + + Returns + ------- + :class:`np.ndarray` + Position vector for the arrow. + """ + + arr_dir_np = self.__dir_map[self.__arr.fetch_arr_dir().value]["np"] + arrow_pos_np = self.__dir_map[self.__pointer_pos.value]["np"] + + # If array's direction and pointer's direction are not parallel 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 with the object.""" + + 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 updater from the object.""" + + 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 + ---------- + :class:`int` + New index towards which the pointer should point to. + + Returns + ------- + :class:`np.ndarray` + A vector that represents how much the pointer should shift. + """ + + index_start = self.__index + index_end = new_index + if index_start > index_end: + index_start, index_end = index_end, index_start + + 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"] + + 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 : :class:`manim.Scene` + The scene where the object should exist. + arr : :class:`MArray` + Array to attach the pointer to. + index : :class:`int` + Index of the array to which the pointer is attached. + label : :class:`str` + Specifies the label of the pointer. + arrow_len : :class:`.enum.MArrayDirection` + Specifies the length of :class:`manim.Arrow`. + arrow_pos_gap : :class:`float` + Specifies the distance between :attr:`__mob_arr` and :attr:`__mob_arrow`. + label_gap : :class:`float` + Specifies the distance between :attr:`__mob_arrow` and :attr:`__mob_label`. + pointer_pos : :class:`MArrayDirection` + Specifies the position of the pointer. + """ + + 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 = {}): + """Updates the attributes of the class. + + Parameters + ---------- + mob_arrow_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Arrow` that represents the pointer arrow. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.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): + """Initializes the :class:`Mobject`s for the class. + + Parameters + ---------- + init_arrow : :class:`bool`, default: `False` + Instantiates a :class:`manim.Arrow` and adds it to :attr:`__mob_arrpw`. + init_label : :class:`bool`, default: `False` + Instantiates a :class:`manim.Text` and adds 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.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 : :class:`manim.Scene` + The scene where the object should exist. + arr : typing.List[:class:`MArray`] + Array to attach the pointer to. + index : :class:`int`, default = `0` + Index of the array to attach the pointer to. + label : :class:`str`, default: `''` + Specifies the label of the pointer. + arrow_len : :class:`float`, default: `1` + Specifies the length of the arrow. + arrow_gap : :class:`float`, default: `0.25` + Specifies the distance between the array and the arrow head. + label_gap : :class:`float`, default: `0.25` + Specifies the distance betweem the label and the arrow tail. + pointer_pos : :class:`.m_enum.MArrayDirection`, default: :attr:`.m_enum.MArrayDirection.DOWN` + Specifies the poistion of the pointer w.r.t the array. + mob_arrow_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Arrow` that represents the arrow for :class:`MArrayPointer`. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the label for :class:`MArrayPointer`. + **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) + + # Initialize position + self.__init_pos() + + # Add updater + self.__add_updater() + + def shift_to_elem( + self, index: int, play_anim: bool = True, play_anim_args: dict = {} + ) -> ApplyMethod: + """Shifts pointer to the specified element. + + Parameters + ---------- + index : :class:`int` + Index of the array to shift the pointer to. + play_anim : :class:`bool`, default: `True` + Specifies whether to play the :class:`manim.Animation`. + play_anim_args : :class:`dict, default: `{}` + Arguments for :meth:`manim.Scene.play`. + + Returns + ------- + :class:`manim.ApplyMethod` + Represents the shifting 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 + + def attach_to_elem(self, index: int) -> None: + """Attaches pointer to the specified element. + + Parameters + ---------- + index : :class:`int` + Index of the array to shift the pointer to. + """ + + if index < 0 or index > len(self.__arr.fetch_mob_arr()): + raise Exception("Index out of bounds!") + + self.__index = index + self.__init_pos() + + def fetch_mob_arrow(self) -> Arrow: + """Fetches :attr:`__mob_arrow`. + + Returns + ------- + :class:`manim.Arrow` + Represents the arrow stored in :attr:`__mob_arrow`. + """ + + return self.__mob_arrow + + def fetch_mob_label(self) -> Text: + """Fetches the :class:`manim.Text` that represents the pointer label. + + Returns + ------- + :class:`manim.Text` + Represents the pointer label. + """ + + return self.__mob_label + + 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: + """Re-intializes the :class:`manim.Text` that represents the pointer label. + + Parameters + ---------- + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the pointer label. + update_anim : :class:`manim.Animation`, default `manim.Write` + Animation to be applied to the updated :class:`manim.Text`. + update_anim_args : :class:`dict`, default: `{}` + Arguments for update :class:`manim.Animation`. + 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 + ------- + :class:`manim.Text` + Represents the updated pointer 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 + + def animate_mob_arrow(self) -> "_AnimationBuilder": # type: ignore + """Invokes the :meth:`manim.Arrow.animate` property of :class:`manim.Arrow` for the pointer arrow. + + Returns + ------- + :class:`_AnimationBuilder` + Value returned by :meth:`manim.Arrow.animate` property of :class:`manim.Arrow`. + """ + + return self.__mob_arrow.animate + + def animate_mob_label(self) -> "_AnimationBuilder": # type: ignore + """Invokes the :meth:`manim.Text.animate` property of :class:`manim.Text` for the pointer label. + + Returns + ------- + :class:`_AnimationBuilder` + Value returned by :meth:`manim.Text.animate` property of :class:`manim.Text`. + """ + + return self.__mob_label.animate From d4daf7e310f6ba2aa9442c9fc9b0a67dcb301308 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Mon, 26 Dec 2022 15:29:42 +0500 Subject: [PATCH 7/8] fix: MArrayPointer spawn & shift error resolved --- src/manim_data_structures/m_array.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index 36db6b6..77a234e 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -1568,9 +1568,9 @@ def __calc_arrow_pos(self) -> np.ndarray: """ arr_dir_np = self.__dir_map[self.__arr.fetch_arr_dir().value]["np"] - arrow_pos_np = self.__dir_map[self.__pointer_pos.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 parallel to each other + # 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] @@ -1610,15 +1610,25 @@ def __calc_shift_np(self, new_index: int) -> 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"] + ( + 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, @@ -1702,6 +1712,11 @@ def __init_mobs(self, init_arrow: bool = False, init_label: bool = False): 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: @@ -1791,9 +1806,6 @@ def __init__( # Initialize mobjects self.__init_mobs(True, True) - # Initialize position - self.__init_pos() - # Add updater self.__add_updater() From 425050c83e714390548699e77acdb8f59fb86868 Mon Sep 17 00:00:00 2001 From: Hammad Nasir Date: Mon, 26 Dec 2022 16:51:20 +0500 Subject: [PATCH 8/8] docs: added docs for MArrayPointer & Example Gallery --- docs/source/example.rst | 45 +++++++++++++++++++++ docs/source/guides/arrays.rst | 52 +++++++++++++++++++++++++ docs/source/index.rst | 1 + docs/source/reference/arrays.rst | 1 + src/manim_data_structures/m_array.py | 11 ++++++ src/manim_data_structures/m_variable.py | 15 +++---- 6 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 docs/source/example.rst diff --git a/docs/source/example.rst b/docs/source/example.rst new file mode 100644 index 0000000..e18653a --- /dev/null +++ b/docs/source/example.rst @@ -0,0 +1,45 @@ +Example Gallery +=============== + +.. currentmodule:: manim_data_structures.m_array + +Find Pair Sum In Sorted MArray +------------------------------ + +The code snippet below uses the famous two pointer technique to find the pair sum ``17`` in the sorted array ``[2, 3, 5, 8, 9, 10, 11]``. + +.. manim:: MainScene + :quality: low + + from manim_data_structures import * + + class MainScene(Scene): + def isPairSumAnim(self, arr, n, val): + p_i = MArrayPointer(self, arr, 0, 'i', mob_arrow_args={'color': GREEN}, mob_label_args={'color': GREEN}) + p_j = MArrayPointer(self, arr, n - 1, 'j', mob_arrow_args={'color': YELLOW}, mob_label_args={'color': YELLOW}) + pair_sum = MVariable(self, 0, label='Sum') + pair_sum.shift(DOWN * 2) + + self.play(Create(pair_sum)) + self.play(Create(p_i), Create(p_j)) + + while (p_i.fetch_index() < p_j.fetch_index()): + pair_sum.update_value(arr.fetch_arr()[p_i.fetch_index()] + arr.fetch_arr()[p_j.fetch_index()]) + + if (pair_sum.fetch_value() == val): + pair_sum.fetch_mob_square().set(fill_color=GREEN) + return True + elif(pair_sum.fetch_value() < val): + p_i.shift_to_elem(p_i.fetch_index() + 1) + else: + p_j.shift_to_elem(p_j.fetch_index() - 1) + + pair_sum.fetch_mob_square().set(fill_color=RED) + return False + + def construct(self): + arr = MArray(self, [2, 3, 5, 8, 9, 10, 11], label='Array') + arr.shift(UP + LEFT * 2) + self.add(arr) + self.isPairSumAnim(arr, 7, 17) + self.wait(1) diff --git a/docs/source/guides/arrays.rst b/docs/source/guides/arrays.rst index 7990f50..0b4077e 100644 --- a/docs/source/guides/arrays.rst +++ b/docs/source/guides/arrays.rst @@ -552,4 +552,56 @@ You can also update the value and the index of an existing array using the :py:c You can also pass ``mob_value_args`` and ``mob_index_args`` to respective methods to customize the updated element mobject. +Using MArrayPointer +~~~~~~~~~~~~~~~~~~~ + +Thus far, if you had been hoping for a pointer to associate with your array, then your prayers have been answered. The :class:`MArrayPointer` allows you to attach a pointer with your array. The following snippet demonstrates its capabilities: + +.. code-block:: python + :linenos: + + class MyScene(Scene): + def construct(self): + arr = MArray(self, [1, 2, 3, 4, 5], label='Array') + arr.shift(UP + LEFT * 2) + self.add(arr) + + pointer = MArrayPointer(self, arr, 2, 'P') + self.play(Create(pointer)) + self.wait(1) + pointer.shift_to_elem(4) + self.wait(1) + pointer.shift_to_elem(0) + self.wait(1) + pointer.attach_to_elem(2) + + self.wait(1) + +.. raw:: html + +
+ +.. manim:: MyScene + :hide_source: + :quality: low + + from manim_data_structures import * + + class MyScene(Scene): + def construct(self): + arr = MArray(self, [1, 2, 3, 4, 5], label='Array') + arr.shift(UP + LEFT * 2) + self.add(arr) + + pointer = MArrayPointer(self, arr, 2, 'P') + self.play(Create(pointer)) + self.wait(1) + pointer.shift_to_elem(4) + self.wait(1) + pointer.shift_to_elem(0) + self.wait(1) + pointer.attach_to_elem(2) + + self.wait(1) + With this we conclude this guide. We hope you found it useful! ✌️ diff --git a/docs/source/index.rst b/docs/source/index.rst index ffeccfb..55b1f32 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -65,5 +65,6 @@ Index .. toctree:: :maxdepth: 2 + example guides/index reference/index diff --git a/docs/source/reference/arrays.rst b/docs/source/reference/arrays.rst index 8b054cf..b73b308 100644 --- a/docs/source/reference/arrays.rst +++ b/docs/source/reference/arrays.rst @@ -8,3 +8,4 @@ Arrays ~m_array.MArrayElement ~m_array.MArray + ~m_array.MArrayPointer diff --git a/src/manim_data_structures/m_array.py b/src/manim_data_structures/m_array.py index 77a234e..0c4bbbb 100644 --- a/src/manim_data_structures/m_array.py +++ b/src/manim_data_structures/m_array.py @@ -1879,6 +1879,17 @@ def fetch_mob_label(self) -> Text: return self.__mob_label + def fetch_index(self) -> int: + """Fetches the index that the pointer is attached to. + + Returns + ------- + :class:`int` + Represents the index that the pointer is attached to. + """ + + return self.__index + def update_mob_label( self, label: str, diff --git a/src/manim_data_structures/m_variable.py b/src/manim_data_structures/m_variable.py index 195cb29..c151ffb 100644 --- a/src/manim_data_structures/m_variable.py +++ b/src/manim_data_structures/m_variable.py @@ -24,6 +24,8 @@ class MVariable(MArrayElement): 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. Attributes ---------- @@ -41,6 +43,7 @@ def __init__( value="", index="", label="", + mob_square_args: dict = {}, mob_value_args: dict = {}, mob_index_args: dict = {}, mob_label_args: dict = {}, @@ -64,15 +67,8 @@ 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. - - Attributes - ---------- - __value - Specifies the value of the variable. - __index - Specifies the index of the variable. - __label - Specifies the label of the variable. + mob_label_args : :class:`dict`, default: `{}` + Arguments for :class:`manim.Text` that represents the element label. """ self.__value = value @@ -85,6 +81,7 @@ def __init__( super().__init__( scene=scene, + mob_square_args=mob_square_args, mob_value_args=mob_value_args, mob_index_args=mob_index_args, mob_label_args=mob_label_args,