def construct(self):
        GraphScene.construct(self)
        self.generate_spanning_tree()
        self.generate_treeified_spanning_tree()
        branches = self.spanning_tree.split()
        branches_copy = deepcopy(branches)
        treeified_branches = self.treeified_spanning_tree.split()
        tree = TextMobject("``Tree''").to_edge(UP)
        spanning_tree = TextMobject("``Spanning Tree''").to_edge(UP)

        self.add(*branches)
        self.play(FadeOut(Mobject(*self.edges + self.vertices)), Animation(Mobject(*branches)))
        self.clear()
        self.add(tree, *branches)
        self.dither()
        self.play(*[Transform(b1, b2, run_time=2) for b1, b2 in zip(branches, treeified_branches)])
        self.dither()
        self.play(
            *[FadeIn(mob) for mob in self.edges + self.vertices]
            + [Transform(b1, b2, run_time=2) for b1, b2 in zip(branches, branches_copy)]
        )
        self.accent_vertices(run_time=2)
        self.remove(tree)
        self.add(spanning_tree)
        self.dither(2)
    def construct(self):
        GraphScene.construct(self)
        self.generate_spanning_tree()
        self.generate_treeified_spanning_tree()
        branches = self.spanning_tree.split()
        branches_copy = deepcopy(branches)
        treeified_branches = self.treeified_spanning_tree.split()
        tree = TextMobject("``Tree''").to_edge(UP)
        spanning_tree = TextMobject("``Spanning Tree''").to_edge(UP)

        self.add(*branches)
        self.play(
            FadeOut(Mobject(*self.edges + self.vertices)),
            Animation(Mobject(*branches)),
        )
        self.clear()
        self.add(tree, *branches)
        self.wait()
        self.play(*[
            Transform(b1, b2, run_time=2)
            for b1, b2 in zip(branches, treeified_branches)
        ])
        self.wait()
        self.play(*[FadeIn(mob) for mob in self.edges + self.vertices] + [
            Transform(b1, b2, run_time=2)
            for b1, b2 in zip(branches, branches_copy)
        ])
        self.accent_vertices(run_time=2)
        self.remove(tree)
        self.add(spanning_tree)
        self.wait(2)
    def construct(self):
        GraphScene.construct(self)
        self.generate_regions()
        self.generate_dual_graph()
        region_mobs = [ImageMobject(disp.paint_region(reg, self.background), invert=False) for reg in self.regions]
        for region, mob in zip(self.regions, region_mobs):
            self.highlight_region(region, mob.get_color())
        outer_region = self.regions.pop()
        outer_region_mob = region_mobs.pop()
        outer_dual_vertex = self.dual_vertices.pop()
        internal_edges = filter(
            lambda e: abs(e.start[0]) < SPACE_WIDTH
            and abs(e.end[0]) < SPACE_WIDTH
            and abs(e.start[1]) < SPACE_HEIGHT
            and abs(e.end[1]) < SPACE_HEIGHT,
            self.dual_edges,
        )
        external_edges = filter(lambda e: e not in internal_edges, self.dual_edges)

        self.dither()
        self.reset_background()
        self.highlight_region(outer_region, outer_region_mob.get_color())
        self.play(*[Transform(reg_mob, dot) for reg_mob, dot in zip(region_mobs, self.dual_vertices)])
        self.dither()
        self.reset_background()
        self.play(ApplyFunction(lambda p: (SPACE_WIDTH + SPACE_HEIGHT) * p / np.linalg.norm(p), outer_region_mob))
        self.dither()
        for edges in internal_edges, external_edges:
            self.play(*[ShowCreation(edge, run_time=2.0) for edge in edges])
            self.dither()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()

        self.add(TextMobject("Duality").to_edge(UP))
        self.remove(*self.vertices)
        def special_alpha(t):
            if t > 0.5:
                t = 1 - t
            if t < 0.25:
                return smooth(4*t)
            else:
                return 1
        kwargs = {
            "run_time" : 5.0,
            "rate_func" : special_alpha
        }
        self.play(*[
            Transform(*edge_pair, **kwargs)
            for edge_pair in zip(self.edges, self.dual_edges)
        ] + [
            Transform(
                Mobject(*[
                    self.vertices[index]
                    for index in cycle
                ]),
                dv,
                **kwargs
            )
            for cycle, dv in zip(
                self.graph.region_cycles, 
                self.dual_vertices
            )
        ])
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        rot_kwargs = {"radians": np.pi / 3, "run_time": 5.0}
        vertices = [point / 2 + OUT if abs(point[0]) == 2 else point + IN for point in self.points]
        cube = Mobject(*[Line(vertices[edge[0]], vertices[edge[1]]) for edge in self.graph.edges])
        cube.rotate(-np.pi / 3, [0, 0, 1])
        cube.rotate(-np.pi / 3, [0, 1, 0])
        dots_to_vertices = TextMobject("Dots $\\to$ Vertices").to_corner()
        lines_to_edges = TextMobject("Lines $\\to$ Edges").to_corner()
        regions_to_faces = TextMobject("Regions $\\to$ Faces").to_corner()

        self.clear()
        # self.play(TransformAnimations(
        #     Rotating(Dodecahedron(), **rot_kwargs),
        #     Rotating(cube, **rot_kwargs)
        # ))
        self.play(Rotating(cube, **rot_kwargs))
        self.clear()
        self.play(*[Transform(l1, l2) for l1, l2 in zip(cube.split(), self.edges)])
        self.dither()
        self.add(dots_to_vertices)
        self.play(*[ShowCreation(dot, run_time=1.0) for dot in self.vertices])
        self.dither(2)
        self.remove(dots_to_vertices, *self.vertices)
        self.add(lines_to_edges)
        self.play(ApplyMethod(Mobject(*self.edges).highlight, "yellow"))
        self.dither(2)
        self.clear()
        self.add(*self.edges)
        self.add(regions_to_faces)
        self.generate_regions()
        for region in self.regions:
            self.highlight_region(region)
        self.dither(3.0)
    def construct(self):
        GraphScene.construct(self)
        account = ImageMobject("facebook_silhouette", invert = False)
        account.scale(0.05)
        logo = ImageMobject("facebook_logo", invert = False)
        logo.scale(0.1)
        logo.shift(0.2*LEFT + 0.1*UP)
        account.add(logo).center()
        account.shift(0.2*LEFT + 0.1*UP)
        friends = TexMobject(
            "\\leftarrow \\text{friends} \\rightarrow"
        ).scale(0.5*EDGE_ANNOTATION_SCALE_VAL)

        self.clear()
        accounts = [
            deepcopy(account).shift(point)
            for point in self.points
        ]
        self.add(*accounts)
        self.dither()
        self.annotate_edges(friends)
        self.dither()
        self.play(*[
            CounterclockwiseTransform(account, vertex)
            for account, vertex in zip(accounts, self.vertices)
        ])
        self.dither()
        self.play(*[
            Transform(ann, edge)
            for ann, edge in zip(self.edge_annotations, self.edges)
        ])
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()

        self.add(TextMobject("Duality").to_edge(UP))
        self.remove(*self.vertices)

        def special_alpha(t):
            if t > 0.5:
                t = 1 - t
            if t < 0.25:
                return smooth(4 * t)
            else:
                return 1

        kwargs = {"run_time": 5.0, "rate_func": special_alpha}
        self.play(*[
            Transform(*edge_pair, **kwargs)
            for edge_pair in zip(self.edges, self.dual_edges)
        ] + [
            Transform(Mobject(*[self.vertices[index]
                                for index in cycle]), dv, **kwargs)
            for cycle, dv in zip(self.graph.region_cycles, self.dual_vertices)
        ])
        self.wait()
    def construct(self):
        GraphScene.construct(self)
        paths = [(1, 2, 4, 5, 6), (6, 7, 1, 3)]
        non_paths = [[(0, 1), (7, 8), (5, 6)], [(5, 0), (0, 2), (0, 1)]]
        valid_path = TextMobject("Valid \\\\ Path").highlight("green")
        not_a_path = TextMobject("Not a \\\\ Path").highlight("red")
        for mob in valid_path, not_a_path:
            mob.to_edge(UP)
        kwargs = {"run_time": 1.0}
        for path, non_path in zip(paths, non_paths):
            path_lines = Mobject(
                *[
                    Line(self.points[path[i]], self.points[path[i + 1]]).highlight("yellow")
                    for i in range(len(path) - 1)
                ]
            )
            non_path_lines = Mobject(
                *[Line(self.points[pp[0]], self.points[pp[1]]).highlight("yellow") for pp in non_path]
            )

            self.remove(not_a_path)
            self.add(valid_path)
            self.play(ShowCreation(path_lines, **kwargs))
            self.dither(2)
            self.remove(path_lines)

            self.remove(valid_path)
            self.add(not_a_path)
            self.play(ShowCreation(non_path_lines, **kwargs))
            self.dither(2)
            self.remove(non_path_lines)
    def construct(self):
        GraphScene.construct(self)
        account = ImageMobject("facebook_silhouette", invert=False)
        account.scale(0.05)
        logo = ImageMobject("facebook_logo", invert=False)
        logo.scale(0.1)
        logo.shift(0.2 * LEFT + 0.1 * UP)
        account.add(logo).center()
        account.shift(0.2 * LEFT + 0.1 * UP)
        friends = TexMobject("\\leftarrow \\text{friends} \\rightarrow").scale(
            0.5 * EDGE_ANNOTATION_SCALE_FACTOR)

        self.clear()
        accounts = [deepcopy(account).shift(point) for point in self.points]
        self.add(*accounts)
        self.wait()
        self.annotate_edges(friends)
        self.wait()
        self.play(*[
            CounterclockwiseTransform(account, vertex)
            for account, vertex in zip(accounts, self.vertices)
        ])
        self.wait()
        self.play(*[
            Transform(ann, edge)
            for ann, edge in zip(self.edge_annotations, self.edges)
        ])
        self.wait()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        self.generate_regions()
        randy = Randolph().shift(LEFT)
        morty = Mortimer().shift(RIGHT)
        name = TextMobject("Mortimer")
        name.shift(morty.get_center() + 1.2*UP)
        randy_path = (0, 1, 3)
        morty_path = (-2, -3, -4)
        morty_crossed_lines = [
            Line(self.points[i], self.points[j]).highlight("red")
            for i, j in [(7, 1), (1, 5)]
        ]
        kwargs = {"run_time" : 1.0}

        self.clear()
        self.add(randy)
        self.dither()
        self.add(morty, name)
        self.dither()
        self.remove(name)
        small_randy = deepcopy(randy).scale(RANDOLPH_SCALE_VAL)
        small_morty = deepcopy(morty).scale(RANDOLPH_SCALE_VAL)
        small_randy.move_to(self.points[randy_path[0]])
        small_morty.move_to(self.dual_points[morty_path[0]])
        self.play(*[
            FadeIn(mob)
            for mob in self.vertices + self.edges
        ] + [
            Transform(randy, small_randy),
            Transform(morty, small_morty),
        ], **kwargs)
        self.dither()


        self.highlight_region(self.regions[morty_path[0]])
        for last, next in zip(morty_path, morty_path[1:]):
            self.play(WalkPiCreature(morty, self.dual_points[next]),**kwargs)
            self.highlight_region(self.regions[next])
        self.dither()
        for last, next in zip(randy_path, randy_path[1:]):
            line = Line(self.points[last], self.points[next])
            line.highlight("yellow")
            self.play(
                WalkPiCreature(randy, self.points[next]),
                ShowCreation(line),
                **kwargs
            )
        self.dither()
        self.play(*[
            ApplyMethod(
                line.rotate_in_place, 
                np.pi/10, 
                rate_func = wiggle) 
            for line in morty_crossed_lines
        ], **kwargs)
        
        self.dither()
 def construct(self):
     GraphScene.construct(self)
     self.generate_dual_graph()
     self.add(TextMobject("Dual Graph").to_edge(UP).shift(2 * LEFT))
     self.play(*[
         ShowCreation(mob) for mob in self.dual_edges + self.dual_vertices
     ])
     self.wait()
 def construct(self):
     GraphScene.construct(self)
     self.generate_dual_graph()
     self.add(TextMobject("Dual Graph").to_edge(UP).shift(2*LEFT))
     self.play(*[
         ShowCreation(mob)
         for mob in self.dual_edges + self.dual_vertices
     ])
     self.dither()
 def construct(self):
     GraphScene.construct(self)
     randy = Randolph().move_to((-3, 0, 0))
     name = TextMobject("Randolph")
     self.play(Transform(randy, deepcopy(randy).scale(RANDOLPH_SCALE_VAL).move_to(self.points[0])))
     self.dither()
     name.shift((0, 1, 0))
     self.add(name)
     self.dither()
 def construct(self):
     GraphScene.construct(self)
     point_path = [self.points[i] for i in self.path]
     randy = Randolph()
     randy.scale(RANDOLPH_SCALE_VAL)
     randy.move_to(point_path[0])
     for next, last in zip(point_path[1:], point_path):
         self.play(WalkPiCreature(randy, next), ShowCreation(Line(last, next).highlight("yellow")), run_time=2.0)
     self.randy = randy
    def construct(self):
        GraphScene.construct(self)
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_VAL).move_to(self.points[0])
        dollar_signs = TextMobject("\\$\\$")
        dollar_signs.scale(EDGE_ANNOTATION_SCALE_VAL)
        dollar_signs = Mobject(*[
            deepcopy(dollar_signs).shift(edge.get_center())
            for edge in self.edges
        ])
        unneeded = TextMobject("unneeded!")
        unneeded.scale(EDGE_ANNOTATION_SCALE_VAL)
        self.generate_spanning_tree()
        def green_dot_at_index(index):
            return Dot(
                self.points[index], 
                radius = 2*Dot.DEFAULT_RADIUS,
                color = "lightgreen",
            )
        def out_of_spanning_set(point_pair):
            stip = self.spanning_tree_index_pairs
            return point_pair not in stip and \
                   tuple(reversed(point_pair)) not in stip
        
        self.add(randy)
        self.accent_vertices(run_time = 2.0)
        self.add(dollar_signs)
        self.dither(2)
        self.remove(dollar_signs)
        run_time_per_branch = 0.5        
        self.play(
            ShowCreation(green_dot_at_index(0)),
            run_time = run_time_per_branch
        )
        for pair in self.spanning_tree_index_pairs:
            self.play(ShowCreation(
                Line(
                    self.points[pair[0]], 
                    self.points[pair[1]]
                ).highlight("yellow"),
                run_time = run_time_per_branch
            ))
            self.play(ShowCreation(
                green_dot_at_index(pair[1]),
                run_time = run_time_per_branch
            ))
        self.dither(2)

        unneeded_edges = filter(out_of_spanning_set, self.graph.edges)
        for edge, limit in zip(unneeded_edges, range(5)):
            line = Line(self.points[edge[0]], self.points[edge[1]])
            line.highlight("red")
            self.play(ShowCreation(line, run_time = 1.0))
            self.add(unneeded.center().shift(line.get_center() + 0.2*UP))
            self.dither()
            self.remove(line, unneeded)
    def construct(self):
        GraphScene.construct(self)
        self.generate_spanning_tree()
        self.generate_dual_graph()
        self.generate_regions()
        randy = Randolph().scale(RANDOLPH_SCALE_VAL)
        morty = Mortimer().scale(RANDOLPH_SCALE_VAL)
        randy.move_to(self.points[0])
        morty.move_to(self.dual_points[0])
        attempted_dual_point_index = 2
        region_ordering = [0, 1, 7, 2, 3, 5, 4, 6]
        dual_edges = [1, 3, 4, 7, 11, 9, 13]
        time_per_dual_edge = 0.5

        self.add(randy, morty)
        self.play(ShowCreation(self.spanning_tree))
        self.dither()
        self.play(
            WalkPiCreature(
                morty,
                self.dual_points[attempted_dual_point_index],
                alpha_func=lambda t: 0.3 * there_and_back(t),
                run_time=2.0,
            )
        )
        self.dither()
        for index in range(len(self.regions)):
            # if index > 0:
            #     edge = self.edges[dual_edges[index-1]]
            #     midpoint = edge.get_center()
            #     self.play(*[
            #         ShowCreation(Line(
            #             midpoint,
            #             tip
            #         ).highlight("red"))
            #         for tip in edge.start, edge.end
            #     ], run_time = time_per_dual_edge)
            self.highlight_region(self.regions[region_ordering[index]])
            self.dither(time_per_dual_edge)
        self.dither()

        cycle_index = region_ordering[-1]
        cycle = self.graph.region_cycles[cycle_index]
        self.highlight_region(self.regions[cycle_index], "black")
        self.play(
            ShowCreation(
                Mobject(
                    *[
                        Line(self.points[last], self.points[next]).highlight("green")
                        for last, next in zip(cycle, list(cycle)[1:] + [cycle[0]])
                    ]
                )
            )
        )
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        tweaked_graph = deepcopy(self.graph)
        for index in 2, 4:
            tweaked_graph.vertices[index] += 2.8*RIGHT + 1.8*DOWN
        tweaked_self = GraphScene(tweaked_graph)
        edges_to_remove = [
            self.edges[self.graph.edges.index(pair)]
            for pair in [(4, 5), (0, 5), (1, 5), (7, 1), (8, 3)]
        ]

        connected, planar, graph = TextMobject([
            "Connected ", "Planar ", "Graph"
        ]).to_edge(UP).split()
        not_okay = TextMobject("Not Okay").highlight("red")
        planar_explanation = TextMobject("""
            (``Planar'' just means we can draw it without
             intersecting lines)
        """, size = "\\small")
        planar_explanation.shift(planar.get_center() + 0.5*DOWN)

        self.draw_vertices()
        self.draw_edges()
        self.clear()
        self.add(*self.vertices + self.edges)
        self.dither()
        self.add(graph)
        self.dither()
        kwargs = {
            "rate_func" : there_and_back,
            "run_time"   : 5.0
        }
        self.add(not_okay)
        self.play(*[
            Transform(*pair, **kwargs)
            for pair in zip(
                self.edges + self.vertices, 
                tweaked_self.edges + tweaked_self.vertices,
            )
        ])
        self.remove(not_okay)
        self.add(planar, planar_explanation)
        self.dither(2)
        self.remove(planar_explanation)
        self.add(not_okay)
        self.remove(*edges_to_remove)
        self.play(ShowCreation(
            Mobject(*edges_to_remove),
            rate_func = lambda t : 1 - t,
            run_time = 1.0
        ))
        self.dither(2)
        self.remove(not_okay)
        self.add(connected, *edges_to_remove)
        self.dither()
 def construct(self):
     GraphScene.construct(self)
     point_path = [self.points[i] for i in self.path]
     randy = Randolph()
     randy.scale(RANDOLPH_SCALE_FACTOR)
     randy.move_to(point_path[0])
     for next, last in zip(point_path[1:], point_path):
         self.play(WalkPiCreature(randy, next),
                   ShowCreation(Line(last, next).highlight("yellow")),
                   run_time=2.0)
     self.randy = randy
    def construct(self):
        GraphScene.construct(self)
        self.generate_spanning_tree()
        self.generate_dual_graph()
        self.generate_regions()
        randy = Randolph().scale(RANDOLPH_SCALE_FACTOR)
        morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR)
        randy.move_to(self.points[0])
        morty.move_to(self.dual_points[0])
        attempted_dual_point_index = 2
        region_ordering = [0, 1, 7, 2, 3, 5, 4, 6]
        dual_edges = [1, 3, 4, 7, 11, 9, 13]
        time_per_dual_edge = 0.5

        self.add(randy, morty)
        self.play(ShowCreation(self.spanning_tree))
        self.wait()
        self.play(
            WalkPiCreature(
                morty,
                self.dual_points[attempted_dual_point_index],
                rate_func=lambda t: 0.3 * there_and_back(t),
                run_time=2.0,
            ))
        self.wait()
        for index in range(len(self.regions)):
            # if index > 0:
            #     edge = self.edges[dual_edges[index-1]]
            #     midpoint = edge.get_center()
            #     self.play(*[
            #         ShowCreation(Line(
            #             midpoint,
            #             tip
            #         ).highlight("red"))
            #         for tip in edge.start, edge.end
            #     ], run_time = time_per_dual_edge)
            self.highlight_region(self.regions[region_ordering[index]])
            self.wait(time_per_dual_edge)
        self.wait()

        cycle_index = region_ordering[-1]
        cycle = self.graph.region_cycles[cycle_index]
        self.highlight_region(self.regions[cycle_index], "black")
        self.play(
            ShowCreation(
                Mobject(*[
                    Line(self.points[last], self.points[next]).highlight(
                        "green")
                    for last, next in zip(cycle,
                                          list(cycle)[1:] + [cycle[0]])
                ])))
        self.wait()
    def construct(self):
        GraphScene.construct(self)
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_FACTOR).move_to(self.points[0])
        dollar_signs = TextMobject("\\$\\$")
        dollar_signs.scale(EDGE_ANNOTATION_SCALE_FACTOR)
        dollar_signs = Mobject(*[
            deepcopy(dollar_signs).shift(edge.get_center())
            for edge in self.edges
        ])
        unneeded = TextMobject("unneeded!")
        unneeded.scale(EDGE_ANNOTATION_SCALE_FACTOR)
        self.generate_spanning_tree()

        def green_dot_at_index(index):
            return Dot(
                self.points[index],
                radius=2 * Dot.DEFAULT_RADIUS,
                color="lightgreen",
            )

        def out_of_spanning_set(point_pair):
            stip = self.spanning_tree_index_pairs
            return point_pair not in stip and \
                   tuple(reversed(point_pair)) not in stip

        self.add(randy)
        self.accent_vertices(run_time=2.0)
        self.add(dollar_signs)
        self.wait(2)
        self.remove(dollar_signs)
        run_time_per_branch = 0.5
        self.play(ShowCreation(green_dot_at_index(0)),
                  run_time=run_time_per_branch)
        for pair in self.spanning_tree_index_pairs:
            self.play(
                ShowCreation(Line(self.points[pair[0]],
                                  self.points[pair[1]]).highlight("yellow"),
                             run_time=run_time_per_branch))
            self.play(
                ShowCreation(green_dot_at_index(pair[1]),
                             run_time=run_time_per_branch))
        self.wait(2)

        unneeded_edges = filter(out_of_spanning_set, self.graph.edges)
        for edge, limit in zip(unneeded_edges, range(5)):
            line = Line(self.points[edge[0]], self.points[edge[1]])
            line.highlight("red")
            self.play(ShowCreation(line, run_time=1.0))
            self.add(unneeded.center().shift(line.get_center() + 0.2 * UP))
            self.wait()
            self.remove(line, unneeded)
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        self.generate_regions()
        randy = Randolph().shift(LEFT)
        morty = Mortimer().shift(RIGHT)
        name = TextMobject("Mortimer")
        name.shift(morty.get_center() + 1.2 * UP)
        randy_path = (0, 1, 3)
        morty_path = (-2, -3, -4)
        morty_crossed_lines = [
            Line(self.points[i], self.points[j]).highlight("red")
            for i, j in [(7, 1), (1, 5)]
        ]
        kwargs = {"run_time": 1.0}

        self.clear()
        self.add(randy)
        self.wait()
        self.add(morty, name)
        self.wait()
        self.remove(name)
        small_randy = deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR)
        small_morty = deepcopy(morty).scale(RANDOLPH_SCALE_FACTOR)
        small_randy.move_to(self.points[randy_path[0]])
        small_morty.move_to(self.dual_points[morty_path[0]])
        self.play(
            *[FadeIn(mob) for mob in self.vertices + self.edges] + [
                Transform(randy, small_randy),
                Transform(morty, small_morty),
            ], **kwargs)
        self.wait()

        self.highlight_region(self.regions[morty_path[0]])
        for last, next in zip(morty_path, morty_path[1:]):
            self.play(WalkPiCreature(morty, self.dual_points[next]), **kwargs)
            self.highlight_region(self.regions[next])
        self.wait()
        for last, next in zip(randy_path, randy_path[1:]):
            line = Line(self.points[last], self.points[next])
            line.highlight("yellow")
            self.play(WalkPiCreature(randy, self.points[next]),
                      ShowCreation(line), **kwargs)
        self.wait()
        self.play(
            *[
                ApplyMethod(line.rotate_in_place, np.pi / 10, rate_func=wiggle)
                for line in morty_crossed_lines
            ], **kwargs)

        self.wait()
    def construct(self):
        GraphScene.construct(self)
        tweaked_graph = deepcopy(self.graph)
        for index in 2, 4:
            tweaked_graph.vertices[index] += 2.8 * RIGHT + 1.8 * DOWN
        tweaked_self = GraphScene(tweaked_graph)
        edges_to_remove = [
            self.edges[self.graph.edges.index(pair)]
            for pair in [(4, 5), (0, 5), (1, 5), (7, 1), (8, 3)]
        ]

        connected, planar, graph = TextMobject(
            ["Connected ", "Planar ", "Graph"]).to_edge(UP).split()
        not_okay = TextMobject("Not Okay").highlight("red")
        planar_explanation = TextMobject("""
            (``Planar'' just means we can draw it without
             intersecting lines)
        """,
                                         size="\\small")
        planar_explanation.shift(planar.get_center() + 0.5 * DOWN)

        self.draw_vertices()
        self.draw_edges()
        self.clear()
        self.add(*self.vertices + self.edges)
        self.wait()
        self.add(graph)
        self.wait()
        kwargs = {"rate_func": there_and_back, "run_time": 5.0}
        self.add(not_okay)
        self.play(*[
            Transform(*pair, **kwargs) for pair in zip(
                self.edges + self.vertices,
                tweaked_self.edges + tweaked_self.vertices,
            )
        ])
        self.remove(not_okay)
        self.add(planar, planar_explanation)
        self.wait(2)
        self.remove(planar_explanation)
        self.add(not_okay)
        self.remove(*edges_to_remove)
        self.play(
            ShowCreation(Mobject(*edges_to_remove),
                         rate_func=lambda t: 1 - t,
                         run_time=1.0))
        self.wait(2)
        self.remove(not_okay)
        self.add(connected, *edges_to_remove)
        self.wait()
    def construct(self):
        buff = 0.5
        self.graph.vertices = map(
            lambda v : v + DOWN + RIGHT,
            self.graph.vertices
        )
        GraphScene.construct(self)
        self.generate_regions()
        objects, notions = Mobject(*TextMobject(
            ["Objects \\quad\\quad ", "Thing that connects objects"]
        )).to_corner().shift(0.5*RIGHT).split()
        horizontal_line = Line(
            (-SPACE_WIDTH, SPACE_HEIGHT-1, 0),
            (max(notions.points[:,0]), SPACE_HEIGHT-1, 0)
        )
        vert_line_x_val = min(notions.points[:,0]) - buff
        vertical_line = Line(
            (vert_line_x_val, SPACE_HEIGHT, 0),
            (vert_line_x_val,-SPACE_HEIGHT, 0)
        )
        objects_and_notions = [
            ("Facebook accounts", "Friendship"),
            ("English Words", "Differ by One Letter"),
            ("Mathematicians", "Coauthorship"),
            ("Neurons", "Synapses"),
            (
                "Regions our graph \\\\ cuts the plane into",
                "Shared edges"
            )
        ]


        self.clear()
        self.add(objects, notions, horizontal_line, vertical_line)
        for (obj, notion), height in zip(objects_and_notions, it.count(2, -1)):
            obj_mob = TextMobject(obj, size = "\\small").to_edge(LEFT)
            not_mob = TextMobject(notion, size = "\\small").to_edge(LEFT)
            not_mob.shift((vert_line_x_val + SPACE_WIDTH)*RIGHT)
            obj_mob.shift(height*UP)
            not_mob.shift(height*UP)

            if obj.startswith("Regions"):
                self.handle_dual_graph(obj_mob, not_mob)
            elif obj.startswith("English"):
                self.handle_english_words(obj_mob, not_mob)
            else:
                self.add(obj_mob)
                self.dither()
                self.add(not_mob)
                self.dither()
 def construct(self):
     GraphScene.construct(self)
     randy = Randolph().move_to((-3, 0, 0))
     name = TextMobject("Randolph")
     self.play(
         Transform(
             randy,
             deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR).move_to(
                 self.points[0]),
         ))
     self.wait()
     name.shift((0, 1, 0))
     self.add(name)
     self.wait()
 def construct(self):
     GraphScene.construct(self)
     self.draw_vertices()
     self.draw_edges()
     self.dither()
     self.clear()
     self.add(*self.edges)
     self.replace_vertices_with(Face().scale(0.4))
     friends = TextMobject("Friends").scale(EDGE_ANNOTATION_SCALE_VAL)
     self.annotate_edges(friends.shift((0, friends.get_height() / 2, 0)))
     self.play(
         *[CounterclockwiseTransform(vertex, Dot(point)) for vertex, point in zip(self.vertices, self.points)]
         + [Transform(ann, line) for ann, line in zip(self.edge_annotations, self.edges)]
     )
     self.dither()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        dual_cycle = DUAL_CYCLE
        trapped_points = [0, 1]
        morty = Mortimer().scale(RANDOLPH_SCALE_VAL)
        morty.move_to(self.dual_points[dual_cycle[0]])
        time_per_edge = 0.5
        text = TextMobject("""
            One of these lines must be included
            in the spanning tree if those two inner
            vertices are to be reached.
        """).scale(0.7).to_edge(UP)

        all_lines = []
        matching_edges = []
        kwargs = {"run_time" : time_per_edge, "rate_func" : None}
        for last, next in zip(dual_cycle, dual_cycle[1:]):
            line = Line(self.dual_points[last], self.dual_points[next])
            line.highlight("red")
            self.play(
                WalkPiCreature(morty, self.dual_points[next], **kwargs),
                ShowCreation(line, **kwargs),
            )
            all_lines.append(line)
            center = line.get_center()
            distances = map(
                lambda e : np.linalg.norm(center - e.get_center()),
                self.edges
            )
            matching_edges.append(
                self.edges[distances.index(min(distances))]
            )
        self.play(*[
            Transform(v, Dot(
                v.get_center(), 
                radius = 3*Dot.DEFAULT_RADIUS,
                color = "green"
            ))
            for v in np.array(self.vertices)[trapped_points]
        ])
        self.add(text)
        self.play(*[
            Transform(line, deepcopy(edge).highlight(line.get_color()))
            for line, edge in zip(all_lines, matching_edges)
        ])
        self.dither()
 def construct(self):
     GraphScene.construct(self)
     self.generate_dual_graph()
     self.remove(*self.vertices)
     self.add(*self.dual_edges)
     self.dither()
     self.play(*[Transform(*pair, run_time=2.0) for pair in zip(self.dual_edges, self.edges)])
     self.dither()
     self.add(
         TextMobject(
             """
             (Or at least I would argue they should \\\\
             be thought of as the same thing.)
         """,
             size="\\small",
         ).to_edge(UP)
     )
     self.dither()
 def construct(self):
     GraphScene.construct(self)
     self.draw_vertices()
     self.draw_edges()
     self.wait()
     self.clear()
     self.add(*self.edges)
     self.replace_vertices_with(Face().scale(0.4))
     friends = TextMobject("Friends").scale(EDGE_ANNOTATION_SCALE_FACTOR)
     self.annotate_edges(friends.shift((0, friends.get_height() / 2, 0)))
     self.play(*[
         CounterclockwiseTransform(vertex, Dot(point))
         for vertex, point in zip(self.vertices, self.points)
     ] + [
         Transform(ann, line)
         for ann, line in zip(self.edge_annotations, self.edges)
     ])
     self.wait()
 def construct(self):
     GraphScene.construct(self)
     self.generate_dual_graph()
     self.remove(*self.vertices)
     self.add(*self.dual_edges)
     self.wait()
     self.play(*[
         Transform(*pair, run_time=2.0)
         for pair in zip(self.dual_edges, self.edges)
     ])
     self.wait()
     self.add(
         TextMobject("""
             (Or at least I would argue they should \\\\
             be thought of as the same thing.)
         """,
                     size="\\small").to_edge(UP))
     self.wait()
    def construct(self):
        GraphScene.construct(self)
        terms = cycles, spanning_trees, dual_graphs = [
            TextMobject(phrase).shift(y * UP).to_edge() for phrase, y in [
                ("Cycles", 3),
                ("Spanning Trees", 1),
                ("Dual Graphs", -1),
            ]
        ]
        self.generate_spanning_tree()
        scale_factor = 1.2

        def accent(mobject, color="yellow"):
            return mobject.scale_in_place(scale_factor).highlight(color)

        def tone_down(mobject):
            return mobject.scale_in_place(1.0 /
                                          scale_factor).highlight("white")

        self.add(accent(cycles))
        self.trace_cycle(run_time=1.0)
        self.wait()
        tone_down(cycles)
        self.remove(self.traced_cycle)

        self.add(accent(spanning_trees))
        self.play(ShowCreation(self.spanning_tree), run_time=1.0)
        self.wait()
        tone_down(spanning_trees)
        self.remove(self.spanning_tree)

        self.add(accent(dual_graphs, "red"))
        self.generate_dual_graph()
        for mob in self.mobjects:
            mob.fade
        self.play(*[
            ShowCreation(mob, run_time=1.0)
            for mob in self.dual_vertices + self.dual_edges
        ])
        self.wait()

        self.clear()
        self.play(ApplyMethod(Mobject(*terms).center))
        self.wait()
    def construct(self):
        GraphScene.construct(self)
        terms = cycles, spanning_trees, dual_graphs = [
            TextMobject(phrase).shift(y*UP).to_edge()
            for phrase, y in [
                ("Cycles", 3),
                ("Spanning Trees", 1),
                ("Dual Graphs", -1),
            ]
        ]
        self.generate_spanning_tree()
        scale_val = 1.2       
        def accent(mobject, color = "yellow"):
            return mobject.scale_in_place(scale_val).highlight(color)
        def tone_down(mobject):
            return mobject.scale_in_place(1.0/scale_val).highlight("white")

        self.add(accent(cycles))
        self.trace_cycle(run_time = 1.0)
        self.dither()
        tone_down(cycles)
        self.remove(self.traced_cycle)

        self.add(accent(spanning_trees))
        self.play(ShowCreation(self.spanning_tree), run_time = 1.0)
        self.dither()
        tone_down(spanning_trees)
        self.remove(self.spanning_tree)

        self.add(accent(dual_graphs, "red"))
        self.generate_dual_graph()
        for mob in self.mobjects:
            mob.fade
        self.play(*[
            ShowCreation(mob, run_time = 1.0)
            for mob in self.dual_vertices + self.dual_edges
        ])
        self.dither()

        self.clear()
        self.play(ApplyMethod(
            Mobject(*terms).center
        ))
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        dual_cycle = DUAL_CYCLE
        trapped_points = [0, 1]
        morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR)
        morty.move_to(self.dual_points[dual_cycle[0]])
        time_per_edge = 0.5
        text = TextMobject("""
            One of these lines must be included
            in the spanning tree if those two inner
            vertices are to be reached.
        """).scale(0.7).to_edge(UP)

        all_lines = []
        matching_edges = []
        kwargs = {"run_time": time_per_edge, "rate_func": None}
        for last, next in zip(dual_cycle, dual_cycle[1:]):
            line = Line(self.dual_points[last], self.dual_points[next])
            line.highlight("red")
            self.play(
                WalkPiCreature(morty, self.dual_points[next], **kwargs),
                ShowCreation(line, **kwargs),
            )
            all_lines.append(line)
            center = line.get_center()
            distances = map(lambda e: np.linalg.norm(center - e.get_center()),
                            self.edges)
            matching_edges.append(self.edges[distances.index(min(distances))])
        self.play(*[
            Transform(
                v,
                Dot(v.get_center(),
                    radius=3 * Dot.DEFAULT_RADIUS,
                    color="green"))
            for v in np.array(self.vertices)[trapped_points]
        ])
        self.add(text)
        self.play(*[
            Transform(line,
                      deepcopy(edge).highlight(line.get_color()))
            for line, edge in zip(all_lines, matching_edges)
        ])
        self.wait()
    def construct(self):
        GraphScene.construct(self)
        self.generate_regions()
        self.generate_dual_graph()
        cycle = [4, 2, 1, 5, 4]
        enclosed_regions = [0, 2, 3, 4]
        dual_cycle = DUAL_CYCLE
        enclosed_vertices = [0, 1]
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_VAL)
        randy.move_to(self.points[cycle[0]])

        lines_to_remove = []
        for last, next in zip(cycle, cycle[1:]):
            line = Line(self.points[last], self.points[next])
            line.highlight("yellow")
            self.play(
                ShowCreation(line),
                WalkPiCreature(randy, self.points[next]),
                run_time = 1.0
            )
            lines_to_remove.append(line)
        self.dither()
        self.remove(randy, *lines_to_remove)
        for region in np.array(self.regions)[enclosed_regions]:
            self.highlight_region(region)
        self.dither(2)
        self.reset_background()
        lines = Mobject(*[
            Line(self.dual_points[last], self.dual_points[next])
            for last, next in zip(dual_cycle, dual_cycle[1:])
        ]).highlight("red")
        self.play(ShowCreation(lines))
        self.play(*[
            Transform(v, Dot(
                v.get_center(), 
                radius = 3*Dot.DEFAULT_RADIUS
            ).highlight("green"))
            for v in np.array(self.vertices)[enclosed_vertices]
        ] + [
            ApplyMethod(self.edges[0].highlight, "green")
        ])
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        paths = [
            (1, 2, 4, 5, 6),
            (6, 7, 1, 3),
        ]
        non_paths = [
            [
                (0, 1),
                (7, 8),
                (5, 6),
            ],
            [(5, 0), (0, 2), (0, 1)],
        ]
        valid_path = TextMobject("Valid \\\\ Path").highlight("green")
        not_a_path = TextMobject("Not a \\\\ Path").highlight("red")
        for mob in valid_path, not_a_path:
            mob.to_edge(UP)
        kwargs = {"run_time": 1.0}
        for path, non_path in zip(paths, non_paths):
            path_lines = Mobject(*[
                Line(self.points[path[i]], self.points[path[i + 1]]).highlight(
                    "yellow") for i in range(len(path) - 1)
            ])
            non_path_lines = Mobject(*[
                Line(
                    self.points[pp[0]],
                    self.points[pp[1]],
                ).highlight("yellow") for pp in non_path
            ])

            self.remove(not_a_path)
            self.add(valid_path)
            self.play(ShowCreation(path_lines, **kwargs))
            self.wait(2)
            self.remove(path_lines)

            self.remove(valid_path)
            self.add(not_a_path)
            self.play(ShowCreation(non_path_lines, **kwargs))
            self.wait(2)
            self.remove(non_path_lines)
    def construct(self):
        GraphScene.construct(self)
        rot_kwargs = {"radians": np.pi / 3, "run_time": 5.0}
        vertices = [
            point / 2 + OUT if abs(point[0]) == 2 else point + IN
            for point in self.points
        ]
        cube = Mobject(*[
            Line(vertices[edge[0]], vertices[edge[1]])
            for edge in self.graph.edges
        ])
        cube.rotate(-np.pi / 3, [0, 0, 1])
        cube.rotate(-np.pi / 3, [0, 1, 0])
        dots_to_vertices = TextMobject("Dots $\\to$ Vertices").to_corner()
        lines_to_edges = TextMobject("Lines $\\to$ Edges").to_corner()
        regions_to_faces = TextMobject("Regions $\\to$ Faces").to_corner()

        self.clear()
        # self.play(TransformAnimations(
        #     Rotating(Dodecahedron(), **rot_kwargs),
        #     Rotating(cube, **rot_kwargs)
        # ))
        self.play(Rotating(cube, **rot_kwargs))
        self.clear()
        self.play(
            *[Transform(l1, l2) for l1, l2 in zip(cube.split(), self.edges)])
        self.wait()
        self.add(dots_to_vertices)
        self.play(*[ShowCreation(dot, run_time=1.0) for dot in self.vertices])
        self.wait(2)
        self.remove(dots_to_vertices, *self.vertices)
        self.add(lines_to_edges)
        self.play(ApplyMethod(Mobject(*self.edges).highlight, "yellow"))
        self.wait(2)
        self.clear()
        self.add(*self.edges)
        self.add(regions_to_faces)
        self.generate_regions()
        for region in self.regions:
            self.highlight_region(region)
        self.wait(3.0)
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        self.generate_spanning_tree()
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_VAL)
        randy.move_to(self.points[0])
        morty = Mortimer()
        morty.scale(RANDOLPH_SCALE_VAL)
        morty.move_to(self.dual_points[0])
        dual_edges = [1, 3, 4, 7, 11, 9, 13]
        words = TextMobject(
            """
            The red edges form a spanning tree of the dual graph!
        """
        ).to_edge(UP)

        self.add(self.spanning_tree, randy, morty)
        self.play(ShowCreation(Mobject(*np.array(self.edges)[dual_edges]).highlight("red")))
        self.add(words)
        self.dither()
    def construct(self):
        GraphScene.construct(self)
        self.generate_dual_graph()
        self.generate_spanning_tree()
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_FACTOR)
        randy.move_to(self.points[0])
        morty = Mortimer()
        morty.scale(RANDOLPH_SCALE_FACTOR)
        morty.move_to(self.dual_points[0])
        dual_edges = [1, 3, 4, 7, 11, 9, 13]
        words = TextMobject("""
            The red edges form a spanning tree of the dual graph!
        """).to_edge(UP)

        self.add(self.spanning_tree, randy, morty)
        self.play(
            ShowCreation(
                Mobject(*np.array(self.edges)[dual_edges]).highlight("red")))
        self.add(words)
        self.wait()
    def construct(self):
        buff = 0.5
        self.graph.vertices = map(lambda v: v + DOWN + RIGHT,
                                  self.graph.vertices)
        GraphScene.construct(self)
        self.generate_regions()
        objects, notions = Mobject(*TextMobject([
            "Objects \\quad\\quad ", "Thing that connects objects"
        ])).to_corner().shift(0.5 * RIGHT).split()
        horizontal_line = Line(
            (-SPACE_WIDTH, SPACE_HEIGHT - 1, 0),
            (max(notions.points[:, 0]), SPACE_HEIGHT - 1, 0))
        vert_line_x_val = min(notions.points[:, 0]) - buff
        vertical_line = Line((vert_line_x_val, SPACE_HEIGHT, 0),
                             (vert_line_x_val, -SPACE_HEIGHT, 0))
        objects_and_notions = [("Facebook accounts", "Friendship"),
                               ("English Words", "Differ by One Letter"),
                               ("Mathematicians", "Coauthorship"),
                               ("Neurons", "Synapses"),
                               ("Regions our graph \\\\ cuts the plane into",
                                "Shared edges")]

        self.clear()
        self.add(objects, notions, horizontal_line, vertical_line)
        for (obj, notion), height in zip(objects_and_notions, it.count(2, -1)):
            obj_mob = TextMobject(obj, size="\\small").to_edge(LEFT)
            not_mob = TextMobject(notion, size="\\small").to_edge(LEFT)
            not_mob.shift((vert_line_x_val + SPACE_WIDTH) * RIGHT)
            obj_mob.shift(height * UP)
            not_mob.shift(height * UP)

            if obj.startswith("Regions"):
                self.handle_dual_graph(obj_mob, not_mob)
            elif obj.startswith("English"):
                self.handle_english_words(obj_mob, not_mob)
            else:
                self.add(obj_mob)
                self.wait()
                self.add(not_mob)
                self.wait()
    def construct(self):
        GraphScene.construct(self)
        self.generate_regions()
        self.generate_dual_graph()
        region_mobs = [
            ImageMobject(disp.paint_region(reg, self.background), invert=False)
            for reg in self.regions
        ]
        for region, mob in zip(self.regions, region_mobs):
            self.highlight_region(region, mob.get_color())
        outer_region = self.regions.pop()
        outer_region_mob = region_mobs.pop()
        outer_dual_vertex = self.dual_vertices.pop()
        internal_edges = filter(
            lambda e : abs(e.start[0]) < SPACE_WIDTH and \
                       abs(e.end[0]) < SPACE_WIDTH and \
                       abs(e.start[1]) < SPACE_HEIGHT and \
                       abs(e.end[1]) < SPACE_HEIGHT,
            self.dual_edges
        )
        external_edges = filter(lambda e: e not in internal_edges,
                                self.dual_edges)

        self.wait()
        self.reset_background()
        self.highlight_region(outer_region, outer_region_mob.get_color())
        self.play(*[
            Transform(reg_mob, dot)
            for reg_mob, dot in zip(region_mobs, self.dual_vertices)
        ])
        self.wait()
        self.reset_background()
        self.play(
            ApplyFunction(
                lambda p: (SPACE_WIDTH + SPACE_HEIGHT) * p / np.linalg.norm(p),
                outer_region_mob))
        self.wait()
        for edges in internal_edges, external_edges:
            self.play(*[ShowCreation(edge, run_time=2.0) for edge in edges])
            self.wait()
    def construct(self):
        GraphScene.construct(self)
        self.generate_regions()
        self.generate_dual_graph()
        cycle = [4, 2, 1, 5, 4]
        enclosed_regions = [0, 2, 3, 4]
        dual_cycle = DUAL_CYCLE
        enclosed_vertices = [0, 1]
        randy = Randolph()
        randy.scale(RANDOLPH_SCALE_FACTOR)
        randy.move_to(self.points[cycle[0]])

        lines_to_remove = []
        for last, next in zip(cycle, cycle[1:]):
            line = Line(self.points[last], self.points[next])
            line.highlight("yellow")
            self.play(ShowCreation(line),
                      WalkPiCreature(randy, self.points[next]),
                      run_time=1.0)
            lines_to_remove.append(line)
        self.wait()
        self.remove(randy, *lines_to_remove)
        for region in np.array(self.regions)[enclosed_regions]:
            self.highlight_region(region)
        self.wait(2)
        self.reset_background()
        lines = Mobject(*[
            Line(self.dual_points[last], self.dual_points[next])
            for last, next in zip(dual_cycle, dual_cycle[1:])
        ]).highlight("red")
        self.play(ShowCreation(lines))
        self.play(*[
            Transform(
                v,
                Dot(v.get_center(), radius=3 *
                    Dot.DEFAULT_RADIUS).highlight("green"))
            for v in np.array(self.vertices)[enclosed_vertices]
        ] + [ApplyMethod(self.edges[0].highlight, "green")])
        self.wait()