Merge commit '47296980495f8bbfc9493e93de85dd62de6fa6b9' as 'paste-framework'

This commit is contained in:
zwf
2026-06-02 19:09:22 +08:00
107 changed files with 21484 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
import base64
from io import BytesIO
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
from paste import chart
from paste.util import ufont
def gen_pie(data_df, value_column, percentage_column, legend_labels, color_palette='BuPu', dpi=128):
"""
生成环形图(Doughnut Chart),并附带右侧图例(色块 + 文字描述)。
注意:虽然函数名为 gen_pie,但实际绘制的是环形图(有中心空洞),非传统饼图。
参数说明(数据结构要求):
--------------------------
data_df : pandas.DataFrame
必须包含以下三列:
- value_column (数值列): 每个类别的数值(如设备数量),用于计算扇区角度。
- percentage_column (百分比列): 每个类别的百分比(如 35.2%),用于显示在图例中。
- legend_labels (标签列): 每个类别的名称(如 '服务器', '交换机'),用于图例文本。
示例结构:
| server_count | percentage | device_type |
|--------------|------------|-------------|
| 35 | 35.2% | 服务器 |
| 28 | 28.1% | 交换机 |
| 22 | 22.0% | 路由器 |
要求:
- 所有数值必须 ≥ 0;
- 百分比列应为字符串格式(含'%'符号),或可被转换为字符串;
- 所有列长度必须一致,且与 data_df 的行数匹配。
value_column : str
data_df 中表示数值的列名(如 'server_count')。
percentage_column : str
data_df 中表示百分比的列名(如 'percentage'),用于图例中显示。
legend_labels : str
data_df 中表示类别名称的列名(如 'device_type'),用于图例文本。
color_palette : str, default='BuPu'
Seaborn 颜色调色板名称,用于为每个扇区分配颜色。
可选值参考:'BuPu', 'viridis', 'plasma', 'Set3' 等。
若类别数 > 调色板颜色数,会自动循环使用。
dpi : int, default=128
输出图像的分辨率(dots per inch),影响 SVG 清晰度。
返回值:
--------
str : Base64 编码的 SVG 图像 Data URL,可直接用于 HTML <img src="...">。
"""
# === 1. 颜色准备 ===
# 从 Seaborn 获取指定调色板的颜色序列,长度等于数据行数
colors = chart.get_seaborn_colors(len(data_df.index), palette=color_palette)
# === 2. 字体设置 ===
# 获取系统可用中文字体,优先使用支持中文的字体
available_font = ufont.get_fonts()
plt.rcParams['font.sans-serif'] = list(available_font)
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题
# === 3. 创建画布与子图 ===
# 使用非白色背景,便于嵌入网页(透明背景)
fig = plt.figure(figsize=(8, 6), facecolor='none', dpi=dpi)
# 左侧:环形图区域
ax1 = fig.add_axes((0.1, 0.05, 0.6, 0.9)) # [left, bottom, width, height]
# 右侧:图例区域(色块+文字)
ax2 = fig.add_axes((0.75, 0.05, 0.2, 0.9))
# 移除两个子图的坐标轴(纯图形,无刻度)
for ax in [ax1, ax2]:
ax.set_axis_off()
# === 4. 绘制环形图 ===
# 使用 wedgeprops 设置内环宽度,实现环形效果
wedges, _ = ax1.pie(
data_df[value_column], # 数值列,决定扇区大小
colors=colors, # 颜色序列
startangle=90, # 从正上方开始绘制(更美观)
wedgeprops=dict(
width=0.6, # 环形宽度(0~1),越大越薄
edgecolor='white', # 边缘白色,提升对比度
linewidth=1 # 边缘线宽
),
radius=1.2, # 半径略大于1,避免边缘被裁剪
)
# === 5. 右侧图例:色块 + 文字 ===
# 图例参数定义
num_items = len(data_df.index)
box_size = 0.08 # 每个色块大小(宽度和高度)
text_offset = 0.05 # 文字与色块的水平间距
font_size = 24 # 字体大小
line_height = 0.1 # 每行占用高度(包括上下间距)
total_legend_height = num_items * line_height
start_y = 0.45 + total_legend_height / 2 # 使图例垂直居中于右侧区域
y_pos = start_y # 当前绘制的 y 坐标
# 遍历每一类,绘制色块和文本
for i, (label, color) in enumerate(zip(data_df[legend_labels], colors)):
# 绘制色块矩形
ax2.add_patch(
Rectangle(
(0.05, y_pos - box_size / 2), # 左下角坐标:x=0.05y居中
box_size, box_size, # 宽高均为 box_size
facecolor=color, # 填充颜色
edgecolor='white', # 白色描边
lw=1 # 线宽
)
)
# 绘制文字标签:名称 + 数值 + 百分比
ax2.text(
0.05 + box_size + text_offset, # 文字起始 x 位置:色块右侧 + 间距
y_pos, # y 位置居中于色块
f"{label}{data_df[value_column][i]}台,{data_df[percentage_column][i]}%",
va='center', # 垂直居中
ha='left', # 水平左对齐
fontsize=font_size,
fontweight='bold'
)
y_pos -= line_height # 下移一行,准备下一个图例项
# === 6. 输出图像 ===
# 使用 BytesIO 缓存 SVG 图像
buffer = BytesIO()
plt.savefig(buffer, format='svg', dpi=dpi, bbox_inches='tight', facecolor='none')
plt.close() # 关闭图形以释放内存
# 编码为 Base64 并构造 Data URL
image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
img_base64 = f"data:image/svg+xml;base64,{image_base64}"
return img_base64