jquick-pdf 生成项目监管月报:从 JCategoryAxis 到 700pt 画布的落地步骤

简介: jquick-pdf实战案例:用三根分组柱状图清晰呈现12个月“受理/通过/退回”审批数据,兼顾趋势对比与构成分析;含完整Java代码、SVG渲染、Flex模板排版及数据校验,助力政务报表高效落地。

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 渲染输出,非示意图):

image.png

数据装配是三步:建序列、灌数、挂到 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 轴标签就对不上了。

复现清单

  1. 依赖 io.github.paohaijiao:jquick-pdfx:4.7.0+(Apache-2.0 协议);
  2. new JOption(),链式设标题与副标,tooltip().trigger(JTrigger.axis);
  3. JCategoryAxis.data(...) 排 12 个月标签,option.yAxis(new JValueAxis());
  4. 三个 JBar 依次 name() 和 data(),顺序按受理、通过、退回,再 option.series(...);
  5. JChartRendererFactory.renderChart(JChartType.BAR, option) 拿 SVG 字符串;
  6. JPdfConfig 开 setFlexLayout(true) 后构造 JQuickPdfFactory,先 bind("svg", svg) 再 executeResource("report/biz/17_project_approval.txt");
  7. 建目录写字节,验证 D:\test\biz\17_project_approval.pdf;
  8. 核对季度合计 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>
相关文章
|
12天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)
4779 29
|
12天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
4518 51
|
7天前
|
存储 弹性计算 人工智能
2026 年阿里云双十一活动入口攻略,新客服务器优惠通道、三大会场入口梳理
综合来看,2026年阿里云双十一活动入口清晰: 阿里云轻量应用服务器、阿里云ECS云服务器、阿里云GPU云服务器、阿里云百炼大模型服务平台 活动会场、综合权益中心、99计划专场三大入口覆盖服务器、AI权益与续费特价;新用户通过控制台首页banner、权益中心、产品页三处路径进入专属专场,老用户通过控制台弹窗和费用中心权益中心进入老客专区,企业用户做企业实名后解锁专属满减与阶梯折扣。
830 0
|
13天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
2176 7
|
7天前
|
云安全 人工智能 安全
|
6天前
|
Linux 开发工具 iOS开发
2026最新codex下载安装保姆级使用教程(下载+安装+登录+使用,含所有历史版本)
本文详解OpenAI Codex国内快速上手指南:提供Win/macOS/Linux全平台免安装包、npm/Homebrew等多方式安装、账号/API Key双登录、简体中文切换,覆盖下载、安装、汉化、使用及高频避坑(如微软商店失败、npm超时等),10分钟即可跑通。
1526 3
|
4天前
|
人工智能 安全 API
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(dsh)是DeepSeek AI于2026年开源的轻量级Agent运行框架,基于Cordis内核,践行“一切皆插件”理念。支持Web/桌面双端,开箱即用;内置多模型接入、丰富插件生态(超3.5万),适配编程、办公、创作等场景。(239字)
1043 0
|
13天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
2204 6
|
6天前
|
人工智能 并行计算 调度
本地 AI 一键部署工具 LocModel:Windows 端集成画图、视频生成、Agent 的开源模型运行方案
LocModel 是专为 Windows 设计的一站式本地 AI 工具,支持零配置启动、自动硬件检测、GGUF 模型一键加载、文生图/视频生成及 Agent 自动化任务。适配 8G 显存,内置显存分片调度,轻松实现 AI 漫剧批量创作与离线大模型调用。(239 字)