28 趋势分析报表:jquick-pdf折线图PDF生成实战
引入
SaaS 团队每月要输出活跃用户和收入趋势,报告除了图形本身,还必须保留统计周期与口径说明。折线图强调连续变化,适合回答“变大还是变小、变化是否稳定”这类问题,而表格只能给出逐月数字,趋势要靠读者自己连点成线。LineCharTest已经证实JLineChartsRenderer、分类轴、值轴与JLine.data的调用链,本文以月度活跃用户(MAU)为例,把按月聚合的序列渲染成折线 SVG,再嵌入 PDF 报告。折线图看似只是一条线,真正决定它可信度的是数据口径:同一个指标换一种定义,曲线的方向可能完全相反。阅读对象是需要输出趋势类报表的 Java 后端开发者。
核心讲解
折线图的语义:有序轴上的连续变化
折线图适合有时间顺序的连续数据:横轴是等间隔的时间点,纵轴是同一量纲的指标。判断依据是横轴符号是否具有先后关系——月份、星期、交易日有序;区域、渠道、部门没有天然顺序,用折线连接它们会暗示一种并不存在的连续性。三种常见图形的分工可以记成:饼图回答“占多少”,柱状图回答“谁更高”,折线图回答“走势如何”。
一个指标一张图
一张折线图只承载一个指标最稳妥。需要同时展示活跃用户与收入时,两个指标量纲不同,应拆成上下两张图,或把其中一张换算成指数后再并列,并在说明文字中标注换算方式,否则两条线的相对高低没有意义。
聚合口径决定结论
折线图画的是数值,但数值来自聚合,聚合规则一变,曲线就可能换一个方向。以月活为例,“去重登录用户”和“去重发生业务行为的用户”会给出不同的量级,也可能给出不同的月度涨跌;“截止月末”与“自然月内曾有登录”只差几天,却可能让跨月用户被归到下一个月。时区同样会影响落月位置,跨时区业务必须固定一个统计时区。因此图表旁边必须写明指标定义、统计周期与时区,否则趋势线只是一条好看的折线,读者无法判断它是否可信。
从序列到 SVG 再到模板
图表配置承载标题与提示触发方式;分类轴放时间标签;值轴描述数值尺度;序列用JLine写入name与data,其中name会出现在图例中,应使用可读的指标名而不是数据库字段名。渲染器JLineChartsRenderer输出 SVG;模板用<svg>${svg}</svg>占位,bind注入后由JQuickPdfFactory输出byte[]。其中图形能力来自jquick-pdf-svg,配置模型来自jquick-pdf-data,文档层核心是jquick-pdfx,jquick-pdf-css提供样式模型,jquick-pdf-font提供内置 CJK 字体。
关键细节
- 月份排序不能依赖字符串默认排序,否则会出现 10 月排在 2 月之前;应先按年月数值排序,再把标签写入分类轴。
- 缺失月份不能悄悄用零替代。补零会让曲线出现虚假的“断崖”,读者会误以为业务量归零;留断点则如实表达“该月无数据”。两种策略都不算错,但必须在报告里声明采用哪一种。
- 分类轴标签数量必须与数值个数一致,错位通常不抛异常,只会让整条趋势线失真。
- 极端异常值会压扁其余月份的波动幅度,应先核对数据来源,必要时在图外单独说明异常月份,而不是直接删点。
- 轴标签太密会重叠,应减少展示周期(按月改按季度)、缩短标签文案或拆页。
- 空序列要显式处理,输出“所选周期内无数据”的提示段落,而不是一张没有折线的空白图。
- 聚合 SQL 与图形的周期必须一致,不能查询结果是六行、图形却画了十二个月。
- 曲线上的每一次涨跌都应能在明细数据里找到原因,报告发布前抽查几个拐点最省事。
- 若同一页要展示多个指标,优先拆图而不是共用一条纵轴;确实要并列时,先把量纲换算成指数再画。
实战说明
依赖与模板
使用 JDK 8+,文档层与图表层分两个依赖引入:
<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>模板用<svg>${svg}</svg>占位,固定文本必须用单引号,变量用${name},两者不能混写。
完整 Java 示例
importcom.github.paohaijiao.JOption;importcom.github.paohaijiao.axis.JCategoryAxis;importcom.github.paohaijiao.axis.JValueAxis;importcom.github.paohaijiao.code.JTrigger;importcom.github.paohaijiao.config.JGraphConfig;importcom.github.paohaijiao.config.JPdfConfig;importcom.github.paohaijiao.data.JGraphContainer;importcom.github.paohaijiao.enums.JChartType;importcom.github.paohaijiao.executor.JQuickPdfFactory;importcom.github.paohaijiao.line.JLineChartsRenderer;importcom.github.paohaijiao.series.JLine;importjava.nio.file.Files;importjava.nio.file.Paths;importjava.nio.charset.StandardCharsets;publicclassLineReportDemo{publicstaticvoidmain(String[]args)throwsException{JOptionoption=newJOption();option.title().text("月活用户趋势");option.tooltip().trigger(JTrigger.axis);option.xAxis(newJCategoryAxis().data("1月","2月","3月","4月","5月","6月"));option.yAxis(newJValueAxis());option.series(newJLine().name("MAU").data(120,132,156,149,188,214));Stringtemplate="<pdf><body><h1>'用户增长趋势报表'</h1><svg>&{svg}</svg>"+"<p>'统计口径:去重登录用户;数据截止每月末。'</p></body></pdf>";JGraphContainergraphContainer=newJGraphContainer();graphContainer.setType(JChartType.LINE);graphContainer.setOption(option);JGraphConfiggraphConfig=newJGraphConfig();graphConfig.put("svg",graphContainer);JPdfConfigconfig=newJPdfConfig();config.setGraphConfig(graphConfig);byte[]pdf=newJQuickPdfFactory(config).executeContent(template);Files.write(Paths.get("d://test//line-report.pdf"),pdf);}}效果如下:
数据来源与绑定
趋势数据通常来自一条按时间分组的聚合查询,返回的每一行对应一个时间点。把它映射到分类轴和数值数组时,要先按时间排序,再分别填入标签与数值,两者顺序必须完全一致。如果查询结果可能缺少某些月份,应在 Java 侧补齐或保留断点,而不是让标签与数值错位。模板只负责呈现,排序、补值和单位换算都放在服务层完成,这样图形和导出数据始终来自同一份结果。
步骤拆解
按时间排序形成分类轴;数值数组必须与月份严格对齐;渲染器输出 SVG;读取后绑定到模板变量并输出 PDF;正文注明统计口径。示例中 4 月由 156 回落到 149,这条下降是数据本身的信息,不应为了“好看”而修订;标题只写指标名、不写结论,避免图形与文字互相矛盾。排查顺序建议是先看数据、再看图形、最后看版式:如果line.svg单独打开就是错的,问题在数据或图表配置;如果 SVG 正确而 PDF 里不对,问题就在变量绑定或模板结构。
生产与验收
保留原始查询与聚合 SQL 以便复核;对缺失值使用业务定义的补零或断点策略,并把选择写进报告;固定统计时区,限制单图数据点数;同时输出一份关键指标表,既方便无障碍阅读,也便于审计对账。嵌入后要实测分页,避免曲线被切到下一页;十二个月放进 A4 通常可读,二十四个月以上建议按季度聚合或拆成两张图。中文月份或指标名如果显示异常,交给jquick-pdf-font的内置字体处理。上线后重点监控数据点数、聚合耗时与渲染失败率,这三项基本能覆盖绝大多数异常。
总结
折线图的结论首先取决于口径,其次才是视觉样式。同一组数据在“去重登录用户”和“去重业务用户”两种定义下可能给出相反的趋势判断,因此口径说明必须与图形同页出现,这不是形式主义,而是让读者能够自行校验结论的唯一线索。它的适用边界是时间序列:不要把无序类别连成折线,也不要在同一张图里混排不同量纲的指标。本文只采用LineCharTest中已经证实的单JLine写法,不假设多系列或其它未验证方法。另外,趋势报表往往被反复查看,建议固定模板与统计口径的版本号,便于对比不同时期导出的报告。版本基线:jquick-pdfx 4.0.0、JDK 8+,升级前请核对 README_zh.md 的版本对照表;更多示例见 GitHub 仓库 官方地址jquick-pdf 。