mirror of
https://github.com/3b1b/manim.git
synced 2025-07-27 12:03:03 +08:00

* Added DocStrings for methods in Container and Scene. Removed 2 unused imports from scene.py. * Added DocStrings for all methods in GraphScene, MovingCameraScene, SceneFileWriter, ThreeDScene, SpecialThreeDScene and ZoomedScene. * Added DocStrings for all methods in `VectorScene` and LinearTransformationScene... ...except `position_x_coordinate` and `position_y_coordinate` Co-authored-by: Aathish Sivasubrahmanian <aathishs@Aathishs-MacBook-Air.local>
1055 lines
34 KiB
Python
1055 lines
34 KiB
Python
import itertools as it
|
|
|
|
from manimlib.animation.creation import Write, DrawBorderThenFill, ShowCreation
|
|
from manimlib.animation.transform import Transform
|
|
from manimlib.animation.update import UpdateFromAlphaFunc
|
|
from manimlib.constants import *
|
|
from manimlib.mobject.functions import ParametricFunction
|
|
from manimlib.mobject.geometry import Line
|
|
from manimlib.mobject.geometry import Rectangle
|
|
from manimlib.mobject.geometry import RegularPolygon
|
|
from manimlib.mobject.number_line import NumberLine
|
|
from manimlib.mobject.svg.tex_mobject import TexMobject
|
|
from manimlib.mobject.svg.tex_mobject import TextMobject
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
|
|
from manimlib.scene.scene import Scene
|
|
from manimlib.utils.bezier import interpolate
|
|
from manimlib.utils.color import color_gradient
|
|
from manimlib.utils.color import invert_color
|
|
from manimlib.utils.space_ops import angle_of_vector
|
|
|
|
# TODO, this should probably reimplemented entirely, especially so as to
|
|
# better reuse code from mobject/coordinate_systems.
|
|
# Also, I really dislike how the configuration is set up, this
|
|
# is way too messy to work with.
|
|
|
|
|
|
class GraphScene(Scene):
|
|
CONFIG = {
|
|
"x_min": -1,
|
|
"x_max": 10,
|
|
"x_axis_width": 9,
|
|
"x_tick_frequency": 1,
|
|
"x_leftmost_tick": None, # Change if different from x_min
|
|
"x_labeled_nums": None,
|
|
"x_axis_label": "$x$",
|
|
"y_min": -1,
|
|
"y_max": 10,
|
|
"y_axis_height": 6,
|
|
"y_tick_frequency": 1,
|
|
"y_bottom_tick": None, # Change if different from y_min
|
|
"y_labeled_nums": None,
|
|
"y_axis_label": "$y$",
|
|
"axes_color": GREY,
|
|
"graph_origin": 2.5 * DOWN + 4 * LEFT,
|
|
"exclude_zero_label": True,
|
|
"default_graph_colors": [BLUE, GREEN, YELLOW],
|
|
"default_derivative_color": GREEN,
|
|
"default_input_color": YELLOW,
|
|
"default_riemann_start_color": BLUE,
|
|
"default_riemann_end_color": GREEN,
|
|
"area_opacity": 0.8,
|
|
"num_rects": 50,
|
|
}
|
|
|
|
def setup(self):
|
|
"""
|
|
This method is used internally by Manim
|
|
to set up the scene for proper use.
|
|
"""
|
|
self.default_graph_colors_cycle = it.cycle(self.default_graph_colors)
|
|
|
|
self.left_T_label = VGroup()
|
|
self.left_v_line = VGroup()
|
|
self.right_T_label = VGroup()
|
|
self.right_v_line = VGroup()
|
|
|
|
def setup_axes(self, animate=False):
|
|
"""
|
|
This method sets up the axes of the graph.
|
|
|
|
Parameters
|
|
----------
|
|
animate (bool=False)
|
|
Whether or not to animate the setting up of the Axes.
|
|
"""
|
|
# TODO, once eoc is done, refactor this to be less redundant.
|
|
x_num_range = float(self.x_max - self.x_min)
|
|
self.space_unit_to_x = self.x_axis_width / x_num_range
|
|
if self.x_labeled_nums is None:
|
|
self.x_labeled_nums = []
|
|
if self.x_leftmost_tick is None:
|
|
self.x_leftmost_tick = self.x_min
|
|
x_axis = NumberLine(
|
|
x_min=self.x_min,
|
|
x_max=self.x_max,
|
|
unit_size=self.space_unit_to_x,
|
|
tick_frequency=self.x_tick_frequency,
|
|
leftmost_tick=self.x_leftmost_tick,
|
|
numbers_with_elongated_ticks=self.x_labeled_nums,
|
|
color=self.axes_color
|
|
)
|
|
x_axis.shift(self.graph_origin - x_axis.number_to_point(0))
|
|
if len(self.x_labeled_nums) > 0:
|
|
if self.exclude_zero_label:
|
|
self.x_labeled_nums = [x for x in self.x_labeled_nums if x != 0]
|
|
x_axis.add_numbers(*self.x_labeled_nums)
|
|
if self.x_axis_label:
|
|
x_label = TextMobject(self.x_axis_label)
|
|
x_label.next_to(
|
|
x_axis.get_tick_marks(), UP + RIGHT,
|
|
buff=SMALL_BUFF
|
|
)
|
|
x_label.shift_onto_screen()
|
|
x_axis.add(x_label)
|
|
self.x_axis_label_mob = x_label
|
|
|
|
y_num_range = float(self.y_max - self.y_min)
|
|
self.space_unit_to_y = self.y_axis_height / y_num_range
|
|
|
|
if self.y_labeled_nums is None:
|
|
self.y_labeled_nums = []
|
|
if self.y_bottom_tick is None:
|
|
self.y_bottom_tick = self.y_min
|
|
y_axis = NumberLine(
|
|
x_min=self.y_min,
|
|
x_max=self.y_max,
|
|
unit_size=self.space_unit_to_y,
|
|
tick_frequency=self.y_tick_frequency,
|
|
leftmost_tick=self.y_bottom_tick,
|
|
numbers_with_elongated_ticks=self.y_labeled_nums,
|
|
color=self.axes_color,
|
|
line_to_number_vect=LEFT,
|
|
label_direction=LEFT,
|
|
)
|
|
y_axis.shift(self.graph_origin - y_axis.number_to_point(0))
|
|
y_axis.rotate(np.pi / 2, about_point=y_axis.number_to_point(0))
|
|
if len(self.y_labeled_nums) > 0:
|
|
if self.exclude_zero_label:
|
|
self.y_labeled_nums = [y for y in self.y_labeled_nums if y != 0]
|
|
y_axis.add_numbers(*self.y_labeled_nums)
|
|
if self.y_axis_label:
|
|
y_label = TextMobject(self.y_axis_label)
|
|
y_label.next_to(
|
|
y_axis.get_corner(UP + RIGHT), UP + RIGHT,
|
|
buff=SMALL_BUFF
|
|
)
|
|
y_label.shift_onto_screen()
|
|
y_axis.add(y_label)
|
|
self.y_axis_label_mob = y_label
|
|
|
|
if animate:
|
|
self.play(Write(VGroup(x_axis, y_axis)))
|
|
else:
|
|
self.add(x_axis, y_axis)
|
|
self.x_axis, self.y_axis = self.axes = VGroup(x_axis, y_axis)
|
|
self.default_graph_colors = it.cycle(self.default_graph_colors)
|
|
|
|
def coords_to_point(self, x, y):
|
|
"""
|
|
The graph is smaller than the scene.
|
|
Because of this, coordinates in the scene don't map
|
|
to coordinates on the graph.
|
|
This method returns a scaled coordinate for the graph,
|
|
given cartesian coordinates that correspond to the scene..
|
|
|
|
Parameters
|
|
----------
|
|
x : (int,float)
|
|
The x value
|
|
|
|
y : (int,float)
|
|
The y value
|
|
|
|
Returns
|
|
-------
|
|
np.ndarray
|
|
The array of the coordinates.
|
|
"""
|
|
assert(hasattr(self, "x_axis") and hasattr(self, "y_axis"))
|
|
result = self.x_axis.number_to_point(x)[0] * RIGHT
|
|
result += self.y_axis.number_to_point(y)[1] * UP
|
|
return result
|
|
|
|
def point_to_coords(self, point):
|
|
"""
|
|
The scene is smaller than the graph.
|
|
|
|
Because of this, coordinates in the graph don't map
|
|
to coordinates on the scene.
|
|
|
|
This method returns a scaled coordinate for the scene,
|
|
given coordinates that correspond to the graph.
|
|
|
|
Parameters
|
|
----------
|
|
point (np.ndarray)
|
|
The point on the graph.
|
|
|
|
Returns
|
|
-------
|
|
tuple
|
|
The coordinates on the scene.
|
|
"""
|
|
return (self.x_axis.point_to_number(point),
|
|
self.y_axis.point_to_number(point))
|
|
|
|
def get_graph(
|
|
self, func,
|
|
color=None,
|
|
x_min=None,
|
|
x_max=None,
|
|
**kwargs
|
|
):
|
|
"""
|
|
This method gets a curve to plot on the graph.
|
|
|
|
Parameters
|
|
----------
|
|
func : function
|
|
The function to plot. It's return value should be
|
|
the y-coordinate for a given x-coordinate
|
|
|
|
color : str
|
|
The string of the RGB color of the curve. in Hexadecimal representation.
|
|
|
|
x_min : (Union[int,float])
|
|
The lower x_value from which to plot the curve.
|
|
|
|
x_max : (Union[int,float])
|
|
The higher x_value until which to plot the curve.
|
|
|
|
**kwargs:
|
|
Any valid keyword arguments of ParametricFunction.
|
|
|
|
Return
|
|
------
|
|
ParametricFunction
|
|
The Parametric Curve for the function passed.
|
|
|
|
"""
|
|
if color is None:
|
|
color = next(self.default_graph_colors_cycle)
|
|
if x_min is None:
|
|
x_min = self.x_min
|
|
if x_max is None:
|
|
x_max = self.x_max
|
|
|
|
def parameterized_function(alpha):
|
|
x = interpolate(x_min, x_max, alpha)
|
|
y = func(x)
|
|
if not np.isfinite(y):
|
|
y = self.y_max
|
|
return self.coords_to_point(x, y)
|
|
|
|
graph = ParametricFunction(
|
|
parameterized_function,
|
|
color=color,
|
|
**kwargs
|
|
)
|
|
graph.underlying_function = func
|
|
return graph
|
|
|
|
def input_to_graph_point(self, x, graph):
|
|
"""
|
|
This method returns a coordinate on the curve
|
|
given an x_value and a the graoh-curve for which
|
|
the corresponding y value should be found.
|
|
|
|
Parameters
|
|
----------
|
|
x (Union[int, float])
|
|
The x value for which to find the y value.
|
|
|
|
graph ParametricFunction
|
|
The ParametricFunction object on which
|
|
the x and y value lie.
|
|
|
|
Returns
|
|
-------
|
|
numpy.nparray
|
|
The array of the coordinates on the graph.
|
|
"""
|
|
return self.coords_to_point(x, graph.underlying_function(x))
|
|
|
|
def angle_of_tangent(self, x, graph, dx=0.01):
|
|
"""
|
|
Returns the angle to the x axis of the tangent
|
|
to the plotted curve at a particular x-value.
|
|
|
|
Parameters
|
|
----------
|
|
x (Union[int, float])
|
|
The x value at which the tangent must touch the curve.
|
|
|
|
graph ParametricFunction
|
|
The ParametricFunction for which to calculate the tangent.
|
|
|
|
dx (Union(float, int =0.01))
|
|
The small change in x with which a small change in y
|
|
will be compared in order to obtain the tangent.
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
The angle of the tangent with the x axis.
|
|
"""
|
|
vect = self.input_to_graph_point(
|
|
x + dx, graph) - self.input_to_graph_point(x, graph)
|
|
return angle_of_vector(vect)
|
|
|
|
def slope_of_tangent(self, *args, **kwargs):
|
|
"""
|
|
Returns the slople of the tangent to the plotted curve
|
|
at a particular x-value.
|
|
|
|
Parameters
|
|
----------
|
|
x (Union[int, float])
|
|
The x value at which the tangent must touch the curve.
|
|
|
|
graph ParametricFunction
|
|
The ParametricFunction for which to calculate the tangent.
|
|
|
|
dx (Union(float, int =0.01))
|
|
The small change in x with which a small change in y
|
|
will be compared in order to obtain the tangent.
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
The slope of the tangent with the x axis.
|
|
"""
|
|
return np.tan(self.angle_of_tangent(*args, **kwargs))
|
|
|
|
def get_derivative_graph(self, graph, dx=0.01, **kwargs):
|
|
"""
|
|
Returns the curve of the derivative of the passed
|
|
graph.
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph for which the derivative must be found.
|
|
|
|
dx (Union(float, int =0.01))
|
|
The small change in x with which a small change in y
|
|
will be compared in order to obtain the derivative.
|
|
|
|
**kwargs
|
|
Any valid keyword argument of ParametricFunction
|
|
|
|
Returns
|
|
-------
|
|
ParametricFuncion
|
|
The curve of the derivative.
|
|
"""
|
|
if "color" not in kwargs:
|
|
kwargs["color"] = self.default_derivative_color
|
|
|
|
def deriv(x):
|
|
return self.slope_of_tangent(x, graph, dx) / self.space_unit_to_y
|
|
return self.get_graph(deriv, **kwargs)
|
|
|
|
def get_graph_label(
|
|
self,
|
|
graph,
|
|
label="f(x)",
|
|
x_val=None,
|
|
direction=RIGHT,
|
|
buff=MED_SMALL_BUFF,
|
|
color=None,
|
|
):
|
|
"""
|
|
This method returns a properly positioned label for the passed graph,
|
|
styled with the passed parameters.
|
|
|
|
Parameters
|
|
----------
|
|
graph : ParametricFunction
|
|
The curve of the function plotted.
|
|
|
|
label : str = "f(x)"
|
|
The label for the function's curve.
|
|
|
|
x_val : Union[float, int]
|
|
The x_value with which the label should be aligned.
|
|
|
|
direction : Union[np.ndarray,list,tuple]=RIGHT
|
|
The position, relative to the curve that the label will be at.
|
|
e.g LEFT, RIGHT
|
|
|
|
buff : Union[float, int]
|
|
The buffer space between the curve and the label
|
|
|
|
color : str
|
|
The color of the label.
|
|
|
|
Returns
|
|
-------
|
|
TexMobject
|
|
The LaTeX of the passed 'label' parameter
|
|
|
|
"""
|
|
label = TexMobject(label)
|
|
color = color or graph.get_color()
|
|
label.set_color(color)
|
|
if x_val is None:
|
|
# Search from right to left
|
|
for x in np.linspace(self.x_max, self.x_min, 100):
|
|
point = self.input_to_graph_point(x, graph)
|
|
if point[1] < FRAME_Y_RADIUS:
|
|
break
|
|
x_val = x
|
|
label.next_to(
|
|
self.input_to_graph_point(x_val, graph),
|
|
direction,
|
|
buff=buff
|
|
)
|
|
label.shift_onto_screen()
|
|
return label
|
|
|
|
def get_riemann_rectangles(
|
|
self,
|
|
graph,
|
|
x_min=None,
|
|
x_max=None,
|
|
dx=0.1,
|
|
input_sample_type="left",
|
|
stroke_width=1,
|
|
stroke_color=BLACK,
|
|
fill_opacity=1,
|
|
start_color=None,
|
|
end_color=None,
|
|
show_signed_area=True,
|
|
width_scale_factor=1.001
|
|
):
|
|
"""
|
|
This method returns the VGroup() of the Riemann Rectangles for
|
|
a particular curve.
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph whose area needs to be approximated
|
|
by the Riemann Rectangles.
|
|
|
|
x_min Union[int,float]
|
|
The lower bound from which to start adding rectangles
|
|
|
|
x_max Union[int,float]
|
|
The upper bound where the rectangles stop.
|
|
|
|
dx Union[int,float]
|
|
The smallest change in x-values that is
|
|
considered significant.
|
|
|
|
input_sample_type str
|
|
Can be any of "left", "right" or "center
|
|
|
|
stroke_width : Union[int, float]
|
|
The stroke_width of the border of the rectangles.
|
|
|
|
stroke_color : str
|
|
The string of hex colour of the rectangle's border.
|
|
|
|
fill_opacity Union[int, float]
|
|
The opacity of the rectangles.
|
|
|
|
start_color : str,
|
|
The hex starting colour for the rectangles,
|
|
this will, if end_color is a different colour,
|
|
make a nice gradient.
|
|
|
|
end_color : str,
|
|
The hex ending colour for the rectangles,
|
|
this will, if start_color is a different colour,
|
|
make a nice gradient.
|
|
|
|
show_signed_area : bool (True)
|
|
Whether or not to indicate -ve area if curve dips below
|
|
x-axis.
|
|
|
|
width_scale_factor : Union[int, float]
|
|
How much the width of the rectangles are scaled by when transforming.
|
|
|
|
Returns
|
|
-------
|
|
VGroup
|
|
A VGroup containing the Riemann Rectangles.
|
|
|
|
"""
|
|
x_min = x_min if x_min is not None else self.x_min
|
|
x_max = x_max if x_max is not None else self.x_max
|
|
if start_color is None:
|
|
start_color = self.default_riemann_start_color
|
|
if end_color is None:
|
|
end_color = self.default_riemann_end_color
|
|
rectangles = VGroup()
|
|
x_range = np.arange(x_min, x_max, dx)
|
|
colors = color_gradient([start_color, end_color], len(x_range))
|
|
for x, color in zip(x_range, colors):
|
|
if input_sample_type == "left":
|
|
sample_input = x
|
|
elif input_sample_type == "right":
|
|
sample_input = x + dx
|
|
elif input_sample_type == "center":
|
|
sample_input = x + 0.5 * dx
|
|
else:
|
|
raise Exception("Invalid input sample type")
|
|
graph_point = self.input_to_graph_point(sample_input, graph)
|
|
points = VGroup(*list(map(VectorizedPoint, [
|
|
self.coords_to_point(x, 0),
|
|
self.coords_to_point(x + width_scale_factor * dx, 0),
|
|
graph_point
|
|
])))
|
|
|
|
rect = Rectangle()
|
|
rect.replace(points, stretch=True)
|
|
if graph_point[1] < self.graph_origin[1] and show_signed_area:
|
|
fill_color = invert_color(color)
|
|
else:
|
|
fill_color = color
|
|
rect.set_fill(fill_color, opacity=fill_opacity)
|
|
rect.set_stroke(stroke_color, width=stroke_width)
|
|
rectangles.add(rect)
|
|
return rectangles
|
|
|
|
def get_riemann_rectangles_list(
|
|
self,
|
|
graph,
|
|
n_iterations,
|
|
max_dx=0.5,
|
|
power_base=2,
|
|
stroke_width=1,
|
|
**kwargs
|
|
):
|
|
"""
|
|
This method returns a list of multiple VGroups of Riemann
|
|
Rectangles. The inital VGroups are relatively inaccurate,
|
|
but the closer you get to the end the more accurate the Riemann
|
|
rectangles become
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph whose area needs to be approximated
|
|
by the Riemann Rectangles.
|
|
|
|
n_iterations,
|
|
The number of VGroups of successive accuracy that are needed.
|
|
|
|
max_dx Union[int,float]
|
|
The maximum change in x between two VGroups of Riemann Rectangles
|
|
|
|
power_base Union[int,float=2]
|
|
|
|
stroke_width : Union[int, float]
|
|
The stroke_width of the border of the rectangles.
|
|
|
|
**kwargs
|
|
Any valid keyword arguments of get_riemann_rectangles.
|
|
|
|
Returns
|
|
-------
|
|
list
|
|
The list of Riemann Rectangles of increasing accuracy.
|
|
"""
|
|
return [
|
|
self.get_riemann_rectangles(
|
|
graph=graph,
|
|
dx=float(max_dx) / (power_base**n),
|
|
stroke_width=float(stroke_width) / (power_base**n),
|
|
**kwargs
|
|
)
|
|
for n in range(n_iterations)
|
|
]
|
|
|
|
def get_area(self, graph, t_min, t_max):
|
|
"""
|
|
Returns a VGroup of Riemann rectangles
|
|
sufficiently small enough to visually
|
|
approximate the area under the graph passed.
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph/curve for which the area needs to be gotten.
|
|
|
|
t_min Union[int, float]
|
|
The lower bound of x from which to approximate the area.
|
|
|
|
t_max Union[int, float]
|
|
The upper bound of x until which the area must be approximated.
|
|
|
|
Returns
|
|
-------
|
|
VGroup
|
|
The VGroup containing the Riemann Rectangles.
|
|
"""
|
|
numerator = max(t_max - t_min, 0.0001)
|
|
dx = float(numerator) / self.num_rects
|
|
return self.get_riemann_rectangles(
|
|
graph,
|
|
x_min=t_min,
|
|
x_max=t_max,
|
|
dx=dx,
|
|
stroke_width=0,
|
|
).set_fill(opacity=self.area_opacity)
|
|
|
|
def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs):
|
|
"""
|
|
This method is used to transform between two VGroups of Riemann Rectangles,
|
|
if they were obtained by get_riemann_rectangles or get_riemann_rectangles_list.
|
|
No animation is returned, and the animation is directly played.
|
|
|
|
Parameters
|
|
----------
|
|
curr_rects : VGroup
|
|
The current Riemann Rectangles
|
|
|
|
new_rects : VGroup
|
|
The Riemann Rectangles to transform to.
|
|
|
|
**kwargs
|
|
added_anims
|
|
Any other animations to play simultaneously.
|
|
"""
|
|
transform_kwargs = {
|
|
"run_time": 2,
|
|
"lag_ratio": 0.5
|
|
}
|
|
added_anims = kwargs.get("added_anims", [])
|
|
transform_kwargs.update(kwargs)
|
|
curr_rects.align_submobjects(new_rects)
|
|
x_coords = set() # Keep track of new repetitions
|
|
for rect in curr_rects:
|
|
x = rect.get_center()[0]
|
|
if x in x_coords:
|
|
rect.set_fill(opacity=0)
|
|
else:
|
|
x_coords.add(x)
|
|
self.play(
|
|
Transform(curr_rects, new_rects, **transform_kwargs),
|
|
*added_anims
|
|
)
|
|
|
|
def get_vertical_line_to_graph(
|
|
self,
|
|
x, graph,
|
|
line_class=Line,
|
|
**line_kwargs
|
|
):
|
|
"""
|
|
This method returns a Vertical line from the x-axis to
|
|
the corresponding point on the graph/curve.
|
|
|
|
Parameters
|
|
----------
|
|
x Union[int,float]
|
|
The x-value at which the line should be placed/calculated.
|
|
|
|
graph (ParametricFunction)
|
|
The graph on which the line should extend to.
|
|
|
|
line_class (Line and similar)
|
|
The type of line that should be used.
|
|
Defaults to Line
|
|
|
|
**line_kwargs
|
|
Any valid keyword arguments of the object passed in "line_class"
|
|
If line_class is Line, any valid keyword arguments of Line are allowed.
|
|
|
|
Return
|
|
------
|
|
An object of type passed in "line_class"
|
|
Defaults to Line
|
|
"""
|
|
if "color" not in line_kwargs:
|
|
line_kwargs["color"] = graph.get_color()
|
|
return line_class(
|
|
self.coords_to_point(x, 0),
|
|
self.input_to_graph_point(x, graph),
|
|
**line_kwargs
|
|
)
|
|
|
|
def get_vertical_lines_to_graph(
|
|
self, graph,
|
|
x_min=None,
|
|
x_max=None,
|
|
num_lines=20,
|
|
**kwargs
|
|
):
|
|
"""
|
|
Obtains multiple lines from the x axis to the Graph/curve.
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph on which the line should extend to.
|
|
|
|
x_min (Union[int, float])
|
|
The lower bound from which lines can appear.
|
|
|
|
x_max (Union[int, float])
|
|
The upper bound until which the lines can appear.
|
|
|
|
num_lines (Union[int, float])
|
|
The number of lines (evenly spaced)
|
|
that are needed.
|
|
|
|
Returns
|
|
-------
|
|
VGroup
|
|
The VGroup of the evenly spaced lines.
|
|
|
|
"""
|
|
x_min = x_min or self.x_min
|
|
x_max = x_max or self.x_max
|
|
return VGroup(*[
|
|
self.get_vertical_line_to_graph(x, graph, **kwargs)
|
|
for x in np.linspace(x_min, x_max, num_lines)
|
|
])
|
|
|
|
def get_secant_slope_group(
|
|
self,
|
|
x, graph,
|
|
dx=None,
|
|
dx_line_color=None,
|
|
df_line_color=None,
|
|
dx_label=None,
|
|
df_label=None,
|
|
include_secant_line=True,
|
|
secant_line_color=None,
|
|
secant_line_length=10,
|
|
):
|
|
"""
|
|
This method returns a VGroup of (two lines
|
|
representing dx and df, the labels for dx and
|
|
df, and the Secant to the Graph/curve at a
|
|
particular x value.
|
|
|
|
Parameters
|
|
----------
|
|
x (Union[float, int])
|
|
The x value at which the secant enters, and intersects
|
|
the graph for the first time.
|
|
|
|
graph (ParametricFunction)
|
|
The curve/graph for which the secant must
|
|
be found.
|
|
|
|
dx (Union[float, int])
|
|
The change in x after which the secant exits.
|
|
|
|
dx_line_color (str)
|
|
The line color for the line that indicates the change in x.
|
|
|
|
df_line_color (str)
|
|
The line color for the line that indicates the change in y.
|
|
|
|
dx_label (str)
|
|
The label to be provided for the change in x.
|
|
|
|
df_label (str)
|
|
The label to be provided for the change in y.
|
|
|
|
include_secant_line (bool=True)
|
|
Whether or not to include the secant line in the graph,
|
|
or just have the df and dx lines and labels.
|
|
|
|
secant_line_color (str)
|
|
The color of the secant line.
|
|
|
|
secant_line_length (Union[float,int=10])
|
|
How long the secant line should be.
|
|
|
|
Returns:
|
|
--------
|
|
VGroup
|
|
Resulting group is of the form VGroup(
|
|
dx_line,
|
|
df_line,
|
|
dx_label, (if applicable)
|
|
df_label, (if applicable)
|
|
secant_line, (if applicable)
|
|
)
|
|
with attributes of those names.
|
|
"""
|
|
kwargs = locals()
|
|
kwargs.pop("self")
|
|
group = VGroup()
|
|
group.kwargs = kwargs
|
|
|
|
dx = dx or float(self.x_max - self.x_min) / 10
|
|
dx_line_color = dx_line_color or self.default_input_color
|
|
df_line_color = df_line_color or graph.get_color()
|
|
|
|
p1 = self.input_to_graph_point(x, graph)
|
|
p2 = self.input_to_graph_point(x + dx, graph)
|
|
interim_point = p2[0] * RIGHT + p1[1] * UP
|
|
|
|
group.dx_line = Line(
|
|
p1, interim_point,
|
|
color=dx_line_color
|
|
)
|
|
group.df_line = Line(
|
|
interim_point, p2,
|
|
color=df_line_color
|
|
)
|
|
group.add(group.dx_line, group.df_line)
|
|
|
|
labels = VGroup()
|
|
if dx_label is not None:
|
|
group.dx_label = TexMobject(dx_label)
|
|
labels.add(group.dx_label)
|
|
group.add(group.dx_label)
|
|
if df_label is not None:
|
|
group.df_label = TexMobject(df_label)
|
|
labels.add(group.df_label)
|
|
group.add(group.df_label)
|
|
|
|
if len(labels) > 0:
|
|
max_width = 0.8 * group.dx_line.get_width()
|
|
max_height = 0.8 * group.df_line.get_height()
|
|
if labels.get_width() > max_width:
|
|
labels.set_width(max_width)
|
|
if labels.get_height() > max_height:
|
|
labels.set_height(max_height)
|
|
|
|
if dx_label is not None:
|
|
group.dx_label.next_to(
|
|
group.dx_line,
|
|
np.sign(dx) * DOWN,
|
|
buff=group.dx_label.get_height() / 2
|
|
)
|
|
group.dx_label.set_color(group.dx_line.get_color())
|
|
|
|
if df_label is not None:
|
|
group.df_label.next_to(
|
|
group.df_line,
|
|
np.sign(dx) * RIGHT,
|
|
buff=group.df_label.get_height() / 2
|
|
)
|
|
group.df_label.set_color(group.df_line.get_color())
|
|
|
|
if include_secant_line:
|
|
secant_line_color = secant_line_color or self.default_derivative_color
|
|
group.secant_line = Line(p1, p2, color=secant_line_color)
|
|
group.secant_line.scale_in_place(
|
|
secant_line_length / group.secant_line.get_length()
|
|
)
|
|
group.add(group.secant_line)
|
|
|
|
return group
|
|
|
|
def add_T_label(self, x_val, side=RIGHT, label=None, color=WHITE, animated=False, **kwargs):
|
|
"""
|
|
This method adds to the Scene:
|
|
-- a Vertical line from the x-axis to the corresponding point on the graph/curve.
|
|
-- a small vertical Triangle whose top point lies on the base of the vertical line
|
|
-- a TexMobject to be a label for the Line and Triangle, at the bottom of the Triangle.
|
|
The scene needs to have the graph have the identifier/variable name self.v_graph.
|
|
|
|
Parameters
|
|
----------
|
|
x_val (Union[float, int])
|
|
The x value at which the secant enters, and intersects
|
|
the graph for the first time.
|
|
|
|
side (np.ndarray())
|
|
|
|
label (str)
|
|
The label to give the vertline and triangle
|
|
|
|
color (str)
|
|
The hex color of the label.
|
|
|
|
animated (bool=False)
|
|
Whether or not to animate the addition of the T_label
|
|
|
|
**kwargs
|
|
Any valid keyword argument of a self.play call.
|
|
"""
|
|
triangle = RegularPolygon(n=3, start_angle=np.pi / 2)
|
|
triangle.set_height(MED_SMALL_BUFF)
|
|
triangle.move_to(self.coords_to_point(x_val, 0), UP)
|
|
triangle.set_fill(color, 1)
|
|
triangle.set_stroke(width=0)
|
|
if label is None:
|
|
T_label = TexMobject(self.variable_point_label, fill_color=color)
|
|
else:
|
|
T_label = TexMobject(label, fill_color=color)
|
|
|
|
T_label.next_to(triangle, DOWN)
|
|
v_line = self.get_vertical_line_to_graph(
|
|
x_val, self.v_graph,
|
|
color=YELLOW
|
|
)
|
|
|
|
if animated:
|
|
self.play(
|
|
DrawBorderThenFill(triangle),
|
|
ShowCreation(v_line),
|
|
Write(T_label, run_time=1),
|
|
**kwargs
|
|
)
|
|
|
|
if np.all(side == LEFT):
|
|
self.left_T_label_group = VGroup(T_label, triangle)
|
|
self.left_v_line = v_line
|
|
self.add(self.left_T_label_group, self.left_v_line)
|
|
elif np.all(side == RIGHT):
|
|
self.right_T_label_group = VGroup(T_label, triangle)
|
|
self.right_v_line = v_line
|
|
self.add(self.right_T_label_group, self.right_v_line)
|
|
|
|
def get_animation_integral_bounds_change(
|
|
self,
|
|
graph,
|
|
new_t_min,
|
|
new_t_max,
|
|
fade_close_to_origin=True,
|
|
run_time=1.0
|
|
):
|
|
"""
|
|
This method requires a lot of prerequisites:
|
|
self.area must be defined from self.get_area()
|
|
self.left_v_line and self.right_v_line must be defined from self.get_v_line
|
|
self.left_T_label_group and self.right_T_label_group must be defined from self.add_T_label
|
|
|
|
This method will returna VGroup of new mobjects for each of those, when provided the graph/curve,
|
|
the new t_min and t_max, the run_time and a bool stating whether or not to fade when close to
|
|
the origin.
|
|
|
|
Parameters
|
|
----------
|
|
graph (ParametricFunction)
|
|
The graph for which this must be done.
|
|
|
|
new_t_min (Union[int,float])
|
|
The new lower bound.
|
|
|
|
new_t_max (Union[int,float])
|
|
The new upper bound.
|
|
|
|
fade_close_to_origin (bool=True)
|
|
Whether or not to fade when close to the origin.
|
|
|
|
run_time (Union[int,float=1.0])
|
|
The run_time of the animation of this change.
|
|
"""
|
|
curr_t_min = self.x_axis.point_to_number(self.area.get_left())
|
|
curr_t_max = self.x_axis.point_to_number(self.area.get_right())
|
|
if new_t_min is None:
|
|
new_t_min = curr_t_min
|
|
if new_t_max is None:
|
|
new_t_max = curr_t_max
|
|
|
|
group = VGroup(self.area)
|
|
group.add(self.left_v_line)
|
|
group.add(self.left_T_label_group)
|
|
group.add(self.right_v_line)
|
|
group.add(self.right_T_label_group)
|
|
|
|
def update_group(group, alpha):
|
|
area, left_v_line, left_T_label, right_v_line, right_T_label = group
|
|
t_min = interpolate(curr_t_min, new_t_min, alpha)
|
|
t_max = interpolate(curr_t_max, new_t_max, alpha)
|
|
new_area = self.get_area(graph, t_min, t_max)
|
|
|
|
new_left_v_line = self.get_vertical_line_to_graph(
|
|
t_min, graph
|
|
)
|
|
new_left_v_line.set_color(left_v_line.get_color())
|
|
left_T_label.move_to(new_left_v_line.get_bottom(), UP)
|
|
|
|
new_right_v_line = self.get_vertical_line_to_graph(
|
|
t_max, graph
|
|
)
|
|
new_right_v_line.set_color(right_v_line.get_color())
|
|
right_T_label.move_to(new_right_v_line.get_bottom(), UP)
|
|
|
|
# Fade close to 0
|
|
if fade_close_to_origin:
|
|
if len(left_T_label) > 0:
|
|
left_T_label[0].set_fill(opacity=min(1, np.abs(t_min)))
|
|
if len(right_T_label) > 0:
|
|
right_T_label[0].set_fill(opacity=min(1, np.abs(t_max)))
|
|
|
|
Transform(area, new_area).update(1)
|
|
Transform(left_v_line, new_left_v_line).update(1)
|
|
Transform(right_v_line, new_right_v_line).update(1)
|
|
return group
|
|
|
|
return UpdateFromAlphaFunc(group, update_group, run_time=run_time)
|
|
|
|
def animate_secant_slope_group_change(
|
|
self, secant_slope_group,
|
|
target_dx=None,
|
|
target_x=None,
|
|
run_time=3,
|
|
added_anims=None,
|
|
**anim_kwargs
|
|
):
|
|
"""
|
|
This method animates the change of the secant slope group from
|
|
the old secant slope group, into a new secant slope group.
|
|
|
|
Parameters
|
|
----------
|
|
secant_slope_group (VGroup)
|
|
The old secant_slope_group
|
|
|
|
target_dx Union[int, float]
|
|
The new dx value.
|
|
|
|
target_x Union[int, float]
|
|
The new x value at which the secant should be.
|
|
|
|
run_time Union[int,float=3]
|
|
The run time for this change when animated.
|
|
|
|
added_anims
|
|
Any exta animations that should be played alongside.
|
|
|
|
**anim_kwargs
|
|
Any valid kwargs of a self.play call.
|
|
|
|
NOTE: At least one of target_dx and target_x should be not None.
|
|
"""
|
|
if target_dx is None and target_x is None:
|
|
raise Exception(
|
|
"At least one of target_x and target_dx must not be None")
|
|
if added_anims is None:
|
|
added_anims = []
|
|
|
|
start_dx = secant_slope_group.kwargs["dx"]
|
|
start_x = secant_slope_group.kwargs["x"]
|
|
if target_dx is None:
|
|
target_dx = start_dx
|
|
if target_x is None:
|
|
target_x = start_x
|
|
|
|
def update_func(group, alpha):
|
|
dx = interpolate(start_dx, target_dx, alpha)
|
|
x = interpolate(start_x, target_x, alpha)
|
|
kwargs = dict(secant_slope_group.kwargs)
|
|
kwargs["dx"] = dx
|
|
kwargs["x"] = x
|
|
new_group = self.get_secant_slope_group(**kwargs)
|
|
group.become(new_group)
|
|
return group
|
|
|
|
self.play(
|
|
UpdateFromAlphaFunc(
|
|
secant_slope_group, update_func,
|
|
run_time=run_time,
|
|
**anim_kwargs
|
|
),
|
|
*added_anims
|
|
)
|
|
secant_slope_group.kwargs["x"] = target_x
|
|
secant_slope_group.kwargs["dx"] = target_dx
|