sillyvis 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sillyvis/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """sillysort — 排序算法可视化"""
2
+
3
+ from .core import Silly, frame
4
+
5
+ __all__ = ["Silly", "frame"]
6
+ __version__ = "0.1.0"
sillyvis/core.py ADDED
@@ -0,0 +1,170 @@
1
+ import matplotlib
2
+
3
+ matplotlib.use("TkAgg")
4
+
5
+ import matplotlib.pyplot as plt
6
+ from matplotlib.animation import FuncAnimation
7
+ from matplotlib import rcParams, font_manager
8
+
9
+
10
+ class Silly:
11
+ """排序可视化器
12
+
13
+ 约定(多排模式):
14
+ yield ( [arr, aux, count, ...], # list of list,每排一个数组
15
+ [hl1, hl2, hl3, ...], # 同长度的 list of dict,每排各自的高亮
16
+ "描述文字" )
17
+ hl 每个元素可以是 dict {idx: color},也可以是一组索引 (用默认高亮色)。
18
+
19
+ 单排模式(兼容旧用法):
20
+ yield ( arr, hl_dict, "描述文字" )
21
+ """
22
+
23
+ NORMAL = "#4e8df5"
24
+ HIGHLIGHT = "#ff4d4d"
25
+ LEFT_PTR = "#ff9f1c"
26
+ RIGHT_PTR = "#9b5de5"
27
+ LOCKED = "#2ecc71"
28
+
29
+ def __init__(self, data, n=None, font_path=None):
30
+ self.data = list(data)
31
+ self.n = len(self.data) if n is None else n
32
+
33
+ # 中文字体
34
+ if font_path:
35
+ try:
36
+ font_manager.fontManager.addfont(font_path)
37
+ except Exception:
38
+ pass
39
+ available = [
40
+ f.name
41
+ for f in font_manager.fontManager.ttflist
42
+ if "Noto" in f.name and "CJK" in f.name
43
+ ]
44
+ cjk_name = None
45
+ for name in available:
46
+ if "SC" in name:
47
+ cjk_name = name
48
+ break
49
+ if cjk_name is None and available:
50
+ cjk_name = available[0]
51
+ if cjk_name:
52
+ rcParams["font.sans-serif"] = [cjk_name]
53
+ rcParams["font.family"] = "sans-serif"
54
+ rcParams["axes.unicode_minus"] = False
55
+
56
+ self.fig, self.ax = plt.subplots(figsize=(13, 7))
57
+ self.ax.set_xticks([])
58
+ self.ax.set_yticks([])
59
+ self.info_text = self.ax.text(
60
+ 0.01,
61
+ 0.99,
62
+ "",
63
+ transform=self.ax.transAxes,
64
+ fontsize=13,
65
+ fontweight="bold",
66
+ color="#333333",
67
+ va="top",
68
+ )
69
+ # 默认单排
70
+ self.bar_groups = [
71
+ self.ax.bar(range(self.n), self.data, color=self.NORMAL)
72
+ ]
73
+ self.multi = False
74
+ self.ani = None
75
+
76
+ def _resolve_arrays(self, arrays):
77
+ """判断并统一成 list-of-lists;返回 (arrays_list, multi_flag)"""
78
+ if isinstance(arrays, list) and arrays and isinstance(arrays[0], list):
79
+ return arrays, True
80
+ return [arrays], False
81
+
82
+ def _ensure_bars(self, arrays_list, multi):
83
+ """根据数组数量/长度重建柱子;arr 在 idx=0 → bottom=0 → 最底下"""
84
+ target = len(arrays_list)
85
+ need_rebuild = self.multi != multi or len(self.bar_groups) != target
86
+ if not need_rebuild:
87
+ # 更新高度即可
88
+ for group, arr in zip(self.bar_groups, arrays_list):
89
+ for idx, b in enumerate(group):
90
+ if idx < len(arr):
91
+ b.set_height(arr[idx])
92
+ return
93
+
94
+ # 重建
95
+ for group in self.bar_groups:
96
+ for b in group:
97
+ b.remove()
98
+ self.bar_groups = []
99
+ self.multi = multi
100
+
101
+ if multi:
102
+ max_val = max(max(a) for a in arrays_list)
103
+ gap = max_val * 0.2
104
+ for idx, arr in enumerate(arrays_list):
105
+ bottom = idx * (max_val + gap)
106
+ self.bar_groups.append(
107
+ self.ax.bar(
108
+ range(len(arr)), arr, bottom=bottom, color=self.NORMAL, width=0.8
109
+ )
110
+ )
111
+ top = (len(arrays_list) - 1) * (max_val + gap) + max_val + gap
112
+ self.ax.set_ylim(0, top)
113
+ else:
114
+ self.bar_groups = [
115
+ self.ax.bar(range(self.n), arrays_list[0], color=self.NORMAL)
116
+ ]
117
+
118
+ @staticmethod
119
+ def _normalize_hl(d, n):
120
+ """把单排的 hl 规格转成 {idx: color} 字典"""
121
+ if isinstance(d, dict):
122
+ return {k: v for k, v in d.items() if 0 <= k < n}
123
+ return {k: Silly.HIGHLIGHT for k in d if 0 <= k < n}
124
+
125
+ def _update(self, frame):
126
+ arrays, hl, text = frame
127
+
128
+ arrays_list, multi = self._resolve_arrays(arrays)
129
+ self._ensure_bars(arrays_list, multi)
130
+
131
+ if multi:
132
+ if isinstance(hl, list):
133
+ hl_list = list(hl) + [{}] * (len(arrays_list) - len(hl))
134
+ else:
135
+ hl_list = [hl] * len(arrays_list)
136
+ else:
137
+ hl_list = [hl]
138
+
139
+ artists = []
140
+ for g_idx, (group, arr) in enumerate(zip(self.bar_groups, arrays_list)):
141
+ clean = self._normalize_hl(hl_list[g_idx], len(arr))
142
+ for idx, b in enumerate(group):
143
+ b.set_height(arr[idx])
144
+ b.set_color(clean.get(idx, self.NORMAL))
145
+ artists.append(b)
146
+
147
+ self.info_text.set_text(text)
148
+ artists.append(self.info_text)
149
+ return artists
150
+
151
+ def go(self, sort_gen):
152
+ self.ani = FuncAnimation(
153
+ self.fig,
154
+ self._update,
155
+ frames=sort_gen(self.data),
156
+ interval=0,
157
+ repeat=False,
158
+ blit=True,
159
+ cache_frame_data=False,
160
+ )
161
+ plt.tight_layout()
162
+ plt.show()
163
+ return self
164
+
165
+
166
+ def frame(*pairs, text=""):
167
+ """按名字组装多排帧。pairs 为若干 (array, highlight) 元组。"""
168
+ arrays = [a.copy() for a, _ in pairs]
169
+ hls = [h for _, h in pairs]
170
+ return (arrays, hls, text)
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.5
2
+ Name: sillyvis
3
+ Version: 0.1.1
4
+ Summary: Sorting algorithm visualizer powered by matplotlib animations
5
+ Author-email: Small Ai <3910271726@qq.com>
6
+ Requires-Python: >=3.8
7
+ Requires-Dist: matplotlib>=3.5
@@ -0,0 +1,5 @@
1
+ sillyvis/__init__.py,sha256=e2rwaLyZZSShuo9VjvXFKukaFKt_YUZz3W14djccevk,126
2
+ sillyvis/core.py,sha256=pySwXynfkBxtLeKvrTYSsjqnTMsl-0549oj2TSVGlyI,5536
3
+ sillyvis-0.1.1.dist-info/METADATA,sha256=pDJ6F01H062kX3qg9Fp_85UmqNAjOekFXU5B7yMWTjI,220
4
+ sillyvis-0.1.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
5
+ sillyvis-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any