首次提交
This commit is contained in:
@@ -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.05,y居中
|
||||
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
|
||||
Reference in New Issue
Block a user