From 6f945923638fe284e8e723d6150e5b7151e0b5e5 Mon Sep 17 00:00:00 2001 From: GoThrones Date: Tue, 2 Jun 2026 10:25:10 +0530 Subject: [PATCH 1/3] Refactored LabeledLine class --- manim/mobject/geometry/labeled.py | 102 +++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/manim/mobject/geometry/labeled.py b/manim/mobject/geometry/labeled.py index a9fa8c891a..38d1db0772 100644 --- a/manim/mobject/geometry/labeled.py +++ b/manim/mobject/geometry/labeled.py @@ -20,6 +20,8 @@ from manim.mobject.types.vectorized_mobject import VGroup from manim.utils.color import WHITE from manim.utils.polylabel import polylabel +from manim.utils.space_ops import rotate_vector + if TYPE_CHECKING: from manim.typing import Point3DLike_Array @@ -102,13 +104,8 @@ def __init__( else: raise TypeError("Unsupported label type. Must be MathTex, Tex, or Text.") - # Add a background box self.background_rect = BackgroundRectangle(self.rendered_label, **box_config) - - # Add a frame around the label self.frame = SurroundingRectangle(self.rendered_label, **frame_config) - - # Add components to the VGroup self.add(self.background_rect, self.rendered_label, self.frame) @@ -128,8 +125,20 @@ class LabeledLine(Line): A dictionary containing the configuration for the background box. frame_config A dictionary containing the configuration for the frame. - - .. seealso:: + align_label_with_line + If set to `True`, the label is aligned with the line. + label_above_line + If set to `True`, the label is above the line. For this to work, + the parameter `align_label_with_line` must also be set to True. + label_below_line + If set to `True`, the label is below the line. For this to work, + the parameter `align_label_with_line` must also be set to True. + The parameters `label_above_line` and `label_below_line` cannnot both be + `True` at the same time. + buff + Buffer space between label and line. + + .. seealso:: :class:`LabeledArrow` Examples @@ -141,15 +150,21 @@ class LabeledLine(Line): class LabeledLineExample(Scene): def construct(self): line = LabeledLine( + color= BLUE, label = '0.5', - label_position = 0.8, + label_position = 0.5, label_config = { - "font_size" : 20 + "font_size" : 45, + "stroke_color" : RED }, start=LEFT+DOWN, - end=RIGHT+UP) + end=RIGHT+UP, + align_label_with_line=True, + label_above_line=False, + label_below_line=True, + ) - line.set_length(line.get_length() * 2) + line.set_length(line.get_length()*2) self.add(line) """ @@ -160,27 +175,57 @@ def __init__( label_config: dict[str, Any] | None = None, box_config: dict[str, Any] | None = None, frame_config: dict[str, Any] | None = None, + align_label_with_line: bool = False, + label_above_line: bool = False, + label_below_line: bool = False, + buff: float = MED_SMALL_BUFF, *args: Any, **kwargs: Any, ) -> None: - super().__init__(*args, **kwargs) + if label_above_line and label_below_line: + raise ValueError("label_above_line and label_below_line cannot both be True.") + + super().__init__(*args, **kwargs) + self.label_position = label_position + self.align_label_with_line = align_label_with_line + self.label_above_line = label_above_line + self.label_below_line = label_below_line + self.buff = buff + self.label_angle = 0 - # Create Label self.label = Label( label=label, label_config=label_config, box_config=box_config, frame_config=frame_config, ) - - # Compute Label Position - line_start, line_end = self.get_start_and_end() - new_vec = (line_end - line_start) * label_position - label_coords = line_start + new_vec - - self.label.move_to(label_coords) + self.position_label() self.add(self.label) + def position_label(self): + normal_unit_vector = rotate_vector(self.get_unit_vector(), TAU/4) + label_position = np.clip(self.label_position, 0, 1) + label_position_on_line = self.point_from_proportion(label_position) + + if self.align_label_with_line: + current_angle = self.get_angle() + delta = current_angle - self.label_angle + self.label.rotate(delta, about_point = ORIGIN) + self.label_angle = current_angle + + if self.label_above_line: + label_position_on_line += normal_unit_vector * self.buff + elif self.label_below_line: + label_position_on_line -= normal_unit_vector * self.buff + self.label.move_to(label_position_on_line) + + def scale(self, scale_factor, **kwargs): + super().scale(scale_factor, **kwargs) + if hasattr(self, "label"): + self.label.scale(1/scale_factor) + self.position_label() + return self + class LabeledArrow(LabeledLine, Arrow): """Constructs an arrow containing a label box somewhere along its length. @@ -211,8 +256,21 @@ class LabeledArrow(LabeledLine, Arrow): class LabeledArrowExample(Scene): def construct(self): - l_arrow = LabeledArrow("0.5", start=LEFT*3, end=RIGHT*3 + UP*2, label_position=0.5) - + l_arrow = LabeledArrow( + "0.5", + color = YELLOW, + start=LEFT*1.5+ 1.5*DOWN, + end=RIGHT*1.5 + UP*1.5, + label_config = { + #"font_size" : 20, + "stroke_color" : RED + }, + label_position=0.5, + align_label_with_line=True, + label_above_line=False, + label_below_line = True, + ) + l_arrow.set_length(l_arrow.get_length() * 1.2) self.add(l_arrow) """ From a98d4dbf5fb85c3bd28db6c571da6badbc372b55 Mon Sep 17 00:00:00 2001 From: GoThrones Date: Tue, 2 Jun 2026 11:23:34 +0530 Subject: [PATCH 2/3] Labeled.py file refactored --- manim/mobject/geometry/labeled.py | 54 +++++++++++++++++++------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/manim/mobject/geometry/labeled.py b/manim/mobject/geometry/labeled.py index 38d1db0772..13c162dcbf 100644 --- a/manim/mobject/geometry/labeled.py +++ b/manim/mobject/geometry/labeled.py @@ -4,7 +4,7 @@ __all__ = ["Label", "LabeledLine", "LabeledArrow", "LabeledPolygram"] -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Self import numpy as np @@ -22,7 +22,6 @@ from manim.utils.polylabel import polylabel from manim.utils.space_ops import rotate_vector - if TYPE_CHECKING: from manim.typing import Point3DLike_Array @@ -133,7 +132,7 @@ class LabeledLine(Line): label_below_line If set to `True`, the label is below the line. For this to work, the parameter `align_label_with_line` must also be set to True. - The parameters `label_above_line` and `label_below_line` cannnot both be + The parameters `label_above_line` and `label_below_line` cannot both be `True` at the same time. buff Buffer space between label and line. @@ -183,15 +182,17 @@ def __init__( **kwargs: Any, ) -> None: if label_above_line and label_below_line: - raise ValueError("label_above_line and label_below_line cannot both be True.") - - super().__init__(*args, **kwargs) + raise ValueError( + "label_above_line and label_below_line cannot both be True." + ) + + super().__init__(*args, **kwargs) self.label_position = label_position self.align_label_with_line = align_label_with_line self.label_above_line = label_above_line self.label_below_line = label_below_line self.buff = buff - self.label_angle = 0 + self.label_angle: float = 0 self.label = Label( label=label, @@ -202,29 +203,34 @@ def __init__( self.position_label() self.add(self.label) - def position_label(self): - normal_unit_vector = rotate_vector(self.get_unit_vector(), TAU/4) + def position_label(self) -> None: + normal_unit_vector = rotate_vector(self.get_unit_vector(), TAU / 4) label_position = np.clip(self.label_position, 0, 1) label_position_on_line = self.point_from_proportion(label_position) if self.align_label_with_line: current_angle = self.get_angle() - delta = current_angle - self.label_angle - self.label.rotate(delta, about_point = ORIGIN) + delta = current_angle - self.label_angle + self.label.rotate(delta, about_point=ORIGIN) self.label_angle = current_angle - + if self.label_above_line: label_position_on_line += normal_unit_vector * self.buff elif self.label_below_line: label_position_on_line -= normal_unit_vector * self.buff self.label.move_to(label_position_on_line) - - def scale(self, scale_factor, **kwargs): - super().scale(scale_factor, **kwargs) + + def scale( + self, + scale_factor: float = 1, + *args: Any, + **kwargs: Any, + ) -> Self: + super().scale(scale_factor, *args, **kwargs) if hasattr(self, "label"): - self.label.scale(1/scale_factor) + self.label.scale(1 / scale_factor) self.position_label() - return self + return self class LabeledArrow(LabeledLine, Arrow): @@ -257,10 +263,10 @@ class LabeledArrow(LabeledLine, Arrow): class LabeledArrowExample(Scene): def construct(self): l_arrow = LabeledArrow( - "0.5", + "0.5", color = YELLOW, - start=LEFT*1.5+ 1.5*DOWN, - end=RIGHT*1.5 + UP*1.5, + start=LEFT*1.5+ 1.5*DOWN, + end=RIGHT*1.5 + UP*1.5, label_config = { #"font_size" : 20, "stroke_color" : RED @@ -281,6 +287,14 @@ def __init__( ) -> None: super().__init__(*args, **kwargs) + def scale( # type: ignore[override] + self, + scale_factor: float = 1, + *args: Any, + **kwargs: Any, + ) -> Self: + return super().scale(scale_factor, *args, **kwargs) + class LabeledPolygram(Polygram): """Constructs a polygram containing a label box at its pole of inaccessibility. From edc5b2fd7dbf419468d1ae8c58972c2de897137a Mon Sep 17 00:00:00 2001 From: GoThrones Date: Tue, 2 Jun 2026 12:33:39 +0530 Subject: [PATCH 3/3] Updated LabeledArrow control frame --- .../control_data/geometry/LabeledArrow.npz | Bin 2884 -> 2869 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/test_graphical_units/control_data/geometry/LabeledArrow.npz b/tests/test_graphical_units/control_data/geometry/LabeledArrow.npz index 0c926cf2992c1a8ab667be543b6da6a8036aaad9..177205aa35a54f7ffa516199d539db532fc85f80 100644 GIT binary patch delta 1222 zcmY+EX;9Ng7{)goif9oPs0EdF96Z1R02MG02r&hrq+&Uf0O3qb=te(vXZFLsyZi3*{Px`y*>UZ5 z2jWf5&;S4n*uMdRJ6fdqyAL51mT(B0a!in56SuEJu`m0q)O%ZOd40>o<*!eRPKkYl z-d0ZIyF}JjQftktJ+GWZlR=YWKkLMu=6E5_tKMxi+`@w_%eK%S4u3R}Cf?`ZfxEvE zT~TtiEGwr^JntXftov5Y*Qk>)0`jK8XNY)yh#@R@Fn!M`+wR2>9M1b=#FC+qEZ$u- z;B;rYx8^h|1L6wDpVN`bXRQ!NfxV+6F*PhbuBfC0|C}iB4=BT)UsH|s zcH2dhcFgI=;F|WF4_ziQ9zR`&c-Y?S;i*P0372UAfeoC0T}pGMmqMi z$^-!W2s|E7*;ry22Nv?Sn@K$C#tq8Ss7B8jp(Uu2wE&+SBqzT~H^x&HV$(}O5R zK_s6~S@ARGJ)uVg(uIZV&V{{kWHPxE+q2q_^z~JimzSTwU}~9A!yVgJS9ed(!crVk zddAmxs6AcxZ(c+3txt05={3H1bkpGRY@@h(Tk5?6)+!^$EWsHfIIfeSLlavYGK*nZ z$nUqu;L#T`pH-Bc!w^`J*FHP(xcy)-Xt=Qwm{rrTX1kDjy7FD?aphl}>O*a0W#o-ZXme;LS11&wXqRV-FSU`;xB}xI zdr52ZR6Mq8m>tHof%xiW6>Qbtg69mQGj0pp^)mm_Lq}XGB`vLxzqYCzol(5XnNf|k zZKOxgmG|2G@Q~7jN*>$)@%Q;!oldjDWHM8b^#z5w8iN?9CsU>aN175xvy~BHaYq4o zdLi`Yut+5OIG2@`MYYPEUAxRnIhsl)S8-zcUFBFew`ZS@4jl{iOd9_=M*R5oTTT_L zPZRd*L`T*~HQgmFC?KG}u^ZZswW0qY;iNH~M;mYHonNNtwJX$j#A`${4jGoq!`n;W z)MiYHpKPS_Am3Pd6^^>*8W8e zw;+ycBb<|&o=tkZ1Sv>)|sF6`# zK=NJ{5?X_q4ZJ=fSI zoc2wF-|l$pjW%`UbwM{To3QPz(yXm~XLhI!J5t=!t?imqbZ$3G&>I%E855UI-219% z963ez)^G4R@9y#a#Uy>TwpmMR*{~`%AV{ZQCwb=YX;$gXyF903?JaBa=t;Fpfq#c# zE6JcA`LOiU=;TtcGGPBk)F(?N(8admPy-DO4_GY4J^Oakd@?H6i@0Oj+>zcPv6xZ! zmjwfZmN+ES>E$&wHByM3 z^V_y=J#Mkd@wi5#@kFBlxSlpQ*;iLD4oZS$Zk|M<=-ty5xfg*@93{&5-T6q`Z?s`i zqyyz%&cP@1ai#z)VTqpDME<~pjEDXh7Z=w>+77^TfsMoZ3Jzqnpz_7(qe13yggWXk z=29{MM?Lo0S*n(G$|6Ymj+lXl%PM(el4`W|XVpjjqH1QW!Cg4q ziU{h=!n^kM#%|rS!}?HG=-&KJ#UT%mx=XkV%JOowTrxeKSX5LbHo{15XB_Z^b3Y_z z7WoGRNOGMB%&p}k%NX2+1{*Vnn6VwXVG8F6SR8+<=R+mr5ZMbrNdkVVGH1It z`&`YvUS4#|`+ncjkmbfy-FSV1guOhrs2!Gt8gKZD-qTw}kw~TFeowG3W z!3cat(WLI@BdY1q*8Gomwj5x4#JzU=^ICko%lzCNd4(esX09#0WPXjT9fsOc6>Ya{ zb^0^mPKU3S)so!ZYsyx9cN>_R+(QXF?b& zl;chsQAx@rQ-e*Cu43;k%>gVf#NktSBlF`;uFS4SBD!{HcM|5J*QK|2q1g-j`}?tu zezGuV%ZOS=!K*j7vc_KIgc*V&jT$_Q4^$}qc>cC3S4e<$6`M7?__DV`ttS$cml3(W$+zZsxMCjbBd