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 +6 -0
- sillyvis/core.py +170 -0
- sillyvis-0.1.1.dist-info/METADATA +7 -0
- sillyvis-0.1.1.dist-info/RECORD +5 -0
- sillyvis-0.1.1.dist-info/WHEEL +4 -0
sillyvis/__init__.py
ADDED
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,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,,
|