Files
paste-framework/paste/chart/pie.py
T
2026-06-02 16:26:10 +08:00

144 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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