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

Finish issue 3804 and pass all the tests of number_line #3962

Open
wants to merge 5 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
114 changes: 114 additions & 0 deletions manim/mobject/graphing/animatable_coordinate_systems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from __future__ import annotations

from manim import *

Check notice

Code scanning / CodeQL

'import *' may pollute namespace Note

Import pollutes the enclosing namespace, as the imported module
manim
does not define '__all__'.


class AnimatableNumberLine(NumberLine):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.x_range_tracker = ValueTracker(self.x_range)

Check warning

Code scanning / CodeQL

Overwriting attribute in super-class or sub-class Warning

Assignment overwrites attribute x_range_tracker, which was previously defined in superclass
NumberLine
.

def get_x_range(self):
return self.x_range_tracker.get_value()

def set_x_range(self, new_range):
self.x_range_tracker.set_value(new_range)
self.x_range = new_range
self.x_min, self.x_max, self.x_step = new_range
self.rebuild()

def rebuild(self):
self.clear()
self.__init__(
x_range=self.get_x_range(),
length=self.length,
include_ticks=self.include_ticks,
include_numbers=self.include_numbers,
**{
k: v
for k, v in self.__dict__.items()
if k not in ["x_range", "length", "include_ticks", "include_numbers"]
},
)


class ChangeNumberLineRange(Animation):
def __init__(self, number_line, new_range, **kwargs):
self.new_range = new_range
super().__init__(number_line, **kwargs)

def interpolate_mobject(self, alpha):
current_range = [
interpolate(start, end, alpha)
for start, end in zip(self.mobject.get_x_range(), self.new_range)
]
self.mobject.set_x_range(current_range)


class AnimatableNumberPlane(NumberPlane):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.x_range_tracker = ValueTracker(self.x_range)
self.y_range_tracker = ValueTracker(self.y_range)

def get_x_range(self):
return self.x_range_tracker.get_value()

def get_y_range(self):
return self.y_range_tracker.get_value()

def set_x_range(self, new_range):
self.x_range_tracker.set_value(new_range)
self.x_range = new_range
self.rebuild()

def set_y_range(self, new_range):
self.y_range_tracker.set_value(new_range)
self.y_range = new_range
self.rebuild()

def rebuild(self):
self.clear()
self.__init__(
x_range=self.get_x_range(),
y_range=self.get_y_range(),
**{
k: v
for k, v in self.__dict__.items()
if k not in ["x_range", "y_range"]
},
)

def get_lines(self):
x_lines = VGroup()
y_lines = VGroup()
x_min, x_max = self.x_range[:2]
y_min, y_max = self.y_range[:2]
for x in self.get_x_axis().get_tick_range():
if x_min <= x <= x_max:
x_lines.add(self.get_vertical_line(self.c2p(x, 0)))
for y in self.get_y_axis().get_tick_range():
if y_min <= y <= y_max:
y_lines.add(self.get_horizontal_line(self.c2p(0, y)))
return VGroup(x_lines, y_lines)


class ChangeNumberPlaneRange(Animation):
def __init__(self, number_plane, new_x_range=None, new_y_range=None, **kwargs):
self.new_x_range = new_x_range
self.new_y_range = new_y_range
super().__init__(number_plane, **kwargs)

def interpolate_mobject(self, alpha):
if self.new_x_range:
current_x_range = [
interpolate(start, end, alpha)
for start, end in zip(self.mobject.get_x_range(), self.new_x_range)
]
self.mobject.set_x_range(current_x_range)
if self.new_y_range:
current_y_range = [
interpolate(start, end, alpha)
for start, end in zip(self.mobject.get_y_range(), self.new_y_range)
]
self.mobject.set_y_range(current_y_range)
142 changes: 102 additions & 40 deletions manim/mobject/graphing/number_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from manim import Animation
from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject

Expand All @@ -24,6 +25,7 @@
from manim.mobject.text.numbers import DecimalNumber
from manim.mobject.text.tex_mobject import MathTex, Tex
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
from manim.mobject.value_tracker import ValueTracker
from manim.utils.bezier import interpolate
from manim.utils.config_ops import merge_dicts_recursively
from manim.utils.space_ops import normalize
Expand Down Expand Up @@ -188,8 +190,10 @@ def __init__(
}

# turn into a NumPy array to scale by just applying the function
self.scaling = scaling
self.x_range = np.array(x_range, dtype=float)
self.x_min, self.x_max, self.x_step = scaling.function(self.x_range)
self.x_min, self.x_max, self.x_step = self.scaling.function(self.x_range)
self.x_range_tracker = ValueTracker(self.x_min)
self.length = length
self.unit_size = unit_size
# ticks
Expand Down Expand Up @@ -267,6 +271,11 @@ def __init__(
font_size=self.font_size,
)

def clear(self):
self.submobjects = []
self.points = np.zeros((0, 3))
return self

def rotate_about_zero(self, angle: float, axis: Sequence[float] = OUT, **kwargs):
return self.rotate_about_number(0, angle, axis, **kwargs)

Expand Down Expand Up @@ -325,7 +334,7 @@ def get_tick_range(self) -> np.ndarray:
np.ndarray
A numpy array of floats represnting values along the number line.
"""
x_min, x_max, x_step = self.x_range
x_min, x_max, x_step = self.get_x_range()
if not self.include_tip:
x_max += 1e-6

Expand All @@ -345,40 +354,31 @@ def get_tick_range(self) -> np.ndarray:
return self.scaling.function(tick_range)

def number_to_point(self, number: float | np.ndarray) -> np.ndarray:
"""Accepts a value along the number line and returns a point with
respect to the scene.
Equivalent to `NumberLine @ number`

Parameters
----------
number
The value to be transformed into a coordinate. Or a list of values.

Returns
-------
np.ndarray
A point with respect to the scene's coordinate system. Or a list of points.
"""
...

Examples
--------

>>> from manim import NumberLine
>>> number_line = NumberLine()
>>> number_line.number_to_point(0)
array([0., 0., 0.])
>>> number_line.number_to_point(1)
array([1., 0., 0.])
>>> number_line @ 1
array([1., 0., 0.])
>>> number_line.number_to_point([1, 2, 3])
array([[1., 0., 0.],
[2., 0., 0.],
[3., 0., 0.]])
>>> number_line = NumberLine(x_range=[-10, 10, 1])
>>> number_line.number_to_point(0)
array([0., 0., 0.])
>>> number_line.number_to_point(1)
array([1., 0., 0.])
>>> number_line @ 1
array([1., 0., 0.])
>>> number_line.number_to_point([1, 2, 3])
array([[1., 0., 0.],
[2., 0., 0.],
[3., 0., 0.]])
>>> number_line.set_x_range([-5, 5, 1])
>>> number_line.number_to_point(1)
array([2., 0., 0.])
"""
number = np.asarray(number)
scalar = number.ndim == 0
number = self.scaling.inverse_function(number)
alphas = (number - self.x_range[0]) / (self.x_range[1] - self.x_range[0])
x_min, x_max, _ = self.get_x_range()
alphas = (number - x_min) / (x_max - x_min)
alphas = float(alphas) if scalar else np.vstack(alphas)
val = interpolate(self.get_start(), self.get_end(), alphas)
return val
Expand All @@ -399,22 +399,26 @@ def point_to_number(self, point: Sequence[float]) -> float:

Examples
--------

>>> from manim import NumberLine
>>> number_line = NumberLine()
>>> number_line.point_to_number((0, 0, 0))
0.0
>>> number_line.point_to_number((1, 0, 0))
1.0
>>> number_line.point_to_number([[0.5, 0, 0], [1, 0, 0], [1.5, 0, 0]])
array([0.5, 1. , 1.5])

>>> number_line = NumberLine(x_range=[-10, 10, 1])
>>> number_line.point_to_number((0, 0, 0))
0.0
>>> number_line.point_to_number((1, 0, 0))
1.0
>>> import numpy as np
>>> np.round(
... number_line.point_to_number(
... np.array([[0.5, 0, 0], [1, 0, 0], [1.5, 0, 0]])
... ),
... decimals=1,
... )
array([0.5, 1. , 1.5])
"""
point = np.asarray(point)
start, end = self.get_start_and_end()
unit_vect = normalize(end - start)
proportion = np.dot(point - start, unit_vect) / np.dot(end - start, unit_vect)
return interpolate(self.x_min, self.x_max, proportion)
x_min, x_max, _ = self.get_x_range()
return interpolate(x_min, x_max, proportion)

def n2p(self, number: float | np.ndarray) -> np.ndarray:
"""Abbreviation for :meth:`~.NumberLine.number_to_point`."""
Expand Down Expand Up @@ -655,6 +659,51 @@ def __rmatmul__(self, other: Point3D | Mobject):
other = other.get_center()
return self.p2n(other)

def get_x_range(self):
x_min = self.x_range_tracker.get_value()
return [x_min, self.x_max, self.x_step]

def set_x_range(self, new_range):
self.x_range = np.array(new_range, dtype=float)
self.x_min, self.x_max, self.x_step = self.scaling.function(self.x_range)
self.x_range_tracker.set_value(self.x_min)

if self.include_ticks:
self.add_ticks()
if self.include_numbers:
self.add_numbers()

if self.length:
self.set_length(self.length)
self.unit_size = self.get_unit_size()
else:
self.scale(self.unit_size)

self.center()

def rebuild(self):
current_x_min = self.x_range_tracker.get_value()
current_scaling = self.scaling
current_kwargs = {
k: v
for k, v in self.__dict__.items()
if k
not in ["x_range", "length", "include_ticks", "include_numbers", "scaling"]
}

current_submobjects = list(self.submobjects)

self.__init__(
x_range=[current_x_min, self.x_max, self.x_step],
length=self.length,
include_ticks=self.include_ticks,
include_numbers=self.include_numbers,
scaling=current_scaling,
**current_kwargs,
)

self.submobjects = current_submobjects


class UnitInterval(NumberLine):
def __init__(
Expand Down Expand Up @@ -685,3 +734,16 @@ def __init__(
decimal_number_config=decimal_number_config,
**kwargs,
)


class ChangeNumberLineRange(Animation):
def __init__(self, number_line, new_range, **kwargs):
self.new_range = new_range
super().__init__(number_line, **kwargs)

def interpolate_mobject(self, alpha):
current_x_min = interpolate(
self.mobject.get_x_range()[0], self.new_range[0], alpha
)
current_range = [current_x_min, self.new_range[1], self.new_range[2]]
self.mobject.set_x_range(current_range)
Loading