jquick-pdf 项目审批月报实战:三根分组柱讲清 12 个月的审批负荷
官网示例:
摘要(meta description):项目监管平台的月报只有一句话的需求——把每个月受理、审批通过、退回补正三件事说清楚。三类数据量级接近,柱状图比折线图更能看出每月的构成。本文从业务场景出发,按数据装配、图表配置、SVG 渲染、模板落版的顺序走一遍 jquick-pdf 的实现步骤,并把季度合计与图上数据逐项核对了一遍(jquick-pdfx 4.7.0+,Apache-2.0)。
标签:jquick-pdf、Java PDF、报表开发、柱状图
一张月报要回答的三个问题
需求方给的原话很短:每个月受理多少、批下来多少、退回去多少。三个数放在一起,能回答的问题其实有三个——审批负荷在不在涨、办理质量有没有改善、哪个环节在拖。
这三个问题的答案形态不一样:负荷看总量趋势,质量看通过率,拖点看退回补正的绝对值和占比。三种看法要挤在一页纸上,图表选型就得兼顾"逐月对比"和"三类并列"。折线图能把趋势画得漂亮,但三条线在 1 月到 12 月之间会互相穿插,读者要花力气分辨哪根是哪根;分组柱每个月份站一组,三根柱高低立现,构成关系不用解释。
Demo 是 JQuickBiz17ProjectApprovalTest,模板 report/biz/17_project_approval.txt,输出 D:\test\biz\17_project_approval.pdf。(效果预览图 alt:jquick-pdf 生成的项目审批数量趋势月报 PDF,居中蓝色抬头、12 组三色分组柱图、下方季度汇总表与五条结论)
成品长什么样
一页 A4,从上到下一路排:居中的深蓝抬头(标题加两行元信息)、柱图加图注、五行季度汇总表、五条结论,页脚居中两行小字。
没有指标卡。这一点和很多经营类单据不同——审批月报的核心信息全在表和图的对应关系里,抽四个数字出来做卡反而会让人只看卡不看表。抬头也只占三行,省下来的高度全给了 700pt 宽的柱图。
图注写的是"图 1 2024年1-12月受理 / 审批通过 / 退回补正项目数量(数据来源:市项目审批监管系统)",把口径和来源都钉在图下面,评审问起来不用翻别处。
真实渲染效果预览
以下为上述代码 + 模板真实运行生成的 PDF 首屏预览(由 jquick-pdf 渲染输出,非示意图):

数据装配是三步:建序列、灌数、挂到 option 上。
JBar accepted = new JBar();
accepted.name("受理").data(142, 138, 155, 163, 158, 171, 182, 176, 195, 203, 214, 226);
JBar approved = new JBar();
approved.name("审批通过").data(112, 110, 124, 132, 128, 140, 150, 145, 161, 169, 178, 189);
JBar returned = new JBar();
returned.name("退回补正").data(22, 20, 24, 25, 23, 26, 27, 25, 28, 27, 29, 30);
option.series(accepted, approved, returned);
JBar 的 name() 是图例名,data() 收变长参数,12 个数对应 12 个月。三个序列的月份顺序必须完全一致,都从 1 月排到 12 月,错一个位置整张图的构成关系就反了。
序列顺序也有讲究:受理在最前、通过在中、退回在最后。分组柱按序列顺序从左到右排,这个顺序刚好是审批流程的时间顺序——先受理,再决定通过还是退回。图上每组柱子右起第一根永远是"受理",最高,读者扫一眼就知道那个位置代表总量。
x 轴是 12 个中文月份标签,JCategoryAxis 收进去就行:
JCategoryAxis xAxis = new JCategoryAxis();
xAxis.data("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月");
option.xAxis(xAxis);
option.yAxis(new JValueAxis());
JValueAxis 空构造,纵轴范围交给渲染器自己算,最大值会顶到 240 左右——受理 12 月的 226 是全年峰值,柱子差一点点碰到顶,视觉上刚好。
option.tooltip().trigger(JTrigger.axis) 这一行也在 Demo 里。柱图的分组结构下按轴触发是常规做法,不过 PDF 是静态的,悬停交互不存在,这行留着只是让配置结构和 Web 端保持一致。
从 SVG 到 PDF:三行代码
String svg = JChartRendererFactory.renderChart(JChartType.BAR, option);
枚举是 BAR,全大写。返回的是 SVG 字符串,不是图片文件,也不是 Base64。
后面三行是固定动作:建目录、开流、写字节。
JPdfConfig config = new JPdfConfig();
config.getLayoutConfig().setFlexLayout(true);
JQuickPdfFactory factory = new JQuickPdfFactory(config);
factory.bind("svg", svg);
byte[] bytes = factory.executeResource("report/biz/17_project_approval.txt");
setFlexLayout(true) 得在构造工厂之前调,这是这份模板能正常排版的前提。整份模板里 display:flex 只出现一次,就是图表容器那行 justifyContent:center;开关不打开,本来居中的图会贴到左边,整页重心跟着歪。
bind("svg", svg) 的名字对应模板里的 ${svg},不带花括号和美元符。executeResource 的路径是 classpath 相对路径,这份模板在 src/test/resources/report/biz/ 下,入参写 report/biz/17_project_approval.txt。
模板里的四处取舍
抬头走的是居中三行,不是左右分栏。textAlignment:center 加 #2F5597 实底、borderRadius:4px、padding:14px,标题 18pt 白色,下面两行 10pt 白色写统计周期和编制单位加报送对象。整个色块是"封面"的逻辑,视线从正中垂下来,直接落到第一根柱子上。文档型月报用这种抬头更稳,因为下面的表格和柱图都是满宽布局,抬头再分左右栏会和正文抢重心。
图表区没有包底色面板。柱图本身的视觉分量就重,外面再套一层浅底卡片,页面会显得碎;这份模板让图直接坐在白底上,靠章节标题上方的留白和一行图注把区域框住。
表格是全边框加斑马纹。五列宽度 120 / 110 / 110 / 110 / 110,加起来正好 560px,表头深蓝反白,数据行奇偶交替用 #EAF1F8。合计行没有反白,只加粗,靠"整行文字变粗"来收口——这是全表和表头唯一的呼应方式,克制得很。
字号全篇用 pt:18pt、13pt、10pt、9pt、8pt。一份模板里单位要统一,混着写会出现"标题看着比预期大一号"的问题。
数一遍:2,123 件和 79 件在办
模板里的合计数是硬编码的,我拿代码里的数据核了一遍。
受理四个季度:142+138+155=435,163+158+171=492,182+176+195=553,203+214+226=643。全年 2,123,和合计行一致。通过:346、400、456、536,合计 1,738。退回:66、74、80、86,合计 306。
对的时候发现一个值得说的地方:受理不等于通过加退回。一季度 435 减 346 减 66 等于 23,二季度 18,三季度 17,四季度 21,全年差 79 件。这 79 件是"在办"——期末还挂在流程里、既没通过也没退回的项目。这个差值不是数据错误,是审批流程的正常沉淀,但它意味着通过率的分母只能是受理量,不能拿通过加退回当分母。模板算的 81.9% 就是 1,738 ÷ 2,123,逻辑是对的。
其余几个数也对得上:四季度受理 643 对一季度 435,增长 47.8%;通过率从 79.5% 爬到 83.4%;退回占比从 15.2% 降到 13.4%(66÷435 和 86÷643);全年退回 306 件除以 12 个月等于 25.5 件。结论区的五条文字,每一条都能在表里找到出处。
这一步不能省。这份模板的结论里写死了十几个数字,改一次源数据就得回头把这十几处过一遍,少改一处,PDF 里就会出现"图上 12 月是峰值、结论里说 11 月最高"的硬伤。
落地前先定下来的两件事
一件事是口径。受理量到底含不含撤件、退回补正算不算一次重新受理、在办余额要不要单独列一行——这三个问题不定,图表画得再准也会被业务方推翻。这份 Demo 的处理是把在办留在差额里不显式呈现,简单,但月报看久了总会有人问"为什么通过加退回不等于受理"。我后来的做法是在表下加一行"期末在办",把 23 / 18 / 17 / 21 摆出来,争议立刻消失。
另一件事是数据源。现在这份代码里的 36 个数字是写死的,真实场景要从审批系统按自然月聚合,SQL 里 GROUP BY 月份加状态,出来的结果按状态拆成三个数组,顺序对齐 1 到 12 月。中间任何一个月没有数据,要补 0 而不是跳过——跳过会让后面的月份整体前移,x 轴标签就对不上了。
复现清单
- 依赖
io.github.paohaijiao:jquick-pdfx:4.7.0+(Apache-2.0 协议); new JOption(),链式设标题与副标,tooltip().trigger(JTrigger.axis);JCategoryAxis.data(...)排 12 个月标签,option.yAxis(new JValueAxis());- 三个
JBar依次name()和data(),顺序按受理、通过、退回,再option.series(...); JChartRendererFactory.renderChart(JChartType.BAR, option)拿 SVG 字符串;JPdfConfig开setFlexLayout(true)后构造JQuickPdfFactory,先bind("svg", svg)再executeResource("report/biz/17_project_approval.txt");- 建目录写字节,验证
D:\test\biz\17_project_approval.pdf; - 核对季度合计 2,123 / 1,738 / 306 与在办差额 79 件。
完整源码附录
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) [2025-2099] Martin (goudingcheng@gmail.com)
*/
package com.github.paohaijiao.biz.chart;
import com.github.paohaijiao.JOption;
import com.github.paohaijiao.axis.JCategoryAxis;
import com.github.paohaijiao.axis.JValueAxis;
import com.github.paohaijiao.code.JTrigger;
import com.github.paohaijiao.config.JPdfConfig;
import com.github.paohaijiao.demo.constant.JQuickConstant;
import com.github.paohaijiao.enums.JChartType;
import com.github.paohaijiao.executor.JQuickPdfFactory;
import com.github.paohaijiao.factory.JChartRendererFactory;
import com.github.paohaijiao.series.JBar;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 业务场景 Demo 17:项目审批数量趋势(政务/项目监管平台)
*
* <p>业务场景:项目监管平台按月统计「受理 / 审批通过 / 退回补正」三类的项目审批数量,
* 用于观察审批负荷与办理质量的变化。</p>
* <p>实际图表类型:{@link JChartType#BAR} 柱状图(分组柱,用于对比三类审批结果)。</p>
*
* <p>模板:{@code report/biz/17_project_approval.txt};图表以 SVG 文本形式绑定到模板的
* {@code ${svg}} 占位符,输出到 {@code D:\test\biz\17_project_approval.pdf}。</p>
*
* <p>图表尺寸说明:图表按 700pt × 380pt(宽 × 高)的横版比例设计;模板中 {@code <svg>}
* 只声明宽度、不声明高度,因此宽度被页面自动收窄时高度会按原比例同步缩放,不会纵向拉伸变形。</p>
*
* @author Martin
* @version 1.0.0
*/
public class JQuickBiz17ProjectApprovalTest {
public static final String path = JQuickConstant.path;
@Test
public void biz17ProjectApproval() throws IOException {
JOption option = new JOption();
option.title().text("2024年项目审批数量趋势").subtext("受理 / 审批通过 / 退回补正(单位:件)");
option.tooltip().trigger(JTrigger.axis);
JCategoryAxis xAxis = new JCategoryAxis();
xAxis.data("1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月");
option.xAxis(xAxis);
option.yAxis(new JValueAxis());
// 三类审批结果各一根柱,沿用政务系统低饱和主色
JBar accepted = new JBar();
accepted.name("受理").data(142, 138, 155, 163, 158, 171, 182, 176, 195, 203, 214, 226);
JBar approved = new JBar();
approved.name("审批通过").data(112, 110, 124, 132, 128, 140, 150, 145, 161, 169, 178, 189);
JBar returned = new JBar();
returned.name("退回补正").data(22, 20, 24, 25, 23, 26, 27, 25, 28, 27, 29, 30);
option.series(accepted, approved, returned);
String svg = JChartRendererFactory.renderChart(JChartType.BAR, option);
File dir = new File(path + "biz");
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fileOutputStream = new FileOutputStream(path + "biz/17_project_approval.pdf");
JPdfConfig config = new JPdfConfig();
// 开启 flex 行布局,使模板中的 display:flex 生效(图表居中)
config.getLayoutConfig().setFlexLayout(true);
JQuickPdfFactory factory = new JQuickPdfFactory(config);
factory.bind("svg", svg);
byte[] bytes = factory.executeResource("report/biz/17_project_approval.txt");
fileOutputStream.write(bytes);
fileOutputStream.close();
}
}
<pdf>
<body>
<div style="textAlignment:center; marginBottom:10px; padding:14px; background:#2F5597; borderRadius:4px; keepTogether:true">
<h1 style="textAlignment:center; color:white; marginBottom:6px; fontSize:18pt; bold:true">'项目审批数量趋势月报'</h1>
<p style="color:white; fontSize:10pt; margin:2px">'统计周期:2024年1月 - 2024年12月'</p>
<p style="color:white; fontSize:10pt; margin:2px">'编制单位:市政务服务管理局 | 报送对象:项目监管联席会议'</p>
</div>
<div style="marginBottom:10px">
<h2 style="color:#2F5597; fontSize:13pt; bold:true; marginBottom:6px">'一、月度审批数量趋势'</h2>
<div style="display:flex; justifyContent:center">
<svg style="width:700pt">${svg}</svg>
</div>
<p style="fontSize:9pt; color:#6c757d; italic:true; marginTop:6px">'图 1 2024年1-12月受理 / 审批通过 / 退回补正项目数量(数据来源:市项目审批监管系统)'</p>
</div>
<div style="marginBottom:10px">
<h2 style="color:#2F5597; fontSize:13pt; bold:true; marginBottom:6px">'二、季度审批结果汇总'</h2>
<table style="width:560px; fontSize:10pt">
<tr>
<th style="width:120px; backgroundColor:#2F5597; color:white; padding:6px; textAlign:center; border:1px solid #DCE3EC">'季度'</th>
<th style="width:110px; backgroundColor:#2F5597; color:white; padding:6px; textAlign:center; border:1px solid #DCE3EC">'受理(件)'</th>
<th style="width:110px; backgroundColor:#2F5597; color:white; padding:6px; textAlign:center; border:1px solid #DCE3EC">'审批通过(件)'</th>
<th style="width:110px; backgroundColor:#2F5597; color:white; padding:6px; textAlign:center; border:1px solid #DCE3EC">'退回补正(件)'</th>
<th style="width:110px; backgroundColor:#2F5597; color:white; padding:6px; textAlign:center; border:1px solid #DCE3EC">'通过率'</th>
</tr>
<tr>
<td style="padding:5px; border:1px solid #DCE3EC">'第一季度'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'435'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'346'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'66'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'79.5%'</td>
</tr>
<tr>
<td style="padding:5px; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'第二季度'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'492'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'400'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'74'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'81.3%'</td>
</tr>
<tr>
<td style="padding:5px; border:1px solid #DCE3EC">'第三季度'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'553'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'456'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'80'</td>
<td style="padding:5px; textAlign:right; border:1px solid #DCE3EC">'82.5%'</td>
</tr>
<tr>
<td style="padding:5px; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'第四季度'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'643'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'536'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'86'</td>
<td style="padding:5px; textAlign:right; backgroundColor:#EAF1F8; border:1px solid #DCE3EC">'83.4%'</td>
</tr>
<tr>
<td style="padding:6px; bold:true; border:1px solid #DCE3EC">'全年合计'</td>
<td style="padding:6px; textAlign:right; bold:true; border:1px solid #DCE3EC">'2,123'</td>
<td style="padding:6px; textAlign:right; bold:true; border:1px solid #DCE3EC">'1,738'</td>
<td style="padding:6px; textAlign:right; bold:true; border:1px solid #DCE3EC">'306'</td>
<td style="padding:6px; textAlign:right; bold:true; border:1px solid #DCE3EC">'81.9%'</td>
</tr>
</table>
</div>
<div style="marginBottom:10px">
<h2 style="color:#2F5597; fontSize:13pt; bold:true; marginBottom:6px">'三、审批运行结论'</h2>
<list style="listStyleType:disc; paddingLeft:16px; fontSize:10pt">
<li style="marginBottom:5px"><span style="bold:true; color:#2F5597">'审批总量稳步上行:'</span>'全年受理项目 2,123 件、审批通过 1,738 件,四季度受理量较一季度增长 47.8%(643 件对 435 件)。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#2F5597">'通过率逐季提升:'</span>'通过率由一季度 79.5% 升至四季度 83.4%,全年 81.9%;退回补正占比由 15.2% 降至 13.4%。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#2F5597">'退回补正高位平稳:'</span>'全年退回补正 306 件,月均 25.5 件,四季度 86 件为年内高点,材料完整性仍是主要影响因素。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#2F5597">'年末高峰特征明显:'</span>'12月受理 226 件、审批通过 189 件,均为全年单月峰值,与年度项目集中申报节奏一致。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#2F5597">'建议:'</span>'在年末申报高峰前增设预审窗口,并完善线上材料校验规则,力争将退回补正占比压降至 12% 以内。'</li>
</list>
</div>
<div style="textAlignment:center; marginTop:10px; paddingTop:10px; borderTop:1px solid #dee2e6; color:#6c757d; fontSize:8pt">
<p>'数据来源:市项目审批监管系统 | 生成时间:2025年1月10日'</p>
<p>'内部资料 · 未经许可请勿外传'</p>
</div>
</body>
</pdf>