Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ConvexHull and LabeledPolygram #3933

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 257 additions & 33 deletions manim/mobject/geometry/labeled.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,102 @@

from __future__ import annotations

__all__ = ["LabeledLine", "LabeledArrow"]
__all__ = ["Label", "LabeledLine", "LabeledArrow", "LabeledPolygram"]

import numpy as np

from manim.constants import *
from manim.mobject.geometry.line import Arrow, Line
from manim.mobject.geometry.polygram import Polygram
from manim.mobject.geometry.shape_matchers import (
BackgroundRectangle,
SurroundingRectangle,
)
from manim.mobject.text.tex_mobject import MathTex, Tex
from manim.mobject.text.text_mobject import Text
from manim.mobject.types.vectorized_mobject import VGroup
from manim.utils.color import WHITE, ManimColor, ParsableManimColor
from manim.utils.polylabel import PolyLabel


class Label(VGroup):
"""A Label consisting of text and frame

Parameters
----------
label : str | Tex | MathTex | Text
Label that will be displayed on the line.
font_size : float | optional
Control font size for the label. This parameter is only used when `label` is of type `str`.
label_color: ParsableManimColor | optional
The color of the label's text. This parameter is only used when `label` is of type `str`.
label_frame : Bool | optional
Add a `SurroundingRectangle` frame to the label box.
frame_fill_color : ParsableManimColor | optional
Background color to fill the label box. If no value is provided, the background color of the canvas will be used.
frame_fill_opacity : float | optional
Determine the opacity of the label box by passing a value in the range [0-1], where 0 indicates complete transparency and 1 means full opacity.

Examples
--------
.. manim:: LabelExample
:save_last_frame:
:quality: high

class LabelExample(Scene):
def construct(self):
label = Label(
label = Text('Label Text', font='sans-serif'),
label_color = WHITE,
label_frame = True
)
label.scale(3)
self.add(label)
"""

def __init__(
self,
label: str | Tex | MathTex | Text,
font_size: float = DEFAULT_FONT_SIZE,
label_color: ParsableManimColor = WHITE,
label_frame: bool = True,
frame_fill_color: ParsableManimColor = None,
frame_fill_opacity: float = 1,
**kwargs,
) -> None:
Comment on lines +23 to +67
Copy link
Contributor

@chopan050 chopan050 Oct 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not necessary to write again the parameter types inside the docstring. Only the signature typings are needed.

Maybe the label_frame parameter could be deleted, because IMO it doesn't make sense to use a Label without adding a frame to it. In that case, one could simply use Tex, MathTex or Text. If it does make sense, then I would suggest a name which better reflects its "booleanness", such as use_label_frame or add_label_frame.

It would be great if people were able to modify text and frame attributes other than the label's font size or color, or the frame's fill color or opacity. Many other Mobjects in Manim are composed of other submobjects which can be configured independently via config dict parameters. Therefore, I'd suggest the same approach and replace these parameters with label_config and frame_config dictionaries.

Suggested change
class Label(VGroup):
"""A Label consisting of text and frame
Parameters
----------
label : str | Tex | MathTex | Text
Label that will be displayed on the line.
font_size : float | optional
Control font size for the label. This parameter is only used when `label` is of type `str`.
label_color: ParsableManimColor | optional
The color of the label's text. This parameter is only used when `label` is of type `str`.
label_frame : Bool | optional
Add a `SurroundingRectangle` frame to the label box.
frame_fill_color : ParsableManimColor | optional
Background color to fill the label box. If no value is provided, the background color of the canvas will be used.
frame_fill_opacity : float | optional
Determine the opacity of the label box by passing a value in the range [0-1], where 0 indicates complete transparency and 1 means full opacity.
Examples
--------
.. manim:: LabelExample
:save_last_frame:
:quality: high
class LabelExample(Scene):
def construct(self):
label = Label(
label = Text('Label Text', font='sans-serif'),
label_color = WHITE,
label_frame = True
)
label.scale(3)
self.add(label)
"""
def __init__(
self,
label: str | Tex | MathTex | Text,
font_size: float = DEFAULT_FONT_SIZE,
label_color: ParsableManimColor = WHITE,
label_frame: bool = True,
frame_fill_color: ParsableManimColor = None,
frame_fill_opacity: float = 1,
**kwargs,
) -> None:
class Label(VGroup):
"""A Label consisting of a text surrounded by a frame.
Parameters
----------
label
Label that will be displayed on the line.
label_config
A dictionary containing the configuration for the label. This is only applied if `label` is of type `str`.
frame_config
A dictionary containing the configuration for the frame.
Examples
--------
.. manim:: LabelExample
:save_last_frame:
:quality: high
class LabelExample(Scene):
def construct(self):
label = Label(
label=Text('Label Text', font='sans-serif', color=WHITE),
)
label.scale(3)
self.add(label)
"""
def __init__(
self,
label: str | Tex | MathTex | Text,
label_config: dict[str, Any] | None = None,
frame_config: dict[str, Any] | None = None,
**kwargs,
) -> None:

super().__init__(**kwargs)

label_color = ManimColor(label_color)
frame_fill_color = ManimColor(frame_fill_color)

# Determine the type of label and instantiate the appropriate object
if isinstance(label, str):
self.rendered_label = MathTex(label, color=label_color, font_size=font_size)
elif isinstance(label, (MathTex, Tex, Text)):
self.rendered_label = label
else:
raise ValueError("Unsupported label type. Must be MathTex, Tex, or Text.")

# Add background box
self.background_rect = BackgroundRectangle(
self.rendered_label,
buff=0.05,
color=frame_fill_color,
fill_opacity=frame_fill_opacity,
stroke_width=0.5,
)

# Optionally add a frame around the label
self.frame = None
if label_frame:
self.frame = SurroundingRectangle(
self.rendered_label, buff=0.05, color=label_color, stroke_width=0.5
)

# Add components to the VGroup
self.add(self.background_rect, self.rendered_label)
if self.frame:
self.add(self.frame)


class LabeledLine(Line):
Expand Down Expand Up @@ -51,11 +136,9 @@ def construct(self):
font_size = 20,
label_color = WHITE,
label_frame = True,

start=LEFT+DOWN,
end=RIGHT+UP)


line.set_length(line.get_length() * 2)
self.add(line)
"""
Expand All @@ -72,42 +155,25 @@ def __init__(
*args,
**kwargs,
) -> None:
label_color = ManimColor(label_color)
frame_fill_color = ManimColor(frame_fill_color)
if isinstance(label, str):
from manim import MathTex

rendered_label = MathTex(label, color=label_color, font_size=font_size)
else:
rendered_label = label

super().__init__(*args, **kwargs)

# calculating the vector for the label position
# Create Label
self.label = Label(
label=label,
font_size=font_size,
label_color=label_color,
label_frame=label_frame,
frame_fill_color=frame_fill_color,
frame_fill_opacity=frame_fill_opacity,
)

# 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

# rendered_label.move_to(self.get_vector() * label_position)
rendered_label.move_to(label_coords)

box = BackgroundRectangle(
rendered_label,
buff=0.05,
color=frame_fill_color,
fill_opacity=frame_fill_opacity,
stroke_width=0.5,
)
self.add(box)

if label_frame:
box_frame = SurroundingRectangle(
rendered_label, buff=0.05, color=label_color, stroke_width=0.5
)

self.add(box_frame)

self.add(rendered_label)
self.label.move_to(label_coords)
self.add(self.label)


class LabeledArrow(LabeledLine, Arrow):
Expand Down Expand Up @@ -153,3 +219,161 @@ def __init__(
**kwargs,
) -> None:
super().__init__(*args, **kwargs)


class LabeledPolygram(Polygram):
"""Constructs a polygram containing a label box at its pole of inaccessibility.

Parameters
----------
label : str | Tex | MathTex | Text
Label that will be displayed on the line.
precision : float | optional
The precision used by the PolyLabel algorithm.
font_size : float | optional
Control font size for the label. This parameter is only used when `label` is of type `str`.
label_color: ParsableManimColor | optional
The color of the label's text. This parameter is only used when `label` is of type `str`.
label_frame : Bool | optional
Add a `SurroundingRectangle` frame to the label box.
frame_fill_color : ParsableManimColor | optional
Background color to fill the label box. If no value is provided, the background color of the canvas will be used.
frame_fill_opacity : float | optional
Determine the opacity of the label box by passing a value in the range [0-1], where 0 indicates complete transparency and 1 means full opacity.
Comment on lines +224 to +242
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same suggestions as before: don't write the types again in the docstring, and use label_config and frame_config parameters.

Don't forget to include the vertex_groups parameter.



.. note::
The PolyLabel Algorithm expects each vertex group to form a closed ring.
If the input is open, LabeledPolygram will attempt to close it.
This may cause the polygon to intersect itself leading to unexpected results.

.. tip::
Make sure the precision corresponds to the scale of your inputs!
For instance, if the bounding box of your polygon stretches from 0 to 10,000, a precision of 1.0 or 10.0 should be sufficient.

Examples
--------
.. manim:: LabeledPolygramExample
:save_last_frame:
:quality: high

class LabeledPolygramExample(Scene):
def construct(self):
# Define Rings
ring1 = [
[-3.8, -2.4, 0], [-2.4, -2.5, 0], [-1.3, -1.6, 0], [-0.2, -1.7, 0],
[1.7, -2.5, 0], [2.9, -2.6, 0], [3.5, -1.5, 0], [4.9, -1.4, 0],
[4.5, 0.2, 0], [4.7, 1.6, 0], [3.5, 2.4, 0], [1.1, 2.5, 0],
[-0.1, 0.9, 0], [-1.2, 0.5, 0], [-1.6, 0.7, 0], [-1.4, 1.9, 0],
[-2.6, 2.6, 0], [-4.4, 1.2, 0], [-4.9, -0.8, 0], [-3.8, -2.4, 0]
]
ring2 = [
[0.2, -1.2, 0], [0.9, -1.2, 0], [1.4, -2.0, 0], [2.1, -1.6, 0],
[2.2, -0.5, 0], [1.4, 0.0, 0], [0.4, -0.2, 0], [0.2, -1.2, 0]
]
ring3 = [[-2.7, 1.4, 0], [-2.3, 1.7, 0], [-2.8, 1.9, 0], [-2.7, 1.4, 0]]

# Create Polygons (for reference)
p1 = Polygon(*ring1, fill_opacity=0.75)
p2 = Polygon(*ring2, fill_color=BLACK, fill_opacity=1)
p3 = Polygon(*ring3, fill_color=BLACK, fill_opacity=1)

# Create Labeled Polygram
polygram = LabeledPolygram(
*[ring1, ring2, ring3],
label=Text('Pole', font='sans-serif'),
precision=0.01,
)

# Display Circle (for reference)
circle = Circle(radius=polygram.radius, color=WHITE).move_to(polygram.pole)

self.add(p1, p2, p3)
self.add(polygram)
self.add(circle)

.. manim:: LabeledCountryExample
:save_last_frame:
:quality: high

import requests
import json

class LabeledCountryExample(Scene):
def construct(self):
# Fetch JSON data and process arcs
data = requests.get('https://cdn.jsdelivr.net/npm/us-atlas@3/nation-10m.json').json()
arcs, transform = data['arcs'], data['transform']
sarcs = [np.cumsum(arc, axis=0) * transform['scale'] + transform['translate'] for arc in arcs]
ssarcs = sorted(sarcs, key=len, reverse=True)[:1]

# Compute Bounding Box
points = np.concatenate(ssarcs)
mins, maxs = np.min(points, axis=0), np.max(points, axis=0)

# Build Axes
ax = Axes(
x_range=[mins[0], maxs[0], maxs[0] - mins[0]], x_length=10,
y_range=[mins[1], maxs[1], maxs[1] - mins[1]], y_length=7,
tips=False
)

# Adjust Coordinates
array = [[ax.c2p(*point) for point in sarc] for sarc in ssarcs]

# Add Polygram
polygram = LabeledPolygram(
*array,
label=Text('USA', font='sans-serif'),
precision=0.01,
fill_color=BLUE,
stroke_width=0,
fill_opacity=0.75
)

# Display Circle (for reference)
circle = Circle(radius=polygram.radius, color=WHITE).move_to(polygram.pole)

self.add(ax)
self.add(polygram)
self.add(circle)
"""

def __init__(
self,
*vertex_groups: Point3D,
label: str | Tex | MathTex | Text,
precision: float = 0.01,
font_size: float = DEFAULT_FONT_SIZE,
label_color: ParsableManimColor = WHITE,
label_frame: bool = True,
frame_fill_color: ParsableManimColor = None,
frame_fill_opacity: float = 1,
**kwargs,
) -> None:
# Initialize the Polygram with the vertex groups
super().__init__(*vertex_groups, **kwargs)

# Create Label
self.label = Label(
label=label,
font_size=font_size,
label_color=label_color,
label_frame=label_frame,
frame_fill_color=frame_fill_color,
frame_fill_opacity=frame_fill_opacity,
)

# Close Vertex Groups
rings = [
group if np.array_equal(group[0], group[-1]) else group + [group[0]]
for group in vertex_groups
]

# Compute the Pole of Inaccessibility
cell = PolyLabel(rings, precision=precision)
self.pole, self.radius = np.pad(cell.c, (0, 1), "constant"), cell.d

# Position the label at the pole
self.label.move_to(self.pole)
self.add(self.label)
Loading
Loading