2022-02-15 14:37:15 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2022-04-15 00:55:02 +08:00
|
|
|
|
import numpy as np
|
|
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
|
from manimlib.constants import DOWN, LEFT, RIGHT, UP
|
2022-12-15 20:33:58 -08:00
|
|
|
|
from manimlib.constants import WHITE
|
2021-01-18 08:20:14 -10:00
|
|
|
|
from manimlib.mobject.svg.tex_mobject import SingleStringTex
|
2021-01-30 18:13:38 -08:00
|
|
|
|
from manimlib.mobject.svg.text_mobject import Text
|
2018-12-24 12:37:51 -08:00
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
2018-03-31 18:49:28 -07:00
|
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2022-12-16 20:19:18 -08:00
|
|
|
|
from typing import TypeVar
|
2022-12-16 20:35:26 -08:00
|
|
|
|
from manimlib.typing import ManimColor, Vect3
|
2022-04-12 19:19:59 +08:00
|
|
|
|
|
|
|
|
|
T = TypeVar("T", bound=VMobject)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
2021-01-30 18:13:38 -08:00
|
|
|
|
|
2018-03-31 18:49:28 -07:00
|
|
|
|
class DecimalNumber(VMobject):
|
2022-12-15 20:33:58 -08:00
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
number: float | complex = 0,
|
|
|
|
|
color: ManimColor = WHITE,
|
|
|
|
|
stroke_width: float = 0,
|
|
|
|
|
fill_opacity: float = 1.0,
|
|
|
|
|
num_decimal_places: int = 2,
|
|
|
|
|
include_sign: bool = False,
|
|
|
|
|
group_with_commas: bool = True,
|
|
|
|
|
digit_buff_per_font_unit: float = 0.001,
|
|
|
|
|
show_ellipsis: bool = False,
|
|
|
|
|
unit: str | None = None, # Aligned to bottom unless it starts with "^"
|
|
|
|
|
include_background_rectangle: bool = False,
|
2022-12-16 20:35:26 -08:00
|
|
|
|
edge_to_fix: Vect3 = LEFT,
|
2022-12-15 20:33:58 -08:00
|
|
|
|
font_size: int = 48,
|
|
|
|
|
text_config: dict = dict(), # Do not pass in font_size here
|
|
|
|
|
**kwargs
|
|
|
|
|
):
|
|
|
|
|
self.num_decimal_places = num_decimal_places
|
|
|
|
|
self.include_sign = include_sign
|
|
|
|
|
self.group_with_commas = group_with_commas
|
|
|
|
|
self.digit_buff_per_font_unit = digit_buff_per_font_unit
|
|
|
|
|
self.show_ellipsis = show_ellipsis
|
|
|
|
|
self.unit = unit
|
|
|
|
|
self.include_background_rectangle = include_background_rectangle
|
|
|
|
|
self.edge_to_fix = edge_to_fix
|
|
|
|
|
self.font_size = font_size
|
|
|
|
|
self.text_config = text_config
|
|
|
|
|
|
|
|
|
|
super().__init__(
|
|
|
|
|
color=color,
|
|
|
|
|
stroke_width=stroke_width,
|
|
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
|
**kwargs
|
|
|
|
|
)
|
|
|
|
|
|
2021-01-13 00:12:08 -10:00
|
|
|
|
self.set_submobjects_from_number(number)
|
2022-05-11 12:45:51 -07:00
|
|
|
|
self.init_colors()
|
2021-01-13 00:12:08 -10:00
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def set_submobjects_from_number(self, number: float | complex) -> None:
|
2018-03-31 18:49:28 -07:00
|
|
|
|
self.number = number
|
2021-01-13 00:12:08 -10:00
|
|
|
|
self.set_submobjects([])
|
2022-04-28 12:14:36 -06:00
|
|
|
|
self.text_config["font_size"] = self.get_font_size()
|
2022-05-11 12:45:51 -07:00
|
|
|
|
num_string = self.num_string = self.get_num_string(number)
|
2022-04-28 12:14:36 -06:00
|
|
|
|
self.add(*(
|
|
|
|
|
Text(ns, **self.text_config)
|
|
|
|
|
for ns in num_string
|
|
|
|
|
))
|
2018-03-31 18:49:28 -07:00
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
# Add non-numerical bits
|
2018-03-31 18:49:28 -07:00
|
|
|
|
if self.show_ellipsis:
|
2022-04-28 12:14:36 -06:00
|
|
|
|
dots = Text("...", **self.text_config)
|
2021-10-01 12:31:16 -07:00
|
|
|
|
dots.arrange(RIGHT, buff=2 * dots[0].get_width())
|
|
|
|
|
self.add(dots)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
if self.unit is not None:
|
2022-04-28 12:14:36 -06:00
|
|
|
|
self.unit_sign = SingleStringTex(self.unit, font_size=self.get_font_size())
|
2018-03-31 18:49:28 -07:00
|
|
|
|
self.add(self.unit_sign)
|
|
|
|
|
|
2019-02-04 14:54:25 -08:00
|
|
|
|
self.arrange(
|
2021-01-12 13:08:24 -10:00
|
|
|
|
buff=self.digit_buff_per_font_unit * self.get_font_size(),
|
2018-04-06 13:58:59 -07:00
|
|
|
|
aligned_edge=DOWN
|
2018-03-31 18:49:28 -07:00
|
|
|
|
)
|
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
# Handle alignment of parts that should be aligned
|
|
|
|
|
# to the bottom
|
2018-03-31 18:49:28 -07:00
|
|
|
|
for i, c in enumerate(num_string):
|
2021-01-30 18:13:38 -08:00
|
|
|
|
if c == "–" and len(num_string) > i + 1:
|
2019-03-08 14:39:47 -06:00
|
|
|
|
self[i].align_to(self[i + 1], UP)
|
2020-02-18 22:32:02 -08:00
|
|
|
|
self[i].shift(self[i + 1].get_height() * DOWN / 2)
|
2018-06-26 14:39:47 -07:00
|
|
|
|
elif c == ",":
|
|
|
|
|
self[i].shift(self[i].get_height() * DOWN / 2)
|
2018-03-31 18:49:28 -07:00
|
|
|
|
if self.unit and self.unit.startswith("^"):
|
|
|
|
|
self.unit_sign.align_to(self, UP)
|
2020-06-05 17:57:44 -07:00
|
|
|
|
|
2018-03-31 18:49:28 -07:00
|
|
|
|
if self.include_background_rectangle:
|
|
|
|
|
self.add_background_rectangle()
|
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def get_num_string(self, number: float | complex) -> str:
|
2021-01-13 11:11:25 -10:00
|
|
|
|
if isinstance(number, complex):
|
|
|
|
|
formatter = self.get_complex_formatter()
|
|
|
|
|
else:
|
|
|
|
|
formatter = self.get_formatter()
|
|
|
|
|
num_string = formatter.format(number)
|
|
|
|
|
|
|
|
|
|
rounded_num = np.round(number, self.num_decimal_places)
|
|
|
|
|
if num_string.startswith("-") and rounded_num == 0:
|
|
|
|
|
if self.include_sign:
|
|
|
|
|
num_string = "+" + num_string[1:]
|
|
|
|
|
else:
|
|
|
|
|
num_string = num_string[1:]
|
2021-01-30 18:13:38 -08:00
|
|
|
|
num_string = num_string.replace("-", "–")
|
2021-01-13 11:11:25 -10:00
|
|
|
|
return num_string
|
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def init_data(self) -> None:
|
2021-01-12 13:08:24 -10:00
|
|
|
|
super().init_data()
|
|
|
|
|
self.data["font_size"] = np.array([self.font_size], dtype=float)
|
|
|
|
|
|
2022-12-15 20:33:58 -08:00
|
|
|
|
def get_font_size(self) -> int:
|
2022-04-28 12:14:36 -06:00
|
|
|
|
return int(self.data["font_size"][0])
|
2020-12-17 15:59:02 -08:00
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def get_formatter(self, **kwargs) -> str:
|
2018-06-26 14:39:47 -07:00
|
|
|
|
"""
|
|
|
|
|
Configuration is based first off instance attributes,
|
|
|
|
|
but overwritten by any kew word argument. Relevant
|
|
|
|
|
key words:
|
|
|
|
|
- include_sign
|
|
|
|
|
- group_with_commas
|
|
|
|
|
- num_decimal_places
|
|
|
|
|
- field_name (e.g. 0 or 0.real)
|
|
|
|
|
"""
|
2019-02-10 10:56:24 -08:00
|
|
|
|
config = dict([
|
|
|
|
|
(attr, getattr(self, attr))
|
|
|
|
|
for attr in [
|
|
|
|
|
"include_sign",
|
|
|
|
|
"group_with_commas",
|
|
|
|
|
"num_decimal_places",
|
|
|
|
|
]
|
|
|
|
|
])
|
2018-06-26 14:39:47 -07:00
|
|
|
|
config.update(kwargs)
|
2022-05-23 11:07:39 -07:00
|
|
|
|
ndp = config["num_decimal_places"]
|
2018-08-12 19:22:13 -07:00
|
|
|
|
return "".join([
|
2018-06-26 14:39:47 -07:00
|
|
|
|
"{",
|
|
|
|
|
config.get("field_name", ""),
|
|
|
|
|
":",
|
|
|
|
|
"+" if config["include_sign"] else "",
|
|
|
|
|
"," if config["group_with_commas"] else "",
|
2022-05-23 11:07:39 -07:00
|
|
|
|
f".{ndp}f",
|
2018-06-26 14:39:47 -07:00
|
|
|
|
"}",
|
|
|
|
|
])
|
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def get_complex_formatter(self, **kwargs) -> str:
|
2018-08-12 19:22:13 -07:00
|
|
|
|
return "".join([
|
|
|
|
|
self.get_formatter(field_name="0.real"),
|
|
|
|
|
self.get_formatter(field_name="0.imag", include_sign=True),
|
|
|
|
|
"i"
|
|
|
|
|
])
|
2018-06-26 14:39:47 -07:00
|
|
|
|
|
2022-05-11 12:45:27 -07:00
|
|
|
|
def get_tex(self):
|
|
|
|
|
return self.num_string
|
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def set_value(self, number: float | complex):
|
2021-01-13 00:12:08 -10:00
|
|
|
|
move_to_point = self.get_edge_center(self.edge_to_fix)
|
2022-05-11 12:45:51 -07:00
|
|
|
|
style = self.family_members_with_points()[0].get_style()
|
2021-01-13 00:12:08 -10:00
|
|
|
|
self.set_submobjects_from_number(number)
|
|
|
|
|
self.move_to(move_to_point, self.edge_to_fix)
|
2022-05-11 12:45:51 -07:00
|
|
|
|
self.set_style(**style)
|
2018-08-30 14:24:40 -07:00
|
|
|
|
return self
|
2018-08-12 19:05:31 -07:00
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def _handle_scale_side_effects(self, scale_factor: float) -> None:
|
2021-01-12 13:08:24 -10:00
|
|
|
|
self.data["font_size"] *= scale_factor
|
2021-01-03 11:44:53 -08:00
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def get_value(self) -> float | complex:
|
2018-08-12 19:05:31 -07:00
|
|
|
|
return self.number
|
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def increment_value(self, delta_t: float | complex = 1) -> None:
|
2019-04-06 14:01:04 -07:00
|
|
|
|
self.set_value(self.get_value() + delta_t)
|
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
2018-03-31 18:49:28 -07:00
|
|
|
|
class Integer(DecimalNumber):
|
2022-12-15 20:33:58 -08:00
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
number: int = 0,
|
|
|
|
|
num_decimal_places: int = 0,
|
|
|
|
|
**kwargs,
|
|
|
|
|
):
|
|
|
|
|
super().__init__(number, num_decimal_places=num_decimal_places, **kwargs)
|
2019-01-04 14:14:15 -08:00
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
|
def get_value(self) -> int:
|
2019-01-17 14:12:14 -08:00
|
|
|
|
return int(np.round(super().get_value()))
|