6.24生成树形结构
完整代码:
from manim import *
class LargeTreeGeneration01(MovingCameraScene):
DEPTH = 3
CHILDREN_PER_VERTEX = 2
LAYOUT_CONFIG = {"vertex_spacing": (0.8, 1)}
VERTEX_CONF = {"radius": 0.12, "color": BLUE_B, "fill_opacity": 1}
def expand_vertex(self, g, vertex_id: str, depth: int):
new_vertices = [f"{vertex_id}/{i}" for i in range(self.CHILDREN_PER_VERTEX)]
new_edges = [(vertex_id, child_id) for child_id in new_vertices]
g.add_edges(
*new_edges,
vertex_config=self.VERTEX_CONF,
positions={
k: g.vertices[vertex_id].get_center() + 0.1 * DOWN for k in new_vertices
},
)
if depth < self.DEPTH:
for child_id in new_vertices:
self.expand_vertex(g, child_id, depth + 1)
return g
def construct(self):
# 创建一个空的图,只有一个根节点
g = Graph(["ROOT"], [], vertex_config=self.VERTEX_CONF)
# 扩展树结构
g = self.expand_vertex(g, "ROOT", 1)
self.add(g)
# 改变布局,并添加动画
self.play(
g.animate.change_layout(
"tree",
root_vertex="ROOT",
layout_config=self.LAYOUT_CONFIG,
)
)
# 自动缩放相机以适应图形
self.play(self.camera.auto_zoom(g, margin=1), run_time=0.5)
接下来解释一下代码吧:
1. 导入所需库
from manim import *
这一行导入了 Manim 库的所有内容。Manim 是一个用于制作数学动画的 Python 库。
2. 创建类
class LargeTreeGeneration(MovingCameraScene):
这个类 LargeTreeGeneration
继承自 MovingCameraScene
。MovingCameraScene
提供了相应的方法来控制相机的移动和缩放,适合用于制作动态场景。
3. 定义类属性
DEPTH = 4 CHILDREN_PER_VERTEX = 3 LAYOUT_CONFIG = {"vertex_spacing": (0.5, 1)} VERTEX_CONF = {"radius": 0.12, "color": BLUE_B, "fill_opacity": 1}
DEPTH
:指树的深度(最深的层级)。CHILDREN_PER_VERTEX
:每个节点的子节点数量。LAYOUT_CONFIG
:布局配置,控制节点间的垂直和水平间距。VERTEX_CONF
:节点的属性配置,包括半径、颜色和填充透明度。
4. 扩展树的节点
def expand_vertex(self, g, vertex_id: str, depth: int):
这个方法用于递归地生成树的节点和连接。
g
:当前的图形对象。vertex_id
:当前节点的 ID。depth
:当前的深度,始于1。
方法内的逻辑:
new_vertices = [f"{vertex_id}/{i}" for i in range(self.CHILDREN_PER_VERTEX)]
根据当前节点 ID 生成子节点的 ID。
new_edges = [(vertex_id, child_id) for child_id in new_vertices]
创建从当前节点到所有子节点的边。
g.add_edges( *new_edges, vertex_config=self.VERTEX_CONF, positions={ k: g.vertices[vertex_id].get_center() + 0.1 * DOWN for k in new_vertices }, )
将这些边添加到图形对象中,同时为新节点设置位置和配置。
if depth < self.DEPTH: for child_id in new_vertices: self.expand_vertex(g, child_id, depth + 1)
如果当前深度小于指定的深度,则递归调用自己,为每个新子节点继续生成其子节点。
5. 构建场景
def construct(self):
这是 Manim 中用来生成场景的主要方法。
g = Graph(["ROOT"], [], vertex_config=self.VERTEX_CONF)
创建一个图形,从一个根节点("ROOT")开始,且没有初始边。
g = self.expand_vertex(g, "ROOT", 1)
调用 expand_vertex
方法,扩展整个图形。
self.add(g)
将图形添加到当前场景中。
6. 动画效果
self.play( g.animate.change_layout( "tree", root_vertex="ROOT", layout_config=self.LAYOUT_CONFIG, ) )
使用 g.animate.change_layout
方法将图形转换为树形布局,并在动画中展示。
self.play(self.camera.auto_zoom(g, margin=1), run_time=0.5)
将相机自动缩放,以适应整个图形,并在0.5秒内完成该动作。
7. 运行代码
最后一行
%manim -qm -v WARNING LargeTreeGeneration
是用来在 Jupyter Notebook 中运行这个场景的命令,它会编译并展示动画。
总结
整体来看,这段代码使用了递归方法生成功能强大的树形结构,同时通过相机动画使得最终的展示更加生动。设计上颇具灵活性,可通过调整 DEPTH
和 CHILDREN_PER_VERTEX
属性轻松实现不同规模的树。
6.25探索有向图
在manim中有Circular Layou,Kamada Kawai Layout,Partite Layout,Planar Layout,Random Layout,Shell Layout,Spectral Layout,Sprial Layout,Spring Layout,Tree Layout 函数,它们都有自己的特点。下面是Manim中不同布局函数的简要解释,包括每个布局的用途、特点以及适用场景的简要描述。
一,介绍
1.Circular Layou
Circular Layout 是一种图形布局方法,用于在一个圆形范围内均匀分布图形中的节点(或顶点)。在这种布局中,节点按照圆周均匀排列,形成一个圆,使得节点之间的距离尽可能相等。这种布局特别适合展示网络结构、群体关系或层次结构,因为它能够有效地利用空间并提供良好的可视化效果。
主要优点: 均匀分布:节点在圆周上均匀分布,视觉上很平衡。 增强可读性:通过减少节点之间的重叠,确保节点的标签和链接更加清晰。 适用性广:适用于各种类型的图(如社交网络、树形结构等),特别是当关系相对对称或没有明显方向时。 视觉吸引力:圆形布局通常更具吸引力,并能够引导观众的视线。 使用场景: 社交网络分析:可以用来展示用户之间的连接和交互。 数据可视化:适合显示类别之间的关系,例如产品特性和相互作用。 生物信息学:在基因组学或生态学中,展示物种或基因之间的关系。
2. Kamada Kaway Layout
- 用途: 常用于绘制图结构,特别是在网络图或连接图中。
- 解释: 基于力导向算法,Kamada-Kaway布局试图最小化节点之间的距离,使图形结构在视觉上看起来更为平衡。
- 特点: 可以很好地展示复杂的连接关系,优先考虑局部连接。
3. Partite Layout
- 用途: 用于可视化分层或分组关系的图。
- 解释: 将节点分为不同的组,并在图中以不同的水平显示这些组,有助于清晰地展示节点之间的连接关系。
- 特点: 非常适合展示二分图或多分图结构,保持组与组之间的距离。
4. Planar Layout
- 用途: 专门用于展示平面图。
- 解释: 以一种不相交的方式将节点排列,并确保没有边线交叉,有助于清晰地可视化图形。
- 特点: 适合于平面图的视觉化应用,能够保持视觉的整洁性和可读性。
5. Random Layout
- 用途: 随机展示节点的布局。
- 解释: 节点的排列没有特定的模式,通常用于快速实验或初步可视化,没有明确的线性或层次结构。
- 特点: 虽然没有结构性,但适合于快速生成可视化,缺乏可读性和美观。
6. Shell Layout
- 用途: 用于环形结构的展示。
- 解释: 节点被放置在同心圆或者环形中,可以展现节点之间的层次或群体。
- 特点: 适合展示具有环形结构的图,特别是当节点具有层次性时,视觉效果较好。
7. Spectral Layout
- 用途: 基于图的谱特征进行节点位置的布局,适用于复杂图形的展示。
- 解释: 通过分析图的拉普拉斯矩阵,利用特征值分解来确定节点的位置,能够反映节点之间的关系。
- 特点: 能够有效地减少图的边的交叉,适合于复杂图的数据表示。
8. Spiral Layout
- 用途: 用于螺旋结构的节点排列。
- 解释: 节点沿着一个螺旋形的路径排列,通常用于展示时间序列数据或具有顺序性质的结构。
- 特点: 适合展示时间演变或是逐步发展关系的图,视觉上独特。
9. Spring Layout
- 用途: 基于物理模拟进行节点布置,常用于社交网络或图形结构。
- 解释: 使用力导向算法,模拟节点之间的相互作用(如弹簧吸引或推斥)来获得节点的位置,从而使图形结构尽可能平衡。
- 特点: 能够直观地反映节点间的连接强度,适合展示复杂的网络关系。
10. Tree Layout
- 用途: 可视化层次性的数据结构,如树、家谱图或组织结构图。
- 解释: 节点根据层次结构排列,根节点位于顶部或中间,子节点分布在下方或侧面,清楚地呈现父子关系。
- 特点: 强调层级关系,提供一种清晰且直观的图形表示。
这些布局功能在Manim中为用户提供了多样化的图形可视化工具,方便展示不同类型和结构的数据,非常适合用于教育、演示和科学研究等场景。
二,应用
1.Circular Layout
-
圆形布局:将顶点放在一个圆上
-
示例1:
from manim import * class CircularLayout(Scene): def construct(self): graph = Graph( [1, 2, 3, 4, 5, 6], [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (5, 1), (1, 3), (3, 5)], layout="circular", labels=True ) self.add(graph)
运行结果:
示例2:
class CircularLayout(Scene):
def construct(self):
graph = Graph(
[1, 2, 3, 4, 5, 6],
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (5, 1), (1, 3), (3, 5)],
layout="circular",
labels=True,
vertex_config={1: {"fill_color": BLUE}},
edge_config={(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1): {"stroke_color": RED}} # Fixed line
)
self.add(graph)
运行结果:
示例3:
from manim import *
class CircularLayout04(Scene):
def construct(self):
points=[1, 2, 3, 4, 5, 6]
edges = [
(1, 2), (2, 3), (3, 4), (4, 5),
(5, 6), (6, 1), (5, 1), (1, 3), (3, 5)
]
# 定义外环边
outer_edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)]
in_edges = [(5, 1), (1, 3), (3, 5)]
# 使用字典生成边配置,将外环边设置为红色,内部边设置为黄色
edge_config = {edge: {"stroke_color": RED} for edge in outer_edges}
edge_config.update({edge: {"stroke_color": YELLOW_E} for edge in in_edges})
graph = Graph(
points,
edges,
layout="circular",
labels=True,
vertex_config={vertex: {"fill_color": BLUE}for vertex in points}, # 配置顶点
edge_config=edge_config # 使用合并后的边配置字典
)
self.add(graph)
运行结果:
2. Kamada Kaway Layout
-
Kamada Kawai Layout:尝试放置顶点,以便尊重它们之间的给定距离
-
示例1:
from manim import * class KamadaKawaiLayout(Scene): def construct(self): from collections import defaultdict distances: dict[int, dict[int, float]] = defaultdict(dict) # set desired distances distances[1][2] = 1 # distance between vertices 1 and 2 is 1 distances[2][3] = 1 # distance between vertices 2 and 3 is 1 distances[3][4] = 2 # etc distances[4][5] = 3 distances[5][6] = 5 distances[6][1] = 8 graph = Graph( [1, 2, 3, 4, 5, 6], [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)], layout="kamada_kawai", layout_config={"dist": distances}, layout_scale=4, labels=True ) self.add(graph)
运行结果:
示例2:
class KamadaKawaiLayout(Scene):
def construct(self):
from collections import defaultdict
distances: dict[int, dict[int, float]] = defaultdict(dict)
# set desired distances
distances[1][2] = 1 # distance between vertices 1 and 2 is 1
distances[2][3] = 1 # distance between vertices 2 and 3 is 1
distances[3][4] = 1 # etc
distances[4][5] = 1
distances[5][6] = 1
distances[6][1] = 1
distances[2][5] = 2
distances[2][4] = 2.5
graph = Graph(
[1, 2, 3, 4, 5, 6],
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)],
layout="kamada_kawai",
layout_config={"dist": distances},
layout_scale=4,
labels=True,
vertex_config={2: {"fill_color": BLUE}} # 配置顶点
)
self.add(graph)
运行结果:
3. Partite Layout
-
Partite Layout:将顶点放置在不同的分区中
-
示例1:
from manim import * class PartiteLayout(Scene): def construct(self): graph = Graph( [1, 2, 3, 4, 5, 6], [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (5, 1), (1, 3), (3, 5)], layout="partite", layout_config={"partitions": [[1,2],[3,4],[5,6]]}, labels=True ) self.add(graph)
运行结果:
示例2:
class PartiteLayout(Scene):
def construct(self):
graph = Graph(
[1, 2, 3, 4, 5, 6],
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (5, 1), (1, 3), (3, 5)],
layout="partite",
layout_config={"partitions": [[1,5],[3,6],[5,6]]},
labels=True
)
self.add(graph)
运行结果: