27 数据占比展示:jquick-pdf饼图动态数据渲染PDF教程
引入
费用报销要说明钱花在哪,客户分析要说明客户从哪来,库存盘点要说明积压集中在哪些品类------这些问题的答案形式都一样:一个整体被拆成几块,每块占多少。用表格列出金额当然没错,但读者要在脑子里做除法才能得到"占比",而饼图把这步提前算好了。饼图能力来自 io.github.paohaijiao:jquick-pdf-svg,序列由 JPie 承载,数据项由 jquick-pdf-data 中的 JData 描述,渲染由 JPieChartsRenderer 完成,PieCharTest 已经证实了这条调用链。本文把数据库聚合后的类别金额转换成饼图 SVG,再嵌入 PDF 报告,重点放在数据口径和落地细节,而不是配色。饼图看起来简单,真正容易出错的却是分母:合计口径、剔除规则和单位只要有一处含糊,扇区就会讲出错误的故事。
核心讲解
饼图的语义:整体中的一部分
饼图只在一种场景下最合适:一组类别构成一个整体,读者关心"各占多大比例"。它的隐含前提是类别总和有意义------所有扇区加起来就是那个 100%。一旦这个前提被破坏(比如既有收入又有支出,或者数据本身有正有负),饼图就会给出误导性的画面。因此绘制之前必须先确定"整体"的定义和统计口径,并把它写进报告正文,读者才知道这张图在说什么。
从类别金额到扇区
一个类别对应一个数据点,数据点由名称和值组成:名称进入图例,值决定扇区角度。多个数据点组成一个系列,系列再挂到图表配置上,配置同时承载标题、副标题和提示触发方式。渲染器读取配置后输出 SVG,SVG 是矢量图形,打印放大后边缘依旧清晰。图表配置模型来自 jquick-pdf-data,图形能力来自 jquick-pdf-svg,文档层核心是 jquick-pdfx,样式模型由 jquick-pdf-css 提供,中文标签缺字时可交给 jquick-pdf-font 的内置 CJK 字体。
分母与口径
饼图的输入本质上是一次分组求和,占比由渲染阶段计算,但分母必须由业务明确给出。以"本季度部门费用,单位万元,已剔除冲销单"为例,口径说明至少包含三点:统计周期、金额单位、剔除或包含的特殊记录。合计值应当显式出现在报告正文里,因为读者看到的是比例而不是金额,只有附上总额才能还原业务量级。另外,同一张图内只能采用一种计数方式,绝对金额和预先算好的百分比不能混用,否则扇区角度会失去意义。
模板绑定与文档层
文档层并不理解"扇区",它只接受 SVG:模板里放一个 <svg>${svg}</svg> 占位符,把渲染结果用 bind 注入即可。变量用 ${name},已注册资源用 &{name};模板根元素用 <pdf> 或 <html>,常用结构为 <pdf><body>...</body></pdf>,文本字面量必须写单引号。图表旁边通常还要补一段口径说明,写明金额单位、统计周期和合计值,读者才能把图形和数字对上。
与表格的分工
饼图负责第一眼的占比印象,表格负责精确数字,两者配合效果最好:图中只保留主要类别,明细金额放进表格,而模板本身支持 table/tr/th/td 等表格元素。需要特别注意的是,图形与明细表必须来自同一次统计、同一套口径,否则读者一旦发现两处数字对不上,就会怀疑整份报告的可信度。
关键细节
- 类别金额为负数时不应直接进入饼图,负值会让扇区角度无法解释,应先确认是口径错误还是应该换一种图形。
- 零值类别会产生零面积扇区,只留下图例噪声,建议入图前过滤,或在正文写一句"该类别本期无发生额"。
- 百分比四舍五入后可能不等于 100%,这不是渲染错误;需要严格闭合时可以指定一个类别吸收舍入误差并注明。
- 类别名称过长会挤压图例,建议在数据侧使用短名称,把全称放到正文或表格里。
- 饼图不能表达时间趋势,月份之间此消彼长并不是"部分与整体"的关系。
- 类别过多时图形会碎成难以辨认的细条,应先按阈值合并为"其他"再绘图。
- 图形与明细表必须来自同一次聚合快照,口径不一致比图形不好看更致命。
- 图例宽度与绘图区直径共同决定可读性,嵌进 A4 后要确认两者同时可见且没有被分页切开。
- 同一张图内不能混用绝对金额与预先算好的百分比,扇区角度会因此失去可比性。
- 系列名只用于标识该系列,不参与占比计算,命名不必纠结,把精力放在类别名上。
实战说明
依赖与模板
使用 JDK 8+,文档层与图表层分两个依赖引入:
xml
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-pdfx</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-pdf-svg</artifactId>
<version>4.0.0</version>
</dependency>
模板可以短到只有标题、图形和一句口径:
html
<pdf><body><h1>'费用结构'</h1><svg>${svg}</svg></body></pdf>
完整 Java 示例
java
import com.github.paohaijiao.JOption;
import com.github.paohaijiao.code.JTrigger;
import com.github.paohaijiao.config.JGraphConfig;
import com.github.paohaijiao.config.JPdfConfig;
import com.github.paohaijiao.data.JData;
import com.github.paohaijiao.data.JGraphContainer;
import com.github.paohaijiao.enums.JChartType;
import com.github.paohaijiao.executor.JQuickPdfFactory;
import com.github.paohaijiao.pie.JPieChartsRenderer;
import com.github.paohaijiao.series.JPie;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
public class PieReportDemo {
public static void main(String[] args) throws Exception {
JOption option = new JOption();
option.title().text("部门费用占比").subtext("本季度");
option.tooltip().trigger(JTrigger.item);
JPie pie = new JPie("费用");
pie.data(new JData().name("研发").value(420), new JData().name("销售").value(260),
new JData().name("行政").value(120), new JData().name("客服").value(200));
option.series(pie);
String template = "<pdf><body><h1>'费用结构报告'</h1><svg>&{svg}</svg>"
+ "<p>'金额单位:万元,合计:1000。'</p></body></pdf>";
JGraphContainer graphContainer = new JGraphContainer();
graphContainer.setType(JChartType.PIE);
graphContainer.setOption(option);
JGraphConfig graphConfig = new JGraphConfig();
graphConfig.put("svg", graphContainer);
JPdfConfig config = new JPdfConfig();
config.setGraphConfig(graphConfig);
byte[] pdf = new JQuickPdfFactory(config).executeContent(template);
Files.write(Paths.get("d://test//pie-report.pdf"), pdf);
}
}
步骤拆解
数据库先按类别求和,并校验非负值与合计口径;把每个类别转成一个数据点,名称与值分别对应图例和扇区;数据点挂到系列,系列挂到图表配置;渲染器写出 SVG;读取 SVG 文本后绑定为模板变量;最后落盘或交给响应流。运行后 PDF 展示扇区和图例,正文补充总额与单位,读者不必自己换算。
动态数据与多图共存
真实项目里类别数量往往不固定。稳妥做法是先在服务层把查询结果整理成有序列表,为每个类别构造数据点再传入系列,而不是用字符串拼接把数据塞进模板;最终得到的 SVG 通过 bind 注入变量,类别从四个变成六个时模板也无需改动。同一页放多张图时,给每张图准备独立的变量名,避免互相覆盖;排序、合并"其他"这类逻辑都放在 Java 侧,模板只负责呈现。
生产处理建议
后端必须保留原始金额与计算口径,图形只是展示层;对小于阈值的类别合并为"其他",避免图例拥挤;同一统计周期的 SVG 可以缓存,减少重复渲染;空集合要生成明确的无数据提示,而不是一张空图。SVG 是中间产物,出问题时可以单独预览,方便判断是数据错了还是图形没渲染出来。上线后关注生成失败率与文件大小即可;占比类报表的问题更多来自口径而不是渲染,因此建议同时保留一份类别金额明细,对账时有据可查。
总结
饼图适合"整体分成几块"的结构占比,不适合多维交叉比较;需要比较多个周期时,可以并排多张图,或改用柱状图按类别对比。选型时可以核对一句话:类别数少、且业务真的关心占比,再用饼图。本文的图表 API 只采用 PieCharTest 中已经证实的写法,不假设任何未验证的 setter 或方法。另外,空状态值得当作一等公民来设计:统计周期内没有数据时,输出一句"本周期无费用记录",比输出一张没有扇区的空图更不容易被误解。保持聚合口径透明、把单位和合计写进正文,报告才经得起审计。版本基线:jquick-pdfx 4.0.0、JDK 8+,升级前请核对 README_zh.md 的版本对照表;更多示例见 GitHub 仓库。