Support VDict init from python dict of mobjects (#316)

* Support VDict init from python dict of mobjects

* Small style change

* Formatting using black

* Rework VDict to emulate init of python dict

* Update comment

* Add a few tests

* Update manim/mobject/types/vectorized_mobject.py

* Update docs

* Add few more tests, raise TypeError instead of Exception

* Raise KeyError instead of Exception when key is not in the VDcit

* Remove old comment

Co-authored-by: Leo Torres <dleonardotn@gmail.com>
This commit is contained in:
Nilay 2020-08-20 16:59:17 +05:30 committed by GitHub
commit c19a06db24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 47 deletions

View file

@ -118,14 +118,14 @@ class UpdatersExample(Scene):
self.wait()
class VDictTest(Scene):
class VDictExample(Scene):
def construct(self):
square = Square().set_color(RED)
circle = Circle().set_color(YELLOW).next_to(square, UP)
# create dict from list of tuples each having key-mobject pair
pairs = [("s", square), ("c", circle)]
my_dict = VDict(*pairs, show_keys=True)
my_dict = VDict(pairs, show_keys=True)
# display it just like a VGroup
self.play(ShowCreation(my_dict))
@ -133,8 +133,9 @@ class VDictTest(Scene):
text = TextMobject("Some text").set_color(GREEN).next_to(square, DOWN)
# add like a VGroup
my_dict.add(("t", text))
# add a key-value pair by wrapping it in a single-element list of tuple
# after attrs branch is merged, it will be easier like `.add(t=text)`
my_dict.add([("t", text)])
self.wait()
rect = Rectangle().next_to(text, DOWN)
@ -150,7 +151,7 @@ class VDictTest(Scene):
my_dict["t"] = TextMobject("Some other text").set_color(BLUE)
self.wait()
# remove submojects by key
# remove submoject by key
my_dict.remove("t")
self.wait()
@ -163,10 +164,22 @@ class VDictTest(Scene):
self.play(FadeOutAndShift(my_dict["r"], DOWN))
self.wait()
# iterate through all submobjects currently associated with my_dict
for submob in my_dict.get_all_submobjects():
self.play(ShowCreation(submob))
self.wait()
# you can also make a VDict from an existing dict of mobjects
plain_dict = {
1: Integer(1).shift(DOWN),
2: Integer(2).shift(2 * DOWN),
3: Integer(3).shift(3 * DOWN),
}
vdict_from_plain_dict = VDict(plain_dict)
vdict_from_plain_dict.shift(1.5 * (UP + LEFT))
self.play(ShowCreation(vdict_from_plain_dict))
# you can even use zip
vdict_using_zip = VDict(zip(["s", "c", "r"], [Square(), Circle(), Rectangle()]))
vdict_using_zip.shift(1.5 * RIGHT)
self.play(ShowCreation(vdict_using_zip))
self.wait()
# See old_projects folder for many, many more

View file

@ -877,15 +877,13 @@ class VDict(VMobject):
Parameters
----------
pairs: Tuple[Hashable, :class:`~.VMobject`]
Each pair is a 2-element :class:`tuple` wherein the first
element is the key for the mobject and the second
element is the actual mobject
mapping_or_iterable : Union[:class:`Mapping`, Iterable[Tuple[Hashable, :class:`~.VMobject`]]], optional
The parameter specifying the key-value mapping of keys and mobjects.
show_keys : :class:`bool`, optional
Whether to also display the key associated with
the mobject. This might be useful when debugging,
especially when there are a lot of mobjects in the
:class:`VDict`. Defaults to False
:class:`VDict`. Defaults to False.
kwargs : Any
Other arguments to be passed to `Mobject` or the CONFIG.
@ -897,58 +895,43 @@ class VDict(VMobject):
especially when there are a lot of mobjects in the
:class:`VDict`. When displayed, the key is towards
the left of the mobject.
Defaults to False
Defaults to False.
submob_dict : :class:`dict`
Is the actual python dictionary that is used to bind
the keys to the mobjects
the keys to the mobjects.
"""
def __init__(self, *pairs, show_keys=False, **kwargs):
if not all(isinstance(m[1], VMobject) for m in pairs):
raise Exception("All submobjects must be of type VMobject")
def __init__(self, mapping_or_iterable={}, show_keys=False, **kwargs):
VMobject.__init__(self, **kwargs)
self.show_keys = show_keys
self.submob_dict = {}
self.add(*pairs)
self.add(mapping_or_iterable)
def add(self, *pairs):
def add(self, mapping_or_iterable):
"""Adds the key-value pairs to the :class:`VDict` object.
Also, it internally adds the value to the `submobjects` :class:`list`
of :class:`~.Mobject`, which is responsible for actual on-screen display
of :class:`~.Mobject`, which is responsible for actual on-screen display.
Parameters
---------
pairs : Tuple[Hashable, :class:`~.VMobject`]
Each pair is a :class:`tuple` wherein the first
element is the key for the mobject and the second
element is the actual mobject
mapping_or_iterable : Union[:class:`Mapping`, Iterable[Tuple[Hashable, :class:`~.VMobject`]]], optional
The parameter specifying the key-value mapping of keys and mobjects.
Returns
-------
:class:`VDict`
Returns the :class:`VDict` object on which this method was called
Returns the :class:`VDict` object on which this method was called.
Examples
--------
Normal usage::
square_obj = Square()
my_dict.add(('s', square_obj))
my_dict.add([('s', square_obj)])
"""
for pair in pairs:
key = pair[0]
value = pair[1]
for key, value in dict(mapping_or_iterable).items():
self.add_key_value_pair(key, value)
mob = value
if self.show_keys:
# This import is here and not at the top to avoid circular import
from ...mobject.svg.tex_mobject import TextMobject
key_text = TextMobject(str(key)).next_to(value, LEFT)
mob.add(key_text)
self.submob_dict[key] = mob
super().add(value)
return self
def remove(self, key):
@ -960,12 +943,12 @@ class VDict(VMobject):
Parameters
----------
key : Hashable
The key of the submoject to be removed
The key of the submoject to be removed.
Returns
-------
:class:`VDict`
Returns the :class:`VDict` object on which this method was called
Returns the :class:`VDict` object on which this method was called.
Examples
--------
@ -973,7 +956,7 @@ class VDict(VMobject):
my_dict.remove('square')
"""
if key not in self.submob_dict:
raise Exception("The given key '%s' is not present in the VDict" % str(key))
raise KeyError("The given key '%s' is not present in the VDict" % str(key))
super().remove(self.submob_dict[key])
del self.submob_dict[key]
return self
@ -1021,7 +1004,7 @@ class VDict(VMobject):
"""
if key in self.submob_dict:
self.remove(key)
self.add((key, value))
self.add([(key, value)])
def get_all_submobjects(self):
"""To get all the submobjects associated with a particular :class:`VDict` object
@ -1040,6 +1023,46 @@ class VDict(VMobject):
submobjects = self.submob_dict.values()
return submobjects
def add_key_value_pair(self, key, value):
"""A utility function used by :meth:`add` to add the key-value pair
to :attr:`submob_dict`. Not really meant to be used externally.
Parameters
----------
key : Hashable
The key of the submobject to be added.
value : :class:`~.VMobject`
The mobject associated with the key
Returns
-------
None
Raises
------
TypeError
If the value is not an instance of VMobject
Examples
--------
Normal usage::
square_obj = Square()
self.add_key_value_pair('s', square_obj)
"""
if not isinstance(value, VMobject):
raise TypeError("All submobjects must be of type VMobject")
mob = value
if self.show_keys:
# This import is here and not at the top to avoid circular import
from ...mobject.svg.tex_mobject import TextMobject
key_text = TextMobject(str(key)).next_to(value, LEFT)
mob.add(key_text)
self.submob_dict[key] = mob
super().add(value)
class VectorizedPoint(VMobject):
CONFIG = {

View file

@ -1,5 +1,5 @@
import pytest
from manim import Mobject, VMobject, VGroup
from manim import Mobject, VMobject, VGroup, VDict
def test_vgroup_init():
@ -29,3 +29,38 @@ def test_vgroup_add():
with pytest.raises(Exception): # TODO change this to ValueError once #307 is merged
# a Mobject cannot contain itself
obj.add(obj)
def test_vdict_init():
"""Test the VDict instantiation."""
# Test empty VDict
VDict()
# Test VDict made from list of pairs
VDict([("a", VMobject()), ("b", VMobject()), ("c", VMobject())])
# Test VDict made from a python dict
VDict({"a": VMobject(), "b": VMobject(), "c": VMobject()})
# Test VDict made using zip
VDict(zip(["a", "b", "c"], [VMobject(), VMobject(), VMobject()]))
# If the value is of type Mobject, must raise a TypeError
with pytest.raises(TypeError):
VDict({"a": Mobject()})
def test_vdict_add():
"""Test the VDict add method."""
obj = VDict()
assert len(obj.submob_dict) == 0
obj.add([("a", VMobject())])
assert len(obj.submob_dict) == 1
with pytest.raises(TypeError):
obj.add([("b", Mobject())])
def test_vdict_remove():
"""Test the VDict remove method."""
obj = VDict([("a", VMobject())])
assert len(obj.submob_dict) == 1
obj.remove("a")
assert len(obj.submob_dict) == 0
with pytest.raises(KeyError):
obj.remove("a")

View file

@ -6,7 +6,7 @@
<path style="stroke:none;" d="M 1.25 0 L 1.25 -6.25 L 6.25 -6.25 L 6.25 0 Z M 1.40625 -0.15625 L 6.09375 -0.15625 L 6.09375 -6.09375 L 1.40625 -6.09375 Z M 1.40625 -0.15625 "/>
</symbol>
<symbol overflow="visible" id="glyph0-1">
<path style="stroke:none;" d="M -0.152344 1.988281 L -0.152344 1.351562 L 5.671875 1.351562 L 5.671875 1.988281 Z M -0.152344 1.988281 "/>
<path style="stroke:none;" d="M -0.152344 1.988281 L -0.152344 1.351563 L 5.671875 1.351563 L 5.671875 1.988281 Z M -0.152344 1.988281 "/>
</symbol>
</g>
</defs>

Before

Width:  |  Height:  |  Size: 761 B

After

Width:  |  Height:  |  Size: 761 B

Before After
Before After