Updating coordinate system mobjects

This commit is contained in:
Grant Sanderson 2019-02-06 21:16:26 -08:00
parent 24d6113bba
commit d88c301622
25 changed files with 425 additions and 354 deletions

View file

@ -41,7 +41,7 @@ class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene):
self.add_foreground_mobject(title)
def edit_background_plane(self):
self.background.main_lines.set_stroke(GREY, 2)
self.backgrounds.set_stroke(GREY, 2)
self.background.secondary_lines.set_stroke(DARK_GREY, 1)
self.add_foreground_mobject(self.background.coordinate_labels)

View file

@ -14,7 +14,7 @@ from manimlib.utils.config_ops import digest_config
class Container(object):
def __init__(self, *submobjects, **kwargs):
def __init__(self, **kwargs):
digest_config(self, kwargs)
def add(self, *items):

View file

@ -1,4 +1,5 @@
import numpy as np
import itertools as it
from manimlib.constants import *
from manimlib.mobject.functions import ParametricFunction
@ -7,17 +8,90 @@ from manimlib.mobject.geometry import Line
from manimlib.mobject.number_line import NumberLine
from manimlib.mobject.svg.tex_mobject import TexMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_config
from manimlib.utils.simple_functions import binary_search
from manimlib.utils.space_ops import angle_of_vector
# TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene
class Axes(VGroup):
class CoordinateSystem():
"""
Abstract class for Axes and NumberPlane
"""
CONFIG = {
"dimension": 2,
"x_min": -FRAME_X_RADIUS,
"x_max": FRAME_X_RADIUS,
"y_min": -FRAME_Y_RADIUS,
"y_max": FRAME_Y_RADIUS,
}
def coords_to_point(self, *coords):
raise Exception("Not implemented")
def point_to_coords(self, point):
raise Exception("Not implemented")
def get_axes(self):
raise Exception("Not implemented")
def get_axis(self, index):
return self.get_axes()[index]
def get_x_axis(self):
return self.get_axis(0)
def get_y_axis(self):
return self.get_axis(1)
def get_z_axis(self):
return self.get_axis(2)
def get_graph(self, function, **kwargs):
x_min = kwargs.pop("x_min", self.x_min)
x_max = kwargs.pop("x_max", self.x_max)
graph = ParametricFunction(
lambda t: self.coords_to_point(t, function(t)),
t_min=x_min,
t_max=x_max,
**kwargs
)
graph.underlying_function = function
return graph
def get_parametric_curve(self, function, **kwargs):
dim = self.dimension
graph = ParametricFunction(
lambda t: self.coords_to_point(
*function(t)[:dim]
),
**kwargs
)
graph.underlying_function = function
return graph
def input_to_graph_point(self, x, graph):
if hasattr(graph, "underlying_function"):
return self.coords_to_point(x, graph.underlying_function(x))
else:
alpha = binary_search(
function=lambda a: self.point_to_coords(
graph.point_from_proportion(a)
)[0],
target=x,
lower_bound=self.x_min,
uplper_bound=self.x_max,
)
if alpha is not None:
return graph.point_from_proportion(alpha)
else:
return None
class Axes(VGroup, CoordinateSystem):
CONFIG = {
"three_d": False,
"number_line_config": {
"color": LIGHT_GREY,
"include_tip": True,
@ -26,24 +100,26 @@ class Axes(VGroup):
"y_axis_config": {
"label_direction": LEFT,
},
"x_min": -FRAME_X_RADIUS,
"x_max": FRAME_X_RADIUS,
"y_min": -FRAME_Y_RADIUS,
"y_max": FRAME_Y_RADIUS,
"center_point": ORIGIN,
}
def __init__(self, **kwargs):
VGroup.__init__(self, **kwargs)
self.x_axis = self.get_axis(
self.x_axis = self.create_axis(
self.x_min, self.x_max, self.x_axis_config
)
self.y_axis = self.get_axis(
self.y_axis = self.create_axis(
self.y_min, self.y_max, self.y_axis_config
)
self.y_axis.rotate(90 * DEGREES, about_point=ORIGIN)
self.add(self.x_axis, self.y_axis)
# Add as a separate group incase various other
# mobjects are added to self, as for example in
# NumberPlane below
self.axes = VGroup(self.x_axis, self.y_axis)
self.add(*self.axes)
self.shift(self.center_point)
def get_axis(self, min_val, max_val, axis_config):
def create_axis(self, min_val, max_val, axis_config):
new_config = merge_config([
axis_config,
{"x_min": min_val, "x_max": max_val},
@ -54,63 +130,23 @@ class Axes(VGroup):
def coords_to_point(self, *coords):
origin = self.x_axis.number_to_point(0)
result = np.array(origin)
for axis, coord in zip(self, coords):
for axis, coord in zip(self.get_axes(), coords):
result += (axis.number_to_point(coord) - origin)
return result
def point_to_coords(self, point):
return tuple([
axis.point_to_number(point)
for axis in self
if isinstance(axis, NumberLine)
for axis in self.get_axes()
])
def get_graph(
self, function,
x_min=None,
x_max=None,
**kwargs
):
kwargs["fill_opacity"] = kwargs.get("fill_opacity", 0)
x_min = x_min or self.x_min
x_max = x_max or self.x_max
graph = ParametricFunction(
lambda t: self.coords_to_point(t, function(t)),
t_min=x_min,
t_max=x_max,
**kwargs
)
graph.underlying_function = function
return graph
def input_to_graph_point(self, x, graph):
if hasattr(graph, "underlying_function"):
return self.coords_to_point(x, graph.underlying_function(x))
else:
# binary search
lh, rh = 0, 1
while abs(lh - rh) > 0.001:
mh = np.mean([lh, rh])
hands = [lh, mh, rh]
points = list(map(graph.point_from_proportion, hands))
lx, mx, rx = list(map(self.x_axis.point_to_number, points))
if lx <= x and rx >= x:
if mx > x:
rh = mh
else:
lh = mh
elif lx <= x and rx <= x:
return points[2]
elif lx >= x and rx >= x:
return points[0]
elif lx > x and rx < x:
lh, rh = rh, lh
return points[1]
return self.coords_to_point(x, graph.underlying_function(x))
def get_axes(self):
return self.axes
class ThreeDAxes(Axes):
CONFIG = {
"dimension": 3,
"x_min": -5.5,
"x_max": 5.5,
"y_min": -5.5,
@ -125,7 +161,7 @@ class ThreeDAxes(Axes):
def __init__(self, **kwargs):
Axes.__init__(self, **kwargs)
z_axis = self.z_axis = self.get_axis(
z_axis = self.z_axis = self.create_axis(
self.z_min, self.z_max, self.z_axis_config
)
z_axis.rotate(-np.pi / 2, UP, about_point=ORIGIN)
@ -133,18 +169,19 @@ class ThreeDAxes(Axes):
angle_of_vector(self.z_normal), OUT,
about_point=ORIGIN
)
self.axes.append(z_axis)
self.add(z_axis)
self.add_3d_pieces()
self.set_axis_shading()
def add_3d_pieces(self):
for axis in self:
for axis in self.axes:
axis.pieces = VGroup(
*axis.main_line.get_pieces(self.num_axis_pieces)
*axis.get_pieces(self.num_axis_pieces)
)
axis.add(axis.pieces)
axis.main_line.set_stroke(width=0, family=False)
axis.set_stroke(width=0, family=False)
axis.set_shade_in_3d(True)
def set_axis_shading(self):
@ -161,195 +198,180 @@ class ThreeDAxes(Axes):
submob.set_sheen(0.2)
class NumberPlane(VMobject):
class NumberPlane(Axes):
CONFIG = {
"color": BLUE_D,
"secondary_color": BLUE_E,
"axes_color": WHITE,
"secondary_stroke_width": 1,
# TODO: Allow coordinate center of NumberPlane to not be at (0, 0)
"x_radius": None,
"y_radius": None,
"x_unit_size": 1,
"y_unit_size": 1,
"center_point": ORIGIN,
"axis_config": {
"stroke_color": WHITE,
"stroke_width": 2,
"include_ticks": False,
"include_tip": False,
"line_to_number_buff": SMALL_BUFF,
"label_direction": DR,
"number_scale_val": 0.5,
},
"y_axis_config": {
"label_direction": DR,
},
"background_line_style": {
"stroke_color": BLUE_D,
"stroke_width": 2,
},
# Defaults to a faded version of line_config
"faded_line_style": None,
"x_line_frequency": 1,
"y_line_frequency": 1,
"secondary_line_ratio": 1,
"written_coordinate_height": 0.2,
"propagate_style_to_family": False,
"faded_line_ratio": 1,
"make_smooth_after_applying_functions": True,
}
def generate_points(self):
if self.x_radius is None:
center_to_edge = (FRAME_X_RADIUS + abs(self.center_point[0]))
self.x_radius = center_to_edge / self.x_unit_size
if self.y_radius is None:
center_to_edge = (FRAME_Y_RADIUS + abs(self.center_point[1]))
self.y_radius = center_to_edge / self.y_unit_size
self.axes = VMobject()
self.main_lines = VMobject()
self.secondary_lines = VMobject()
tuples = [
(
self.x_radius,
self.x_line_frequency,
self.y_radius * DOWN,
self.y_radius * UP,
RIGHT
),
(
self.y_radius,
self.y_line_frequency,
self.x_radius * LEFT,
self.x_radius * RIGHT,
UP,
),
]
for radius, freq, start, end, unit in tuples:
main_range = np.arange(0, radius, freq)
step = freq / float(freq + self.secondary_line_ratio)
for v in np.arange(0, radius, step):
line1 = Line(start + v * unit, end + v * unit)
line2 = Line(start - v * unit, end - v * unit)
if v == 0:
self.axes.add(line1)
elif v in main_range:
self.main_lines.add(line1, line2)
else:
self.secondary_lines.add(line1, line2)
self.add(self.secondary_lines, self.main_lines, self.axes)
self.stretch(self.x_unit_size, 0)
self.stretch(self.y_unit_size, 1)
self.shift(self.center_point)
# Put x_axis before y_axis
y_axis, x_axis = self.axes.split()
self.axes = VMobject(x_axis, y_axis)
def __init__(self, **kwargs):
digest_config(self, kwargs)
kwargs["number_line_config"] = self.axis_config
Axes.__init__(self, **kwargs)
self.init_background_lines()
def init_colors(self):
VMobject.init_colors(self)
self.axes.set_stroke(self.axes_color, self.stroke_width)
self.main_lines.set_stroke(self.color, self.stroke_width)
self.secondary_lines.set_stroke(
self.secondary_color, self.secondary_stroke_width
def init_background_lines(self):
if self.faded_line_style is None:
background_line_style = self.background_line_style
color = background_line_style.get(
"stroke_color", WHITE
)
return self
stroke_width = background_line_style.get("stroke_width", 2) / 2
self.faded_line_style = {
"stroke_color": color,
"stroke_width": stroke_width,
"stroke_opacity": 0.5,
}
self.background_lines, self.faded_lines = self.get_lines()
self.background_lines.set_style(
**self.background_line_style,
)
self.faded_lines.set_style(
**self.faded_line_style,
)
self.add_to_back(
self.faded_lines,
self.background_lines,
)
def get_lines(self):
x_axis = self.get_x_axis()
y_axis = self.get_y_axis()
x_freq = self.x_line_frequency
y_freq = self.y_line_frequency
x_lines1, x_lines2 = self.get_lines_parallel_to_axis(
x_axis, y_axis, x_freq,
self.faded_line_ratio,
)
y_lines1, y_lines2 = self.get_lines_parallel_to_axis(
y_axis, x_axis, y_freq,
self.faded_line_ratio,
)
lines1 = VGroup(*x_lines1, *y_lines1)
lines2 = VGroup(*x_lines2, *y_lines2)
return lines1, lines2
def get_lines_parallel_to_axis(self, axis1, axis2, freq, ratio):
line = Line(axis1.get_start(), axis1.get_end())
vect = line.get_vector()
dense_freq = (1 + ratio)
step = 1 / dense_freq
lines1 = VGroup()
lines2 = VGroup()
ranges = (
np.arange(0, axis2.x_max, step),
np.arange(0, axis2.x_min, -step),
)
for inputs in ranges:
for k, x in enumerate(inputs):
new_line = line.copy()
new_line.move_to(axis2.number_to_point(x))
new_line.align_to(line, vect)
if k % (1 + ratio) == 0:
lines1.add(new_line)
else:
lines2.add(new_line)
return lines1, lines2
def get_center_point(self):
return self.coords_to_point(0, 0)
def coords_to_point(self, x, y):
x, y = np.array([x, y])
result = self.axes.get_center()
result += x * self.get_x_unit_size() * RIGHT
result += y * self.get_y_unit_size() * UP
return result
def point_to_coords(self, point):
new_point = point - self.axes.get_center()
x = new_point[0] / self.get_x_unit_size()
y = new_point[1] / self.get_y_unit_size()
return x, y
# Does not recompute center, unit_sizes for each call; useful for
# iterating over large lists of points, but does assume these
# attributes are kept accurate. (Could alternatively have a method
# which returns a function dynamically created after a single
# call to each of get_center(), get_x_unit_size(), etc.)
def point_to_coords_cheap(self, point):
new_point = point - self.center_point
x = new_point[0] / self.x_unit_size
y = new_point[1] / self.y_unit_size
return x, y
def get_x_unit_size(self):
return self.axes.get_width() / (2.0 * self.x_radius)
return self.get_x_axis().get_unit_size()
def get_y_unit_size(self):
return self.axes.get_height() / (2.0 * self.y_radius)
return self.get_x_axis().get_unit_size()
def get_coordinate_labels(self, x_vals=None, y_vals=None):
coordinate_labels = VGroup()
if x_vals is None:
x_vals = list(range(-int(self.x_radius), int(self.x_radius) + 1))
if y_vals is None:
y_vals = list(range(-int(self.y_radius), int(self.y_radius) + 1))
for index, vals in enumerate([x_vals, y_vals]):
num_pair = [0, 0]
for val in vals:
if val == 0:
continue
num_pair[index] = val
point = self.coords_to_point(*num_pair)
num = TexMobject(str(val))
num.add_background_rectangle()
num.set_height(
self.written_coordinate_height
)
num.next_to(point, DOWN + LEFT, buff=SMALL_BUFF)
coordinate_labels.add(num)
self.coordinate_labels = coordinate_labels
return coordinate_labels
x_vals = x_vals or []
y_vals = y_vals or []
x_mobs = self.get_x_axis().get_number_mobjects(*x_vals)
y_mobs = self.get_y_axis().get_number_mobjects(*y_vals)
self.coordinate_labels = VGroup(x_mobs, y_mobs)
return self.coordinate_labels
def get_axes(self):
return self.axes
def get_axis_labels(self, x_label="x", y_label="y"):
x_axis, y_axis = self.get_axes().split()
quads = [
(x_axis, x_label, UP, RIGHT),
(y_axis, y_label, RIGHT, UP),
]
labels = VGroup()
for axis, tex, vect, edge in quads:
label = TexMobject(tex)
label.add_background_rectangle()
label.next_to(axis, vect)
label.to_edge(edge)
labels.add(label)
self.axis_labels = labels
return labels
def get_x_axis_label(self, label_tex, edge=RIGHT, direction=DL, **kwargs):
return self.get_axis_label(
label_tex, self.get_x_axis(),
edge, direction, **kwargs
)
def get_y_axis_label(self, label_tex, edge=UP, direction=DR, **kwargs):
return self.get_axis_label(
label_tex, self.get_y_axis(),
edge, direction, **kwargs
)
def get_axis_label(self, label_tex, axis, edge, direction, buff=MED_SMALL_BUFF):
label = TexMobject(label_tex)
label.next_to(
axis.get_edge_center(edge), direction,
buff=buff
)
return label
def get_axis_labels(self, x_label_tex="x", y_label_tex="y"):
self.axis_labels = VGroup(
self.get_x_axis_label(x_label_tex),
self.get_y_axis_label(y_label_tex),
)
return self.axis_labels
def add_coordinates(self, x_vals=None, y_vals=None):
self.add(*self.get_coordinate_labels(x_vals, y_vals))
self.add(self.get_coordinate_labels(x_vals, y_vals))
return self
def get_vector(self, coords, **kwargs):
point = coords[0] * RIGHT + coords[1] * UP
arrow = Arrow(ORIGIN, point, **kwargs)
return arrow
kwargs["buff"] = 0
return Arrow(
self.coords_to_point(0, 0),
self.coords_to_point(*coords),
**kwargs
)
def prepare_for_nonlinear_transform(self, num_inserted_curves=50):
for mob in self.family_members_with_points():
num_curves = mob.get_num_curves()
if num_inserted_curves > num_curves:
mob.insert_n_curves(
num_inserted_curves - num_curves)
mob.make_smooth()
num_inserted_curves - num_curves
)
return self
class ComplexPlane(NumberPlane):
CONFIG = {
"color": BLUE,
"unit_size": 1,
"line_frequency": 1,
"faded_line_frequency": 0.5,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
kwargs.update({
"x_unit_size": self.unit_size,
"y_unit_size": self.unit_size,
"x_line_frequency": self.line_frequency,
"x_faded_line_frequency": self.faded_line_frequency,
"y_line_frequency": self.line_frequency,
"y_faded_line_frequency": self.faded_line_frequency,
})
NumberPlane.__init__(self, **kwargs)
def number_to_point(self, number):
number = complex(number)
return self.coords_to_point(number.real, number.imag)
@ -358,35 +380,35 @@ class ComplexPlane(NumberPlane):
x, y = self.point_to_coords(point)
return complex(x, y)
def get_coordinate_labels(self, *numbers):
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
result = VGroup()
if len(numbers) == 0:
numbers = list(range(-int(self.x_radius), int(self.x_radius) + 1))
numbers += [
complex(0, y)
for y in range(-int(self.y_radius), int(self.y_radius) + 1)
if y != 0
def get_default_coordinate_values(self):
x_numbers = self.get_x_axis().default_numbers_to_display()
y_numbers = self.get_y_axis().default_numbers_to_display()
y_numbers = [
complex(0, y) for y in y_numbers if y != 0
]
return [*x_numbers, *y_numbers]
def get_coordinate_labels(self, *numbers, **kwargs):
if len(numbers) == 0:
numbers = self.get_default_coordinate_values()
self.coordinate_labels = VGroup()
for number in numbers:
# if number == complex(0, 0):
# continue
point = self.number_to_point(number)
num_str = str(number).replace("j", "i")
if num_str.startswith("0"):
num_str = "0"
elif num_str in ["1i", "-1i"]:
num_str = num_str.replace("1", "")
num_mob = TexMobject(num_str)
num_mob.add_background_rectangle()
num_mob.set_height(self.written_coordinate_height)
num_mob.next_to(point, DOWN + LEFT, SMALL_BUFF)
result.add(num_mob)
self.coordinate_labels = result
return result
z = complex(number)
if abs(z.imag) > abs(z.real):
axis = self.get_y_axis()
value = z.imag
kwargs = merge_config([
{"number_config": {"unit": "i"}},
kwargs,
])
else:
axis = self.get_x_axis()
value = z.real
number_mob = axis.get_number_mobject(value, **kwargs)
self.coordinate_labels.add(number_mob)
return self.coordinate_labels
def add_coordinates(self, *numbers):
self.coordinate_labels = self.get_coordinate_labels(*numbers)
self.add(self.coordinate_labels)
self.add(self.get_coordinate_labels(*numbers))
return self

View file

@ -36,11 +36,9 @@ class Mobject(Container):
"target": None,
}
def __init__(self, *submobjects, **kwargs):
Container.__init__(self, *submobjects, **kwargs)
if not all([isinstance(m, Mobject) for m in submobjects]):
raise Exception("All submobjects must be of type Mobject")
self.submobjects = list(submobjects)
def __init__(self, **kwargs):
Container.__init__(self, **kwargs)
self.submobjects = []
self.color = Color(self.color)
if self.name is None:
self.name = self.__class__.__name__
@ -1031,7 +1029,8 @@ class Mobject(Container):
class Group(Mobject):
# Alternate name to improve readibility in cases where
# the mobject is used primarily for its submobject housing
# functionality.
pass
def __init__(self, *mobjects, **kwargs):
if not all([isinstance(m, Mobject) for m in mobjects]):
raise Exception("All submobjects must be of type Mobject")
Mobject.__init__(self, **kwargs)
self.add(*mobjects)

View file

@ -1,25 +1,30 @@
import operator as op
from manimlib.constants import *
from manimlib.mobject.geometry import Arrow
from manimlib.mobject.geometry import RegularPolygon
from manimlib.mobject.geometry import Line
from manimlib.mobject.numbers import DecimalNumber
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import merge_config
from manimlib.utils.simple_functions import fdiv
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import normalize
class NumberLine(VMobject):
class NumberLine(Line):
CONFIG = {
"color": LIGHT_GREY,
"x_min": -FRAME_X_RADIUS,
"x_max": FRAME_X_RADIUS,
"unit_size": 1,
"include_ticks": True,
"tick_size": 0.1,
"tick_frequency": 1,
"leftmost_tick": None, # Defaults to value near x_min s.t. 0 is a tick
# Defaults to value near x_min s.t. 0 is a tick
# TODO, rename this
"leftmost_tick": None,
# Change name
"numbers_with_elongated_ticks": [0],
"include_numbers": False,
"numbers_to_show": None,
@ -29,6 +34,8 @@ class NumberLine(VMobject):
"label_direction": DOWN,
"line_to_number_buff": MED_SMALL_BUFF,
"include_tip": False,
"tip_width": 0.25,
"tip_height": 0.25,
"decimal_number_config": {
"num_decimal_places": 0,
}
@ -36,68 +43,72 @@ class NumberLine(VMobject):
def __init__(self, **kwargs):
digest_config(self, kwargs)
if self.leftmost_tick is None:
tf = self.tick_frequency
self.leftmost_tick = tf * np.ceil(self.x_min / tf)
VMobject.__init__(self, **kwargs)
start = self.unit_size * self.x_min * RIGHT
end = self.unit_size * self.x_max * RIGHT
Line.__init__(self, start, end, **kwargs)
self.shift(-self.number_to_point(self.number_at_center))
self.init_leftmost_tick()
if self.include_ticks:
self.add_tick_marks()
if self.include_tip:
self.add_tip()
if self.include_numbers:
self.add_numbers()
def generate_points(self):
self.main_line = Line(self.x_min * RIGHT, self.x_max * RIGHT)
self.tick_marks = VGroup()
self.add(self.main_line, self.tick_marks)
rounding_value = int(-np.log10(0.1 * self.tick_frequency))
rounded_numbers_with_elongated_ticks = np.round(
self.numbers_with_elongated_ticks,
rounding_value
def init_leftmost_tick(self):
if self.leftmost_tick is None:
self.leftmost_tick = op.mul(
self.tick_frequency,
np.ceil(self.x_min / self.tick_frequency)
)
for x in self.get_tick_numbers():
rounded_x = np.round(x, rounding_value)
if rounded_x in rounded_numbers_with_elongated_ticks:
tick_size_used = self.longer_tick_multiple * self.tick_size
else:
tick_size_used = self.tick_size
self.add_tick(x, tick_size_used)
self.stretch(self.unit_size, 0)
self.shift(-self.number_to_point(self.number_at_center))
def add_tick(self, x, size=None):
self.tick_marks.add(self.get_tick(x, size))
return self
def add_tick_marks(self):
tick_size = self.tick_size
self.tick_marks = VGroup(*[
self.get_tick(x, tick_size)
for x in self.get_tick_numbers()
])
big_tick_size = tick_size * self.longer_tick_multiple
self.big_tick_marks = VGroup(*[
self.get_tick(x, big_tick_size)
for x in self.numbers_with_elongated_ticks
])
self.add(
self.tick_marks,
self.big_tick_marks,
)
def get_tick(self, x, size=None):
if size is None:
size = self.tick_size
result = Line(size * DOWN, size * UP)
result.rotate(self.main_line.get_angle())
result.rotate(self.get_angle())
result.move_to(self.number_to_point(x))
result.match_style(self)
return result
def get_tick_marks(self):
return self.tick_marks
return VGroup(
*self.tick_marks,
*self.big_tick_marks,
)
def get_tick_numbers(self):
epsilon = 0.001
return np.arange(
self.leftmost_tick, self.x_max + epsilon,
self.leftmost_tick,
self.x_max + self.tick_frequency / 2,
self.tick_frequency
)
def number_to_point(self, number):
alpha = float(number - self.x_min) / (self.x_max - self.x_min)
return interpolate(
self.main_line.get_start(),
self.main_line.get_end(),
alpha
self.get_start(), self.get_end(), alpha
)
def point_to_number(self, point):
start_point, end_point = self.main_line.get_start_and_end()
start_point, end_point = self.get_start_and_end()
full_vect = end_point - start_point
unit_vect = normalize(full_vect)
@ -110,28 +121,43 @@ class NumberLine(VMobject):
)
return interpolate(self.x_min, self.x_max, proportion)
def get_unit_size(self):
return (self.x_max - self.x_min) / self.get_length()
def default_numbers_to_display(self):
if self.numbers_to_show is not None:
return self.numbers_to_show
return np.arange(int(self.leftmost_tick), int(self.x_max) + 1)
def get_number_mobject(self, number,
number_config=None,
scale_val=None,
direction=None,
buff=None):
number_config = merge_config([
number_config or {},
self.decimal_number_config
])
scale_val = scale_val or self.number_scale_val
direction = direction or self.label_direction
buff = buff or self.line_to_number_buff
num_mob = DecimalNumber(number, **number_config)
num_mob.scale(scale_val)
num_mob.next_to(
self.number_to_point(number),
direction=direction,
buff=buff
)
return num_mob
def get_number_mobjects(self, *numbers, **kwargs):
# TODO, handle decimals
if len(numbers) == 0:
numbers = self.default_numbers_to_display()
result = VGroup()
for number in numbers:
mob = DecimalNumber(
number, **self.decimal_number_config
)
mob.scale(self.number_scale_val)
mob.next_to(
self.number_to_point(number),
self.label_direction,
self.line_to_number_buff,
)
result.add(mob)
return result
return VGroup(*[
self.get_number_mobject(number, **kwargs)
for number in numbers
])
def get_labels(self):
return self.get_number_mobjects()
@ -144,12 +170,13 @@ class NumberLine(VMobject):
return self
def add_tip(self):
start, end = self.main_line.get_start_and_end()
vect = (end - start) / get_norm(end - start)
arrow = Arrow(start, end + MED_SMALL_BUFF * vect, buff=0)
tip = arrow.tip
tip.set_stroke(width=self.get_stroke_width())
tip.set_color(self.color)
tip = RegularPolygon(3)
color = self.color
tip.set_stroke(color, width=self.get_stroke_width())
tip.set_fill(color, opacity=1)
tip.set_width(self.tip_width)
tip.set_height(self.tip_height, stretch=True)
tip.move_to(self.get_end(), LEFT)
self.tip = tip
self.add(tip)

View file

@ -26,7 +26,7 @@ class DecimalNumber(VMobject):
formatter = self.get_formatter()
num_string = formatter.format(number)
rounded_num = np.round(float(number), self.num_decimal_places)
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:]

View file

@ -11,7 +11,6 @@ from manimlib.mobject.geometry import Rectangle
from manimlib.mobject.geometry import RoundedRectangle
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.bezier import is_closed
from manimlib.utils.color import *
from manimlib.utils.config_ops import digest_config
from manimlib.utils.config_ops import digest_locals
@ -105,14 +104,14 @@ class SVGMobject(VMobject):
pass # TODO
# warnings.warn("Unknown element type: " + element.tagName)
result = [m for m in result if m is not None]
self.handle_transforms(element, VMobject(*result))
self.handle_transforms(element, VGroup(*result))
if len(result) > 1 and not self.unpack_groups:
result = [VGroup(*result)]
return result
def g_to_mobjects(self, g_element):
mob = VMobject(*self.get_mobjects_from(g_element))
mob = VGroup(*self.get_mobjects_from(g_element))
self.handle_transforms(g_element, mob)
return mob.submobjects
@ -124,7 +123,7 @@ class SVGMobject(VMobject):
ref = use_element.getAttribute("xlink:href")[1:]
if ref not in self.ref_to_element:
warnings.warn("%s not recognized" % ref)
return VMobject()
return VGroup()
return self.get_mobjects_from(
self.ref_to_element[ref]
)

View file

@ -796,18 +796,11 @@ class VMobject(Mobject):
class VGroup(VMobject):
def __init__(self, *args, **kwargs):
if len(args) == 1 and isinstance(args[0], (tuple, list)):
args = args[0]
packed_args = []
for arg in args:
if isinstance(arg, (tuple, list)):
packed_args.append(VGroup(arg))
else:
packed_args.append(arg)
VMobject.__init__(self, *packed_args, **kwargs)
def __init__(self, *vmobjects, **kwargs):
if not all([isinstance(m, VMobject) for m in vmobjects]):
raise Exception("All submobjects must be of type VMobject")
VMobject.__init__(self, **kwargs)
self.add(*vmobjects)
class VectorizedPoint(VMobject):

View file

@ -96,7 +96,7 @@ class ComplexTransformationScene(Scene):
# TODO...
def paint_plane(self, plane):
for lines in plane.main_lines, plane.secondary_lines:
for lines in planes, plane.secondary_lines:
lines.set_color_by_gradient(
self.vert_start_color,
self.vert_end_color,

View file

@ -156,10 +156,10 @@ class SpecialThreeDScene(ThreeDScene):
axes = ThreeDAxes(**self.three_d_axes_config)
for axis in axes:
if self.cut_axes_at_radius:
p0 = axis.main_line.get_start()
p0 = axis.get_start()
p1 = axis.number_to_point(-1)
p2 = axis.number_to_point(1)
p3 = axis.main_line.get_end()
p3 = axis.get_end()
new_pieces = VGroup(
Line(p0, p1), Line(p1, p2), Line(p2, p3),
)

View file

@ -56,12 +56,16 @@ def digest_config(obj, kwargs, caller_locals={}):
obj.__dict__ = merge_config(all_dicts)
# TODO, priority here is backwards from dict.update.
# Should I change the convention?
def merge_config(all_dicts):
"""
Creates a dict whose keyset is the union of all the
input dictionaries. The value for each key is based
on the first dict in the list with that key.
First dicts have higher priority
When values are dictionaries, it is applied recursively
"""
all_config = reduce(op.add, [list(d.items()) for d in all_dicts])

View file

@ -62,3 +62,30 @@ def fdiv(a, b, zero_over_zero_value=None):
where = True
return np.true_divide(a, b, out=out, where=where)
def binary_search(function,
target,
lower_bound,
upper_bound,
tolerance=1e-4):
lh = lower_bound
rh = upper_bound
while abs(rh - lh) > tolerance:
mh = np.mean([lh, rh])
lx, mx, rx = [function(h) for h in (lh, mh, rh)]
if lx == target:
return lx
if rx == target:
return rx
if lx <= target and rx >= target:
if mx > target:
rh = mh
else:
lh = mh
elif lx > target and rx < target:
lh, rh = rh, lh
else:
return None
return mh

View file

@ -781,7 +781,7 @@ class InputOutputScene(Scene):
plane.add_coordinates(x_vals = list(range(-2, 3)), y_vals = list(range(-2, 3)))
plane.white_parts = VGroup(plane.axes, plane.coordinate_labels)
plane.coordinate_labels.set_background_stroke(width=0)
plane.lines_to_fade = VGroup(plane.main_lines, plane.secondary_lines)
plane.lines_to_fade = VGroup(planes, plane.secondary_lines)
plane.move_to(vect*FRAME_X_RADIUS/2 + self.y_shift*DOWN)
label = TextMobject(text)
label.scale(1.5)

View file

@ -76,7 +76,7 @@ class NumberlineTransformationScene(ZoomedScene):
full_config = dict(self.number_line_config)
full_config.update(added_config)
number_line = NumberLine(**full_config)
number_line.main_line.insert_n_curves(
number_line.insert_n_curves(
self.num_inserted_number_line_curves
)
number_line.shift(zero_point - number_line.number_to_point(0))
@ -179,12 +179,12 @@ class NumberlineTransformationScene(ZoomedScene):
self.moving_input_line = input_line_copy
input_line_copy.remove(input_line_copy.numbers)
# input_line_copy.set_stroke(width=2)
input_line_copy.main_line.insert_n_curves(
input_line_copy.insert_n_curves(
self.num_inserted_number_line_curves
)
return AnimationGroup(
self.get_mapping_animation(
func, input_line_copy.main_line,
func, input_line_copy,
apply_function_to_points
),
self.get_mapping_animation(
@ -242,7 +242,7 @@ class NumberlineTransformationScene(ZoomedScene):
zoom_anim.update(1)
target_mini_line = Line(frame.get_left(), frame.get_right())
target_mini_line.scale(self.mini_line_scale_factor)
target_mini_line.match_style(self.output_line.main_line)
target_mini_line.match_style(self.output_line)
zoom_anim.update(0)
zcbr_group.submobjects.insert(1, target_mini_line)
if target_coordinate_values:
@ -312,7 +312,7 @@ class NumberlineTransformationScene(ZoomedScene):
mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
mini_line.scale(self.mini_line_scale_factor)
mini_line.insert_n_curves(self.num_inserted_number_line_curves)
mini_line.match_style(self.input_line.main_line)
mini_line.match_style(self.input_line)
mini_line_copy = mini_line.copy()
zcbr_group.add(mini_line_copy, mini_line)
anims += [FadeIn(mini_line), FadeIn(mini_line_copy)]
@ -2935,7 +2935,7 @@ class AnalyzeFunctionWithTransformations(NumberlineTransformationScene):
def setup_number_lines(self):
NumberlineTransformationScene.setup_number_lines(self)
for line in self.input_line, self.output_line:
VGroup(line.main_line, line.tick_marks).set_stroke(width=2)
VGroup(line, line.tick_marks).set_stroke(width=2)
def add_function_title(self):
title = TexMobject("f(x)", "=", "1 +", "\\frac{1}{x}")

View file

@ -3883,7 +3883,7 @@ class ThinkBackToHowAmazingThisIs(ThreeDScene):
self.number_line = number_line
def show_giant_circle(self):
self.number_line.main_line.insert_n_curves(10000)
self.number_line.insert_n_curves(10000)
everything = VGroup(*self.mobjects)
circle = everything.copy()
circle.move_to(ORIGIN)

View file

@ -3223,7 +3223,7 @@ class PhaseSpaceOfPopulationModel(ShowTwoPopulations, PiCreatureScene, MovingCam
])
for axis, label, vect in zip(axes, axes_labels, [RIGHT, UP]):
label.next_to(
axis.main_line, vect,
axis, vect,
submobject_to_align=label[0]
)
@ -4297,7 +4297,7 @@ class ZToHalfFlowNearWall(ComplexTransformationScene, MovingCameraScene):
secondary_line_ratio=0,
)
plane.next_to(ORIGIN, UP, buff=0.001)
horizontal_lines = VGroup(*[l for l in list(plane.main_lines) + [plane.axes[0]] if np.abs(l.get_center()[0]) < 0.1])
horizontal_lines = VGroup(*[l for l in list(planes) + [plane.axes[0]] if np.abs(l.get_center()[0]) < 0.1])
plane.set_stroke(MAROON_B, width=2)
horizontal_lines.set_stroke(BLUE, width=2)

View file

@ -65,7 +65,7 @@ class SlopeOfCircleExample(ZoomedScene):
def setup_plane(self):
self.plane = NumberPlane(**self.plane_kwargs)
self.plane.main_lines.fade()
self.planes.fade()
self.plane.add(self.plane.get_axis_labels())
self.plane.add_coordinates()

View file

@ -652,7 +652,7 @@ class ConstantVelocityPlot(PlotVelocity):
def note_units(self):
x_line, y_line = lines = VGroup(*[
axis.main_line.copy()
axis.copy()
for axis in (self.x_axis, self.y_axis)
])
lines.set_color(TIME_COLOR)

View file

@ -2221,7 +2221,7 @@ class ChangeToEigenBasis(ExampleTranformationScene):
self.play(FadeOut(self.plane))
cob_transform = self.get_matrix_transformation([[1, 0], [-1, 1]])
ApplyMethod(self.plane.apply_function, cob_transform).update(1)
self.plane.main_lines.set_color(BLUE_D)
self.planes.set_color(BLUE_D)
self.plane.axes.set_color(WHITE)
self.play(
FadeIn(self.plane),

View file

@ -2047,8 +2047,8 @@ class ShowCommutativeDiagram(ShowLinearity):
VGroup(ta_group[1], fa_group[1]).shift(MED_LARGE_BUFF*UP)
for ta, fa in zip(ta_group, fa_group):
fa.next_to(
ta.x_axis.main_line, RIGHT,
submobject_to_align = fa.x_axis.main_line
ta.x_axis, RIGHT,
submobject_to_align = fa.x_axis
)
fa.to_edge(RIGHT)
ta.remove(ta.labels)
@ -2084,7 +2084,7 @@ class ShowCommutativeDiagram(ShowLinearity):
fourier_graph.set_color(self.center_of_mass_color)
arrow = Arrow(
ta.x_axis.main_line, fa.x_axis.main_line,
ta.x_axis, fa.x_axis,
color = WHITE,
buff = MED_LARGE_BUFF,
)
@ -3712,7 +3712,7 @@ class SummarizeTheFullTransform(DrawFrequencyPlot):
)
imaginary_fourier_graph.set_color(BLUE)
imaginary_fourier_graph.shift(
frequency_axes.x_axis.main_line.get_right() - \
frequency_axes.x_axis.get_right() - \
imaginary_fourier_graph.points[-1],
)

View file

@ -46,7 +46,7 @@ class Slider(NumberLine):
def add_label(self, tex):
label = TexMobject(tex)
label.scale(self.label_scale_val)
label.move_to(self.main_line.get_top())
label.move_to(self.get_top())
label.shift(MED_LARGE_BUFF*UP)
self.add(label)
self.label = label

View file

@ -674,7 +674,7 @@ class QuarterTurn(Scene):
class UsingTheta(Scene):
def construct(self):
plane = NumberPlane(x_unit_size = 3, y_unit_size = 3)
# plane.main_lines.fade(0.5)
# planes.fade(0.5)
# plane.secondary_lines.fade(0.5)
plane.fade(0.5)
self.add(plane)

View file

@ -1245,7 +1245,7 @@ class IntroduceLinusTheLinelander(Scene):
algebra.shift(3 * RIGHT)
self.play(
ShowCreation(number_line.main_line),
ShowCreation(number_line),
linus.look_at, number_line
)
self.play(
@ -3123,9 +3123,9 @@ class IntroduceThreeDNumbers(SpecialThreeDScene):
z_axis.set_color(WHITE)
z_axis_top = Line(
z_axis.number_to_point(0),
z_axis.main_line.get_end(),
z_axis.get_end(),
)
z_axis_top.match_style(z_axis.main_line)
z_axis_top.match_style(z_axis)
z_unit_line = Line(
z_axis.number_to_point(0),
@ -3155,7 +3155,7 @@ class IntroduceThreeDNumbers(SpecialThreeDScene):
colored_coord_lines = VGroup(colored_, y_line, z_line)
coord_lines = VGroup(
plane.axes[0], plane.axes[1], z_axis.main_line,
plane.axes[0], plane.axes[1], z_axis,
)
for i1, i2 in [(0, 2), (1, 0), (2, 1)]:
coord_lines[i1].target = coord_lines[i2].copy()

View file

@ -1700,7 +1700,7 @@ class VisualizeZSquared(Scene):
color_grid = self.get_color_grid()
self.play(
self.background_plane.main_lines.set_stroke, None, 1,
self.background_planes.set_stroke, None, 1,
LaggedStart(
FadeIn, color_grid,
run_time = 2
@ -1807,7 +1807,7 @@ class VisualizeZSquared(Scene):
secondary_line_ratio = 0,
stroke_width = 2,
)
color_grid.main_lines.set_color_by_gradient(
color_grids.set_color_by_gradient(
*[GREEN, RED, MAROON_B, TEAL]*2
)
color_grid.remove(color_grid.axes[0])

View file

@ -877,7 +877,7 @@ class TwoCarsAtRedLight(Scene):
))
self.play(
ApplyMethod(
self.time_axes.x_axis.main_line.stretch, 2.5, 0,
self.time_axes.x_axis.stretch, 2.5, 0,
{"about_edge" : LEFT},
run_time = 4,
rate_func = squish_rate_func(smooth, 0.3, 0.6),
@ -885,7 +885,7 @@ class TwoCarsAtRedLight(Scene):
UpdateFromFunc(
self.time_axes.x_axis.tip,
lambda m : m.move_to(
self.time_axes.x_axis.main_line.get_right(),
self.time_axes.x_axis.get_right(),
LEFT
)
),
@ -1364,7 +1364,7 @@ class CenterOfMassDescription(FourierRecapScene):
circle_plane.target.set_height(FRAME_HEIGHT)
circle_plane.target.center()
circle_plane.target.axes.set_stroke(width = 2)
circle_plane.target.main_lines.set_stroke(width = 2)
circle_plane.targets.set_stroke(width = 2)
circle_plane.target.secondary_lines.set_stroke(width = 1)
start_coords = (0.5, 0.5)
@ -2940,7 +2940,7 @@ class IntroduceDeBroglie(Scene):
self.wait()
#Transform time_line
line = time_line.main_line
line = time_line
self.play(
FadeOut(time_line.numbers),
VGroup(arrow, words, date).shift, MED_LARGE_BUFF*UP,