Stock business tiering (asset operations) — generate this PDF with Java (Bubble)
Bubble2 页 A4 · 152 KB
Places existing business on balance, yield and customer count axes and tiers them with a bubble chart.

Output specs
- PDF pages
- 2
- Chart type
- Bubble
- Template lines
- 99
- Source lines
- 165
Java source · JQuickBiz45StockBubbleTest.java
This is the complete test class that produced the PDF above — copy it and run as-is; see the quick-start section for the Maven dependency.
/*
* 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 ([email protected])
*/
package com.github.paohaijiao.demo.biz;
import com.github.paohaijiao.JOption;
import com.github.paohaijiao.bubble.CategoryAxis;
import com.github.paohaijiao.bubble.ScatterSeries;
import com.github.paohaijiao.bubble.ValueAxis;
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 org.junit.Test;
import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 业务场景 Demo 45:存量业务分层(资产运营 / 存量客户经营)
*
* <p>业务场景:存量业务管理按账龄(月)与综合收益率(%)对存量资产分层,气泡大小表示存量规模,
* 颜色表示分层(核心 / 成长 / 观察),用于存量资产的结构诊断与经营策略调整。</p>
* <p>实际图表类型:{@link JChartType#Bubble} 气泡图。选题原拟用散点图,但 Demo 29 已使用
* {@link JChartType#SCATTER} 散点图,为避免图表类型重复,本 Demo 改用气泡图——气泡图在散点
* 二维坐标基础上增加了"气泡大小=存量规模"这一维度,能同时表达账龄、收益率与规模三重信息。</p>
*
* <p>模板:{@code report/biz/45_stock_bubble.txt};图表以 SVG 文本形式绑定到模板的
* {@code ${svg}} 占位符,输出到 {@code D:\test\biz\45_stock_bubble.pdf}。</p>
*
* <p>图表尺寸说明:图表按 700pt × 400pt(宽 × 高)的横版比例设计;模板中 {@code <svg>}
* 只声明宽度、不声明高度,因此宽度被页面自动收窄时高度会按原比例同步缩放,不会纵向拉伸变形。</p>
*
* @author Martin
* @version 1.0.0
*/
public class JQuickBiz45StockBubbleTest {
public static final String path = JQuickConstant.path;
@Test
public void biz45StockBubble() throws IOException {
JOption option = new JOption();
option.title().text("存量业务分层分析").subtext("账龄(月)× 综合收益率(%)× 存量规模");
// 图例取值必须与数据点 category 完全一致
option.legend("核心业务", "成长业务", "观察业务");
option.xAxis(new CategoryAxis().name("账龄(月)"));
option.yAxis(new ValueAxis().name("综合收益率(%)"));
ScatterSeries series = new ScatterSeries("存量规模");
List<Map<String, Object>> seriesData = new ArrayList<>();
// 核心业务:账龄长、收益率中等、存量规模大
Map<String, Object> p1 = new HashMap<>();
p1.put("x", "12");
p1.put("y", 4.8);
p1.put("size", 860);
p1.put("category", "核心业务");
p1.put("name", "客户A");
p1.put("color", new Color(40, 53, 147, 180));
seriesData.add(p1);
Map<String, Object> p2 = new HashMap<>();
p2.put("x", "24");
p2.put("y", 4.5);
p2.put("size", 1240);
p2.put("category", "核心业务");
p2.put("name", "客户B");
p2.put("color", new Color(40, 53, 147, 180));
seriesData.add(p2);
Map<String, Object> p3 = new HashMap<>();
p3.put("x", "36");
p3.put("y", 4.2);
p3.put("size", 980);
p3.put("category", "核心业务");
p3.put("name", "客户C");
p3.put("color", new Color(40, 53, 147, 180));
seriesData.add(p3);
// 成长业务:账龄中等、收益率较高、存量规模中等
Map<String, Object> p4 = new HashMap<>();
p4.put("x", "6");
p4.put("y", 6.2);
p4.put("size", 420);
p4.put("category", "成长业务");
p4.put("name", "客户D");
p4.put("color", new Color(92, 107, 192, 180));
seriesData.add(p4);
Map<String, Object> p5 = new HashMap<>();
p5.put("x", "15");
p5.put("y", 5.8);
p5.put("size", 560);
p5.put("category", "成长业务");
p5.put("name", "客户E");
p5.put("color", new Color(92, 107, 192, 180));
seriesData.add(p5);
Map<String, Object> p6 = new HashMap<>();
p6.put("x", "27");
p6.put("y", 5.4);
p6.put("size", 640);
p6.put("category", "成长业务");
p6.put("name", "客户F");
p6.put("color", new Color(92, 107, 192, 180));
seriesData.add(p6);
// 观察业务:账龄短、收益率高、存量规模小
Map<String, Object> p7 = new HashMap<>();
p7.put("x", "3");
p7.put("y", 7.6);
p7.put("size", 180);
p7.put("category", "观察业务");
p7.put("name", "客户G");
p7.put("color", new Color(197, 202, 233, 180));
seriesData.add(p7);
Map<String, Object> p8 = new HashMap<>();
p8.put("x", "9");
p8.put("y", 7.2);
p8.put("size", 260);
p8.put("category", "观察业务");
p8.put("name", "客户H");
p8.put("color", new Color(197, 202, 233, 180));
seriesData.add(p8);
Map<String, Object> p9 = new HashMap<>();
p9.put("x", "21");
p9.put("y", 6.9);
p9.put("size", 210);
p9.put("category", "观察业务");
p9.put("name", "客户I");
p9.put("color", new Color(197, 202, 233, 180));
seriesData.add(p9);
series.data(seriesData.toArray());
option.series(series);
String svg = JChartRendererFactory.renderChart(JChartType.Bubble, option);
File dir = new File(path + "biz");
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fileOutputStream = new FileOutputStream(path + "biz/45_stock_bubble.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/45_stock_bubble.txt");
fileOutputStream.write(bytes);
fileOutputStream.close();
}
}Minimal invocation (3 lines)
Skip the full test class — the core is these three lines: bind the chart SVG, execute the template, get the PDF bytes. Copy them straight into your project.
// config 可省略;svg 来自 JChartRendererFactory.renderChart(...),见下方完整源码
factory.bind("svg", svg);
byte[] pdf = new JQuickPdfFactory(config).executeResource("report/biz/45_stock_bubble.txt");Template file · 45_stock_bubble.txt
An HTML-like template with inline CSS that renders to PDF once bound to data. The full template is below — copy it or download the raw file.
<pdf>
<body>
<div style="background:#283593; padding:14px; borderRadius:4px; marginBottom:10px; keepTogether:true">
<div style="display:flex; justifyContent:space-between">
<div style="width:300px">
<h1 style="color:white; fontSize:18; bold:true; marginBottom:4px">'存量业务分层分析表'</h1>
<p style="color:#B9C0E8; fontSize:9">'账龄(月)× 综合收益率(%)× 存量规模'</p>
</div>
<div style="width:240px; textAlignment:right; borderLeft:1px solid #6E78C0; paddingLeft:10px">
<p style="color:white; fontSize:9">'统计周期:截至2024年三季度末'</p>
<p style="color:#B9C0E8; fontSize:9">'编制单位:存量业务经营部'</p>
<p style="color:#B9C0E8; fontSize:9">'报送对象:资产结构分析例会'</p>
</div>
</div>
</div>
<div style="display:flex; justifyContent:space-between; marginBottom:12px; keepTogether:true">
<div style="width:132px; minHeight:54px; background:#EAEBF8; borderRadius:4px; padding:8px; verticalAlignment:middle">
<p style="color:#283593; fontSize:16; bold:true; textAlignment:center; marginBottom:2px">'4,780'</p>
<p style="color:#5A6B7C; fontSize:9; textAlignment:center">'存量规模合计(万元)'</p>
</div>
<div style="width:132px; minHeight:54px; background:#EAEBF8; borderRadius:4px; padding:8px; verticalAlignment:middle">
<p style="color:#283593; fontSize:16; bold:true; textAlignment:center; marginBottom:2px">'5.12%'</p>
<p style="color:#5A6B7C; fontSize:9; textAlignment:center">'综合收益率'</p>
</div>
<div style="width:132px; minHeight:54px; background:#EAEBF8; borderRadius:4px; padding:8px; verticalAlignment:middle">
<p style="color:#283593; fontSize:16; bold:true; textAlignment:center; marginBottom:2px">'20.3'</p>
<p style="color:#5A6B7C; fontSize:9; textAlignment:center">'平均账龄(月)'</p>
</div>
<div style="width:132px; minHeight:54px; background:#EAEBF8; borderRadius:4px; padding:8px; verticalAlignment:middle">
<p style="color:#283593; fontSize:16; bold:true; textAlignment:center; marginBottom:2px">'64.4%'</p>
<p style="color:#5A6B7C; fontSize:9; textAlignment:center">'核心业务占比'</p>
</div>
</div>
<h2 style="color:#283593; fontSize:13; bold:true; borderBottom:1px solid #283593; paddingBottom:3px; marginBottom:8px">'一、存量业务分层气泡分布'</h2>
<div style="display:flex; justifyContent:center">
<svg style="width:700pt">${svg}</svg>
</div>
<p style="fontSize:9; color:#6c757d; italic:true; marginTop:6px">'图 1 分层气泡图:横轴为账龄(月),纵轴为综合收益率(%),气泡大小表示存量规模,颜色表示分层(核心 / 成长 / 观察)'</p>
<h2 style="color:#283593; fontSize:13; bold:true; borderBottom:1px solid #283593; paddingBottom:3px; marginBottom:8px; marginTop:10px">'二、分层关键指标一览'</h2>
<table style="width:540px; fontSize:10">
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'核心业务存量'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'3,080 万元'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'核心业务占比'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'64.4%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'核心业务平均账龄'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'24.0 个月'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'核心业务收益率'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'4.50%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'成长业务存量'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'1,620 万元'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'成长业务占比'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'33.9%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'成长业务平均账龄'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'16.0 个月'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'成长业务收益率'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'5.80%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'观察业务存量'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'650 万元'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'观察业务占比'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'13.6%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'观察业务平均账龄'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'11.0 个月'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC">'观察业务收益率'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC">'7.23%'</td>
</tr>
<tr>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC; bold:true">'分层合计存量'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC; bold:true">'4,780 万元'</td>
<td style="width:150px; backgroundColor:#EAEBF8; padding:5px; border:1px solid #DCE3EC; bold:true">'加权综合收益率'</td>
<td style="width:120px; padding:5px; textAlign:right; border:1px solid #DCE3EC; bold:true">'5.12%'</td>
</tr>
</table>
<h2 style="color:#283593; fontSize:13; bold:true; borderBottom:1px solid #283593; paddingBottom:3px; marginBottom:8px; marginTop:10px">'三、分层经营结论'</h2>
<list style="paddingLeft:18px; fontSize:10" listType=ordered>
<li style="marginBottom:5px"><span style="bold:true; color:#283593">'核心业务是收益基本盘但性价比偏低:'</span>'存量 3,080 万元占 64.4%,气泡最大、账龄最长(平均 24 个月),综合收益率 4.50% 为三层最低。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#283593">'成长业务性价比最优:'</span>'以 33.9% 的存量占比贡献 5.80% 的收益率,较核心业务高 1.30 个百分点,账龄 16 个月处于风险可控区间。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#283593">'观察业务高收益小规模:'</span>'收益率 7.23% 为三层最高,但存量仅 650 万元、账龄最短(平均 11 个月),客户稳定性与到期续作存在不确定性。'</li>
<li style="marginBottom:5px"><span style="bold:true; color:#283593">'结构性风险提示:'</span>'核心业务中账龄 24 个月以上的 2 户存量合计 2,220 万元,若收益率继续下行将拉低整体资产回报,建议适度向成长业务腾挪额度。'</li>
</list>
<p style="textAlignment:right; color:#8a939b; fontSize:8; marginTop:10px">'注:存量规模按期末本金余额统计,综合收益率按加权平均口径折算;分层标准详见《存量业务分层管理办法》。'</p>
</body>
</pdf>More demos in this group
The PDF, preview image, template and source code on this page were all produced by a real JQuick-Pdf run and are free to copy into your own project.