☰
基于Jira与matplotlib的缺陷严重性分布饼图自动化报表实践
2026/10/16 3:20:57 网站建设 项目流程

这周的版本回顾会开完,我又被问到了那个经典问题:“这个版本严重缺陷占比到底多少?”以前遇到这种问题,我得先上Jira筛选出对应项目和版本,导出Excel,删掉无关列,再插入饼图、换个不那么丑的配色,最后截图贴到PPT里,半小时起步,而且周周重复。后来我把这块彻底做进了自动化报表链路里——脚本每天跑一遍,缺陷数据拉下来之后,用matplotlib直接生成缺陷严重性分布饼图,根本不用再手动折腾。

这篇是这个自动化报表分析系列的第七篇,重点聊饼图这一环。我会先讲清楚为什么严重性分布适合用饼图,再说数据口径怎么定、matplotlib的参数怎么调、真数据出来之后会遇到哪些坑。如果你想在自己团队里也做一套缺陷数据可视化,这篇文章可以直接参考着往下落地。

1. 饼图在缺陷报表链路里的定位:为什么严重性分布天生适合用饼图表达

1.1 缺陷数据的两类可视化:趋势归趋势,结构归结构

做缺陷报表做得久了,你会发现手里的数据其实分两种:一种是有时间维度的,比如每个迭代每天新增了多少缺陷、关闭了多少、遗留了多少,这类数据适合画折线图或面积图,因为它要表达的是“变化”;另一种是看当前构成比例的,比如所有未关闭缺陷里,严重缺陷、一般缺陷、轻微缺陷各占多少,这类数据适合画饼图或堆叠柱状图,因为它要表达的是“结构”。

严重性分布就是典型的后者。它不是一个时间序列问题,而是一个“蛋糕切几块”的问题。饼图的本质功能是把整体占比可视化,扇区面积越大,意味着这类缺陷在总量里越需要被重视。这个信息用饼图来表达,比用数字罗列直观得多——你可以一眼看到是不是某个严重级别占据了半壁江山。

1.2 饼图的适用边界:什么时候别硬画

但我也得泼一盆冷水:饼图不是万能的。它适合分类数比较少、且各分类占比差异不至于极端悬殊的情况。如果缺陷分类超过五六个,或者某一张饼图里90%都是同一个等级,剩下几个等级挤成一条缝,那饼图基本就废了——不是说不画,而是画出来信息传达效率太差。

我自己的习惯是,严重性分布这种口径固定、分类在四个左右的场景,用饼图没问题。但如果哪天你想把“严重性 + 模块”两个维度塞到同一张图里,那饼图就不合适了,这时候该用的是嵌套环形图或分组柱状图。还有一点:如果某一类缺陷数量极少,比如严重缺陷只占1%,画到饼图上就是一个看不见的小细条,很容易让看报表的人误以为“没有严重缺陷”。所以我后面会在数据预处理和展示两个环节都做处理,这些细节等讲到具体步骤时再展开。

2. Jira侧的数据准备:从优先级字段到严重性分类的映射口径

2.1 先想清楚一个问题:Priority不是直接等于Severity

很多团队第一次做这种报表时,会直接把Jira里的“优先级Priority”字段当作严重性来用。严格来说这是有问题的。优先级本身更多是“这件事在队列里排多前面”的意思,它会受业务紧急程度、版本排期影响,同一个严重缺陷在临发布前可能被提到P1,在平时可能只是P2。而严重性描述的是“这个缺陷本身对系统影响有多大”,比如是否阻断登录、是否导致数据丢失。

所以我在自动化报表里做了一件事:把Jira的Priority按一套团队约定好的口径映射成Severity等级,然后针对映射后的等级做饼图。你可以说这就是一种归一化。协同开发、测试、产品几方对齐过定义之后,映射规则就可以沉淀成一段固定的映射表,写死在脚本里。

2.2 数据从Jira里怎么拉:一个稳定的接口即可

数据获取环节,稳定的做法是通过Jira REST API把当前要分析版本里的缺陷数据拉下来。核心是JQL查询条件、返回字段和结果大小限制。报表脚本通常只需要拉和分析相关的字段,没必要把整个issue的全量信息都捞出来。字段太多不仅慢,而且容易被一些自定义字段的怪异返回值干扰,这是我的经验。

import requests import pandas as pd jira_base = "https://jira.example.com" jql = "project = APP AND issuetype = Bug AND fixVersion = 2.4.0" fields = "summary,priority,status,created" resp = requests.get( f"{jira_base}/rest/api/2/search", params={ "jql": jql, "fields": fields, "maxResults": 500, "startAt": 0, }, auth=("robot_user", "token"), ) resp.raise_for_status() issues = resp.json()["issues"]

注意Jira接口是有分页的,maxResults默认只返回50条,所以要么用startAt翻页循环,要么直接把maxResults调到上限再循环拉完。我习惯写一个小的while循环,把startAt不断累加,直到拿到的条数少于一页为止。这样即使缺陷数量超过500,也不会出现数据被截断导致饼图占比算错的情况。

2.3 映射规则怎么定:一套可以被写死的对照表

拿到的priority字段通常是字符串,比如P0、P1、P2或者Blocker、Critical、Major、Minor、Trivial。不同项目模板的叫法还不一样。所以我在脚本里维护了一张映射表,把这些叫法统一归成四个严重性等级:严重、高、中、低。

表格后面是常用的一套映射方案,实际用的时候根据团队自己的等级定义改即可。关键是这个映射必须经过测试和产品一起确认,而不是脚本作者拍脑袋定的。

Jira优先级/类型归一化严重性说明
P0 / Blocker / 阻塞严重阻断使用、数据丢失、安全漏洞
P1 / Critical / 紧急高核心功能不可用,无绕过方案
P2 / Major / 高中主要功能受影响,有临时方案
P3 / Minor / 中低轻微问题,不影响主流程
P4 / Trivial / 低低界面细节、文案问题
未设置 / 其他未知单独归一类,不要直接丢弃

把Priority映射到Severity,代码写起来很简单:

severity_map = { "P0": "严重", "Blocker": "严重", "P1": "高", "Critical": "高", "P2": "中", "Major": "中", "P3": "低", "Minor": "低", "P4": "低", "Trivial": "低", } df["severity"] = df["priority"].map(severity_map).fillna("未知")

2.4 空值、未知值和脏数据的兜底策略

这张映射表里我特意加了一个“未知”分类,这是我在做自动化报表时吃过亏之后补上的。有一次查一个版本的缺陷分布,发现饼图上怎么少了几个缺陷,对来对去发现是那几个issue根本没有设置优先级字段。如果map之后fillna成空字符串,后续value_counts里就会冒出一种奇怪的空白类,图例上显示空白标签,看着非常像脚本bug。

所以现在我的处理是:所有无法映射的优先级统一归入“未知”,并且在分析时保留这个分类,但在展示时单独标注为数据质量问题。如果“未知”占比超过一定比例,比如5%,我就会把这个版本的数据标成“待确认”,提醒先把Jira数据补全了再谈分析。这在自动化链路里很重要——数据质量不达标,图形再好看也没有意义。

3. matplotlib饼图核心参数拆解:从统计数据到第一张图

3.1 分组统计:顺序一定要显式指定

数据拿到手之后,需要统计每个严重性等级各有多少条。这一步最朴实的方式是value_counts,但有一个细节不能忽略:value_counts的默认排序是数量从多到少,而不是你想要的严重性顺序。

如果按数量排序,假设这周低等级缺陷特别多,饼图的第一块扇区就是“低”,而看报表的人习惯性会按“严重、高、中、低”这种业务顺序去找第一块。顺序一旦和直觉不一致,读图就费劲。所以我在统计之后会用一个固定列表重新排序:

severity_order = ["严重", "高", "中", "低", "未知"] counts = df["severity"].value_counts() sizes = [counts.get(sev, 0) for sev in severity_order] labels = severity_order

这里把缺失的等级用0补上,是为了保持饼图扇区位置稳定。否则这个版本没有严重缺陷,图例里直接少掉一块,下一周又冒出来,前后两张图对不上,没法做跨版本对比。

3.2 plt.pie的主要参数:每一个参数都值得单独理解

matplotlib里画饼图的核心就是plt.pie。参数不算多,但每个参数都影响最终效果。我先把我日常使用的完整配置贴出来,再逐个拆解。

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False colors = ["#c0392b", "#e67e22", "#f1c40f", "#95a5a6", "#bdc3c7"] explode = [0.08, 0, 0, 0, 0] labels_with_pct = [f"{l} {n:>5d}个" for l, n in zip(labels, sizes)] fig, ax = plt.subplots(figsize=(8, 6), dpi=150) ax.pie( sizes, labels=labels_with_pct, colors=colors, autopct=lambda pct: _format_pct(pct, sizes), startangle=90, counterclock=False, explode=explode, pctdistance=0.75, wedgeprops={"edgecolor": "white", "linewidth": 2}, ) ax.set_title("2.4.0版本缺陷严重性分布", fontsize=14) ax.axis("equal")

我按重要程度逐个说:

  • explode,把扇区“炸开”。这是为了突出严重缺陷。默认所有扇区都贴在一起,严重级别占比低的时候完全看不清。给它0.08的偏移量之后,严重缺陷那块会从整体里“跳出来”,看报表的人第一眼就能锁定。对于严重缺陷这种风险信号,夸张一点没关系。
  • autopct,控制扇区上标注的百分比文字。我把默认的"%1.1f%%"换成了一个自定义函数,原因后面说。需要注意百分比和数量到底显示哪个。我的经验是都显示:图上写“严重 12个”,旁边用图例承载更多信息,比只写“8.5%”强得多。
  • startangle=90 + counterclock=False,是为了让扇区顺序从左上方开始顺时针排列。这里涉及一种默认行为差异:如果不设置,matplotlib是从0度也就是三点钟方向开始逆时针画的,而我希望从十二点钟方向开始,这样第一块扇区“严重”处在正上方,最显眼。90度加逆时针关闭组合起来,就是顺时针从顶部开始。
  • wedgeprops里的edgecolor和linewidth,这是用来给每块扇区加白色描边的。不加的话,相邻扇区间颜色对比较弱时,分界线会不明显,整块饼看起来发闷。加一圈白边,扇区分界立刻清楚。
  • axis("equal"),不加的话图形的椭圆比例会被拉歪,饼图变成饼“椭圆”,尤其是图周围有标题或图例时更容易出现。这一行是保证饼图就是正圆的关键。

自定义的百分比格式化函数长这样:

def _format_pct(pct, all_sizes): total = sum(all_sizes) actual = pct / 100 * total if pct < 3: return "" return f"{pct:.1f}%"

这个函数做的事情是:当占比小于3%时,不显示百分比文字,避免多个小扇区的文字堆在一起叠成一团黑;占比大于等于3%才正常显示。这是我第一版图踩了文字重叠的坑之后加上去的,后面遇到那么多小分类混在一起时非常管用。

3.3 中文字体与负号显示:新环境跑脚本的第一个白眼

第一次跑这个脚本的人,八成会遇到同一个问题:图出来了,但所有中文标签都是一个个方块,标题也全是框框。原因很直接,matplotlib默认字体里没有中文字形。

解决方法是前面代码里那两行:

plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False

font.sans-serif这行指定了图形里的无衬线字体优先用黑体或微软雅黑,只要系统里有其中任意一个,中文就能正常显示。后面那行是修正坐标轴上负号的显示问题的,虽然饼图一般不涉及负坐标,但这是matplotlib中文字体的经典组合,我凡是画图脚本都会顺手加上。

如果跑在Linux服务器上,这份脚本大概率没有SimHei也没有Microsoft YaHei,那你需要提前装中文字体包,或者直接指定你手动放进去的字体文件路径。最稳妥的办法是用font_manager把字体文件的绝对路径加载进来,再用rcParams指定font.family。服务器上没有图形界面,更不容易凭直觉装字体,这块提前处理,否则报表里全是方块字,自动化再好也白搭。

4. 让饼图在自动化报告里真的“能看”:排版、配色与自动解读

4.1 图片尺寸与dpi:统一输出规格比“尽量高清”更重要

饼图进入自动报表之后,它就不再是一张孤立的图片,而是要嵌入邮件、HTML报告或者PDF文档里的固定版面元素。统一规格在这里特别重要。我固定用figsize=(8,6),dpi=150,导出PNG。这个分辨率放大到普通邮件正文里不会模糊,文件大小也不会过分膨胀。

保存图片时有一个参数容易被忽略,就是bbox_inches="tight":

fig.savefig("severity_pie.png", dpi=150, bbox_inches="tight")

不加这个参数,保存出来的图片周围会有一圈多余的白边,尺寸看起来会比画布里内容大一圈。加上了,matplotlib会自动计算内容区的范围,把空白部分裁掉。多图拼到同一份报告里时,每张图的边距一致,整体排版会干净很多。为了更稳,我通常在savefig之前只调用plt.close(fig),避免脚本在循环里生成多张图时出现内存堆积。

4.2 图例、标题与百分比文案:信息层次要分清楚

一张专业的饼图,信息层次应该是:标题告诉读者这是什么版本的什么分布,扇区本身承担主要的信息传达,图例则用来解释颜色与分类的对应。如果饼图里每块都写了很长的标签再加百分比,再加上图例,画面上就全是字,反而没有重点。

我的做法是:每个扇区旁边只显示“等级 + 数量”,百分比信息放在扇区内部。图例放在画布右侧外侧,用来解释颜色对应的严重性。如果严重等级与颜色之间的对应关系在两个版本之间保持一致,看熟了的同学甚至不需要看图例,直接通过颜色就能判断哪块是严重缺陷。

配色方面,我选了一套带有语义的规则颜色,而不是让matplotlib自动分配默认色。严重用深红色,高用橙色,中用黄色,低用灰色,未知用浅灰。这套颜色的思路很简单:暖色调表达“危险和需要关注”,冷色调和灰色表达“正常和低风险”。如果团队有现成的品牌色或报表模板色,最好直接沿用,保持整个月的报表风格统一。

还有标题,我建议把版本号带进去,比如“2.4.0版本缺陷严重性分布”。因为自动化报表一个月会跑好几份不同版本的数据,图片保存到本地之后,如果不看文件名根本不知道是哪份的。把版本号写进标题和文件名里,都是避免混淆的细节。

4.3 自动解读:让图自己说出结论,而不是等人肉总结

光有一张饼图还不够。报表的意义在于让人快速得到结论,所以我在这张饼图生成之后加了一个简单的结论生成器,根据严重性的占比自动生成一两句文字。

逻辑大致是:

  • 如果严重缺陷占比大于等于某个阈值,比如10%,生成一句“严重缺陷占比超过10%,建议发布前逐条评审”;
  • 如果严重与高两个等级合计占比超过30%,生成一句“高风险缺陷集中,建议评估测试覆盖是否充分”;
  • 如果未知类占比大于0,生成一句“有X条数据未设置优先级,需要补全后再分析”。

这段文字会直接追加到图片嵌入HTML报表时的说明文本里。它不算什么高级能力,但自动化报表的价值就在这里:数据一进来,图有了,结论也有了。看报表的人不只是一个一个猜这些图形什么意思,能直接捕捉到当前版本的风险信号。

5. 三周真实数据迭代的过程:第一版饼图与三次调参记录

5.1 第一版画出来长什么样:问题基本都暴露了

我第一次拿真实迭代数据跑这个图,是某产品线一个三周迭代的缺陷数据。当时统计出来严重2条、高8条、中17条、低45条、未知3条。第一版脚本很朴素,直接value_counts后丢给plt.pie,没做顺序固定,没做格式处理。出来的图有四个问题:顺序乱,占比最大的“低”从十二点钟方向开始;严重只有两条,2.7%的扇区细得几乎看不见;四个等级的文字标签全挤在扇区旁边,转成一坨;图例里因为value_counts没算上缺失等级,所以只有四个项。

那版图我只在内部测试里跑了一下,没敢放到正式报表里。虽然能看出大概分布,但作为给决策者看的材料,信息传达不合格。

5.2 调整重心:顺序、爆炸项、百分比显示三管齐下

第一版的问题暴露之后,我按前面两个章节的办法逐项调整。首先是顺序,把严重性等级改成显式排序;其次是explode,让严重缺陷那一块从饼图里弹出来;然后是百分比显示,小于3%的扇区不显示数字,避免跟其他标签挤在一起。

改完之后,严重那2.7%虽然还是很小,但因为颜色深红又往外弹出了一段,配合图例说明,存在感比之前强很多。后来我发现光这一点还不够,于是又在标题下方加了一行总数说明,比如“缺陷总数75条,严重缺陷2条(2.7%)”,让最关键的信号在标题区就被读出来,而不是让人低头去找扇区。

这里也涉及到我前面提到的数据口径问题:未知那3条,在饼图里单独一个灰色小扇区。第一版看这个扇区时,我的第一反应是“这是不是bug”,查了数据文本才发现源头是三个没设优先级的issue。于是后面在实际分析时,我会把未知分类的缺陷清单单独打印出来,方便定位是哪几个issue需要回Jira补字段。

5.3 增加历史对比:单一版本分布与区间分布的组合联动

单一版本的饼图说到底是静止的。像我们团队每三周一个迭代,单看某一个迭代的严重性占比,只能得到一个相对印象。如果想看团队整体质量趋势,还得把多次迭代的严重性占比放在一起比较。

我现在会额外生成一张“连续N个迭代严重性占比堆积图”,把各版本的严重、高、中、低占比画成堆叠柱状图,每一根柱子是一个版本。这样再看饼图时,它回答的是“当前版本内部结构长什么样”,而堆积图回答的是“这几个月严重性结构是在变好还是变坏”。两张图一组合,报表对质量趋势的解释力就上了一个台阶。饼图本身不需要变,但它作为报表里的一环,和别的图放在一起看,价值才完整。

6. 把饼图嵌入整份自动化报告时的几个落地细节

6.1 子函数化与文件命名:让脚本更早稳定下来

自动化报表的脚本不是写一次就完了,而是要反复跑、反复改的。我把饼图生成逻辑封成了一个独立函数,只暴露数据DataFrame、版本号、输出路径三个入口参数。函数内部自己做映射、统计、画图、保存,不在函数外散落着画图代码。这样主流程清晰,改饼图样式时也不会误伤其他图表的生成逻辑。

输出文件命名也遵循一个约定:severity_distribution_v{版本号}.png。这能保证每次跑完脚本,旧图会被新图覆盖,但文件名里自带版本号,不至于在一片无名的图片里靠打开时间猜内容。配合前面的标题带版本号,双保险。

6.2 报表里多图排版的协调问题

如果自动化报告里除了饼图,还有前面几篇里提到的缺陷趋势图、累积流量图等多张图,那么版面协调就得考虑一下。我的经验是:饼图比较适合放在报告上半部分的概述区,因为它反映的是当前快照状态;趋势图放在下一层,用作时间维度的分析。这样读报表的人先看到一个整体的严重性结构,再往下看变化过程,逻辑上是顺的。

如果要把多张图拼到同一份PDF或网页里,建议给每张图留出独立的空间,不要用表格硬凑。宽度保持800像素左右,高度按比例自适应。饼图的长宽比接近正方形,视觉上比较好;折线图高度可以稍微矮一点,不需要强行等高。

6.3 黑白打印、深浅色模式和字体缺失:三个易被忽略的展示兼容问题

有几个细节是我在实际推送报表后才发现的。一是有同事会把报表打印出来开会,黑白打印下,黄色和浅灰两块扇区几乎看不出差别。所以我的配色刻意让高、中、低在灰度下也有区分度,严重的深红印成黑白后仍然是最深的一块,这个对比足够。

二是某些邮件客户端会把PNG背景强制改成白色,所以我保存图片时明确设置了facecolor="white",不让它保留透明的背景,避免嵌到深色主题页面里时出现一块黑底。

三是不同运行环境字体问题。本地Windows上跑得好好的,放到某台服务器上一跑,中文又变方块了。解决方案就是前面说的,要么在服务器上安装中文字体,要么用font_manager显式加载字体文件路径。自动化链路跑在哪个环境,就提前在那个环境验证一次中文字体,这个验证动作应该写进部署清单里。

6.4 我的一点个人体会

这个饼图生成模块从第一版到现在,改动次数不算多,但每次改动都来自真实使用反馈。有时候是一线测试说某个色块分不清,有时候是研发负责人说想一眼看到严重缺陷数量和占比,有时候是我自己发现数据里有未知分类没处理干净。做这种自动化报表,最难的不是画图函数本身,而是把数据口径、展示逻辑、异常处理和交付形式串成一个稳定可重复的流程。

现在每次版本回顾会前,我只需要跑一遍脚本,图、结论、风险提示都生成好了。严重缺陷占比这东西,虽然只是一个数字,但它背后代表的是这个版本的质量风险信号,值得每次发布前都认真看一眼。这套流程你也可以直接照搬,先把映射口径对齐,再跑通一张饼图,后面再慢慢往里面加你的团队特色。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询