-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
graph_scene.py
1054 lines (893 loc) · 34 KB
/
graph_scene.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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