-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new animation TraceColor
(Name for debate) and Graph.set_edge_color()
#3533
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ def construct(self): | |
"ApplyWave", | ||
"Circumscribe", | ||
"Wiggle", | ||
"TraceColor", | ||
] | ||
|
||
from typing import Callable, Iterable, Optional, Tuple, Type, Union | ||
|
@@ -319,6 +320,60 @@ def clean_up_from_scene(self, scene: Scene) -> None: | |
submob.pointwise_become_partial(start, 0, 1) | ||
|
||
|
||
class TraceColor(ShowPartial): | ||
"""Changing the color of a VMobject along its stroke. | ||
|
||
Parameters | ||
---------- | ||
mobject | ||
The mobject whose stroke is animated. | ||
color | ||
The color to which the stroke is changed. | ||
|
||
Examples | ||
-------- | ||
.. manim:: TraceColorExample | ||
|
||
class TraceColorExample(Scene): | ||
def construct(self) -> None: | ||
c = Circle().set_color(RED).set_opacity(0.5) | ||
s = Square().set_color(BLUE).scale(2) | ||
self.add(c) | ||
self.play(TraceColor(c, color=BLUE, run_time=1), TraceColor(s, color=RED, run_time=2)) | ||
""" | ||
|
||
def __init__( | ||
self, mobject: "VMobject", color: ParsableManimColor, **kwargs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason |
||
) -> None: | ||
super().__init__(mobject, **kwargs) | ||
self.color = color | ||
|
||
def begin(self) -> None: | ||
self.starting_mobject = ( | ||
self.mobject.copy() | ||
) # Used to transition in the new line | ||
self.mobject_copy = ( | ||
self.mobject.copy() | ||
) # just kept as reference to what the mobject once was | ||
self.inner_mobject = ( | ||
self.mobject.copy() | ||
) # Used to transition out the original line | ||
self.mobject.set_opacity(0) | ||
self.mobject.add(self.inner_mobject) | ||
self.mobject.add(self.starting_mobject) | ||
|
||
def interpolate_mobject(self, alpha: float) -> None: | ||
self.inner_mobject.pointwise_become_partial(self.mobject_copy, alpha, 1) | ||
self.starting_mobject.pointwise_become_partial( | ||
self.mobject_copy, 0, alpha | ||
).set_color(self.color) | ||
|
||
def finish(self) -> None: | ||
self.mobject.remove(self.starting_mobject) | ||
self.mobject.remove(self.inner_mobject) | ||
self.mobject.become(self.mobject_copy.set_color(self.color)) | ||
|
||
|
||
class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup): | ||
def __init__(self, vmobject, n_segments=10, time_width=0.1, remover=True, **kwargs): | ||
self.n_segments = n_segments | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,9 @@ | |||||
|
||||||
from __future__ import annotations | ||||||
|
||||||
from manim.animation.indication import TraceColor | ||||||
from manim.utils.color.core import ParsableManimColor | ||||||
|
||||||
__all__ = [ | ||||||
"Graph", | ||||||
"DiGraph", | ||||||
|
@@ -584,7 +587,7 @@ def __init__( | |||||
edge_config: dict | None = None, | ||||||
) -> None: | ||||||
super().__init__() | ||||||
|
||||||
self.edges = {} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This way linters can raise errors about setting/getting stuff from this attribute. |
||||||
nx_graph = self._empty_networkx_graph() | ||||||
nx_graph.add_nodes_from(vertices) | ||||||
nx_graph.add_edges_from(edges) | ||||||
|
@@ -1109,6 +1112,20 @@ def add_edges( | |||||
) | ||||||
return self.get_group_class()(*added_mobjects) | ||||||
|
||||||
def get_edge(self, edge: tuple[Hashable, Hashable]) -> Mobject: | ||||||
"""A dictionary of all edges in the graph. | ||||||
|
||||||
The keys are tuples of vertex identifiers, and the values are | ||||||
the corresponding edge mobjects. | ||||||
""" | ||||||
try: | ||||||
return self.edges[edge] | ||||||
except KeyError: | ||||||
try: | ||||||
return self.edges[(edge[1], edge[0])] | ||||||
except KeyError: | ||||||
raise ValueError(f"The {self} does not contain an edge '{edge}'") | ||||||
|
||||||
@override_animate(add_edges) | ||||||
def _add_edges_animation(self, *args, anim_args=None, **kwargs): | ||||||
if anim_args is None: | ||||||
|
@@ -1120,6 +1137,50 @@ def _add_edges_animation(self, *args, anim_args=None, **kwargs): | |||||
*(animation(mobj, **anim_args) for mobj in mobjects), group=self | ||||||
) | ||||||
|
||||||
def set_edge_color( | ||||||
self, vertices: tuple[Hashable, Hashable], color: ParsableManimColor | ||||||
): | ||||||
"""Set the color of an edge. Can also be used to animate the color change in the direction of the edge. | ||||||
|
||||||
Parameters | ||||||
---------- | ||||||
|
||||||
vertices | ||||||
The edge (as a tuple of vertex identifiers) whose color should be changed. | ||||||
|
||||||
Returns | ||||||
------- | ||||||
Group | ||||||
A group containing all newly added vertices and edges. | ||||||
|
||||||
Examples | ||||||
-------- | ||||||
|
||||||
.. manim:: SetEdgeColor | ||||||
|
||||||
class SetEdgeColor(Scene): | ||||||
def construct(self): | ||||||
g = Graph(vertices=[1,2,3], edges=[(1,2), (2,3), (3,1)]) | ||||||
self.add(g) | ||||||
self.play(AnimationGroup((g.animate.set_edge_color((u,v), RED) for u,v in g.edges), lag_ratio=0.5)) | ||||||
""" | ||||||
self.get_edge(vertices).set_color(color) | ||||||
|
||||||
@override_animate(set_edge_color) | ||||||
def _set_edge_color_animation(self, vertices, color, anim_args=None, **kwargs): | ||||||
if anim_args is None: | ||||||
anim_args = {} | ||||||
animation = anim_args.pop("animation", TraceColor) | ||||||
|
||||||
mobj = self.get_edge(vertices) # Will error on DiGraph if edge is wrong way | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be a good idea to add a warning either in the code (via |
||||||
if vertices in self.edges: | ||||||
return animation(mobj, color=color, **anim_args) | ||||||
else: | ||||||
# Make sure we animate the edge in the right direction (i.e., from u to v) but also flip the name in edges if it is called inverted, this will not work on DiGraph | ||||||
self.edges.pop((vertices[1], vertices[0])) | ||||||
self.edges[vertices] = mobj | ||||||
return animation(mobj.reverse_points(), color=color, **anim_args) | ||||||
|
||||||
def _remove_edge(self, edge: tuple[Hashable]): | ||||||
"""Remove an edge from the graph. | ||||||
|
||||||
|
@@ -1771,5 +1832,15 @@ def update_edges(self, graph): | |||||
edge.become(new_edge) | ||||||
edge.add_tip(tip) | ||||||
|
||||||
def get_edge(self, edge: tuple[Hashable, Hashable]) -> Mobject: | ||||||
"""Retrieves an edge by its vertices. | ||||||
|
||||||
The order of the vertices is relevant here. | ||||||
""" | ||||||
try: | ||||||
return self._edges[edge] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Typo? |
||||||
except KeyError: | ||||||
raise ValueError(f"The {self} does not contain an edge '{edge}'") | ||||||
|
||||||
def __repr__(self: DiGraph) -> str: | ||||||
return f"Directed graph on {len(self.vertices)} vertices and {len(self.edges)} edges" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this neccessary?