Donut Chart of Enterprise Credit Grade Distribution
jquick-pdf by example — demo #20 of 184. Every article in this series ships one complete, runnable demo: a template file and a Java class. No snippets, no omissions.
Grade distribution with the total enterprise count printed in the donut hole.
This document belongs to the Business chart series set. Like every demo in the series it has its own colour scheme and layout combination, derived deterministically from the demo number — so the look is stable across runs and two neighbouring documents never look alike.
What you'll build
- A PDF written to
D:/test/biz/20_credit_grade_circle.pdf - A template file (
report/biz/20_credit_grade_circle.txt) with 1 bound placeholder - A detail grid of 7 rows × 4 columns, laid out with
width:100%so it fills the text area - One self-contained Java class (
JQuickBiz20CreditGradeCircleTest) that binds the data and renders the file
Bound placeholders
${svg} Prerequisites
Java 8+ and one dependency — jquick-pdf is built on Apache PDFBox, not iText:
<dependency> <groupId>io.github.paohaijiao</groupId> <artifactId>jquick-pdfx</artifactId> <version>4.2.0</version> </dependency> Coordinates: io.github.paohaijiao:jquick-pdfx:4.2.0.
Step 1 — the template file
JPdfX is jquick-pdf's HTML-like template language. It lives in its own file (src/test/resources/report/biz/20_credit_grade_circle.txt) instead of a Java string, so designers can edit it without touching code.
<pdf> <body> <div style="marginBottom:10px; borderBottom:1px solid #B9C2CC; paddingBottom:8px; keepTogether:true"> <h1 style="color:#243B53; fontSize:17pt; bold:true; marginBottom:3px">'企业信用等级分布'</h1> <p style="color:#6c757d; fontSize:9pt">'统计周期:2025年三季度 | 数据来源:信用信息平台评级库'</p> </div> <div style="marginBottom:10px"> <div style="backgroundColor:#E8F0F7; padding:6px 10px; borderRadius:3px; marginBottom:6px"> <h2 style="color:#1F4E79; fontSize:13pt; bold:true; marginTop:0px; marginBottom:0px">'一、等级结构概览'</h2> </div> <div style="display:flex; justifyContent:space-between; marginBottom:10px"> <div style="width:285px"> <div style="display:flex; justifyContent:center"> <svg style="width:285px">${svg}</svg> </div> </div> <div style="width:265px"> <h3 style="color:#1F4E79; fontSize:12pt; bold:true; marginBottom:6px">'指标说明'</h3> <list style="listStyleType:disc; paddingLeft:14px; fontSize:9pt"> <li style="marginBottom:5px"><span style="bold:true; color:#1F4E79">'入库企业总数:'</span>'9,080 户,覆盖全省 21 个市(州)。'</li> <li style="marginBottom:5px"><span style="bold:true; color:#1F4E79">'等级口径:'</span>'AAA / AA / A / BBB / BB 及以下共五档,按季度评级结果统计。'</li> <li style="marginBottom:5px"><span style="bold:true; color:#1F4E79">'A 级占比最高:'</span>'A 级 3,180 户、占比 35.0%,是等级分布的主体。'</li> <li style="marginBottom:5px"><span style="bold:true; color:#1F4E79">'优质主体储备:'</span>'AA 级及以上合计 3,740 户、占比 41.2%。'</li> <li style="marginBottom:5px"><span style="bold:true; color:#1F4E79">'低等级主体:'</span>'BB 及以下 640 户、占比 7.0%,风险敞口整体可控。'</li> </list> </div> </div> </div> <div style="marginBottom:10px"> <div style="backgroundColor:#E8F0F7; padding:6px 10px; borderRadius:3px; marginBottom:6px"> <h2 style="color:#1F4E79; fontSize:13pt; bold:true; marginTop:0px; marginBottom:0px">'二、等级明细数据'</h2> </div> <table style="width:540px; fontSize:9pt"> <tr> <td style="width:150px; padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'入库企业总数'</td> <td style="width:120px; padding:6px; textAlign:right; border:1px solid #D6DEE8">'9,080 户'</td> <td style="width:150px; padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'覆盖市(州)数'</td> <td style="width:120px; padding:6px; textAlign:right; border:1px solid #D6DEE8">'21 个'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AAA 级企业'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'1,280 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AAA 占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'14.1%'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AA 级企业'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'2,460 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AA 占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'27.1%'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'A 级企业'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'3,180 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'A 占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'35.0%'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'BBB 级企业'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'1,520 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'BBB 占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'16.7%'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'BB 及以下企业'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'640 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'BB 及以下占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'7.0%'</td> </tr> <tr> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AA 级及以上合计'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'3,740 户'</td> <td style="padding:6px; backgroundColor:#E8F0F7; border:1px solid #D6DEE8">'AA 级及以上占比'</td> <td style="padding:6px; textAlign:right; border:1px solid #D6DEE8">'41.2%'</td> </tr> </table> </div> <div style="marginBottom:10px"> <div style="backgroundColor:#E8F0F7; padding:6px 10px; borderRadius:3px; marginBottom:6px"> <h2 style="color:#1F4E79; fontSize:13pt; bold:true; marginTop:0px; marginBottom:0px">'三、结构特征与建议'</h2> </div> <list style="listStyleType:none; paddingLeft:0px; fontSize:10pt"> <li style="borderTop:1px dashed #C7C7C7; paddingTop:6px; marginBottom:4px"><span style="bold:true; color:#1F4E79">'结构呈“中间大、两头小”:'</span>'A 级 35.0%、AA 级 27.1%,两档合计 62.1%,而 AAA 级仅 14.1%,高等级主体占比偏低。'</li> <li style="borderTop:1px dashed #C7C7C7; paddingTop:6px; marginBottom:4px"><span style="bold:true; color:#1F4E79">'优质主体储备充足:'</span>'AA 级及以上共 3,740 户、占比 41.2%,为授信投放提供了较厚的客户基础。'</li> <li style="borderTop:1px dashed #C7C7C7; paddingTop:6px; marginBottom:4px"><span style="bold:true; color:#1F4E79">'低等级主体可控:'</span>'BB 及以下 640 户、占比 7.0%,建议纳入年度复审名单并提高抽查比例。'</li> <li style="borderTop:1px dashed #C7C7C7; paddingTop:6px; marginBottom:4px"><span style="bold:true; color:#1F4E79">'建议:'</span>'对 A 级企业开展“提级辅导”,力争两年内把 AA 级及以上占比提升至 50%。'</li> </list> <p style="textAlignment:right; color:#8a939b; fontSize:8pt">'注:等级数据按季度更新,占比四舍五入保留一位小数,合计可能存在 0.1 个百分点的尾差。'</p> </div> </body> </pdf> Step 2 — the Java class
jquick-pdfx/src/test/java/com/github/paohaijiao/biz/chart/JQuickBiz20CreditGradeCircleTest.java
/* * 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.combol.JCircleChartData; 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.List; /** * 业务场景 Demo 20:企业信用等级分布(信用平台) * * <p>业务场景:信用信息平台按季度汇总入库企业的信用等级(AAA / AA / A / BBB / BB 及以下), * 用于区域信用状况概览与授信政策制定。</p> * <p>实际图表类型:{@link JChartType#Circle} 环形图(中心显示企业总数,外环按等级占比展开)。</p> * * <p>模板:{@code report/biz/20_credit_grade_circle.txt};图表以 SVG 文本形式绑定到模板的 * {@code ${svg}} 占位符,输出到 {@code D:\test\biz\20_credit_grade_circle.pdf}。</p> * * <p>图表尺寸说明:图表按 500pt × 400pt(宽 × 高)的方形比例设计;模板中 {@code <svg>} * 只声明宽度、不声明高度,因此宽度被页面自动收窄时高度会按原比例同步缩放,不会纵向拉伸变形。</p> * * @author Martin * @version 1.0.0 */ public class JQuickBiz20CreditGradeCircleTest { public static final String path = JQuickConstant.path; @Test public void biz20CreditGradeCircle() throws IOException { JCircleChartData chartData = new JCircleChartData(); chartData.setTitleText("企业信用等级分布"); chartData.setSubtitleText("统计口径:入库企业信用评级(单位:户)"); chartData.setCenterTitle("企业总数"); chartData.setCenterUnit("户"); chartData.setFooterText("数据来源:信用信息平台"); // 五个信用等级扇形,颜色沿同一主色由深至浅,保持政务/企业系统低饱和风格 List<JCircleChartData.SectorData> sectors = new ArrayList<>(); sectors.add(new JCircleChartData.SectorData("AAA", 1280, new Color(31, 78, 121))); sectors.add(new JCircleChartData.SectorData("AA", 2460, new Color(62, 107, 157))); sectors.add(new JCircleChartData.SectorData("A", 3180, new Color(120, 152, 187))); sectors.add(new JCircleChartData.SectorData("BBB", 1520, new Color(168, 190, 214))); sectors.add(new JCircleChartData.SectorData("BB及以下", 640, new Color(205, 219, 234))); chartData.setSectorDataList(sectors); JOption option = new JOption(); option.setData(chartData); String svg = JChartRendererFactory.renderChart(JChartType.Circle, option); File dir = new File(path + "biz"); if (!dir.exists()) { dir.mkdirs(); } FileOutputStream fileOutputStream = new FileOutputStream(path + "biz/20_credit_grade_circle.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/20_credit_grade_circle.txt"); fileOutputStream.write(bytes); fileOutputStream.close(); } } Step 3 — run it
The class is a JUnit test, so run it with Maven:
mvn -pl jquick-pdfx test -Dtest=JQuickBiz20CreditGradeCircleTest …or call the test method from a main wrapper if you want a plain entry point:
public static void main(String[] args) throws Exception { new JQuickBiz20CreditGradeCircleTest().biz20CreditGradeCircle(); } Output: D:/test/biz/20_credit_grade_circle.pdf
How it works
Binding a chart SVG into the template
The chart is not a bitmap — JChartRendererFactory.renderChart(...) returns an SVG string, and the template embeds it with a single <svg> element bound to ${svg}. That means the chart stays vector-sharp at any zoom level, and it participates in normal flow layout instead of being stamped on top of the page.
- Declare only the width on
<svg>; the height is derived from the chart's aspect ratio, so the chart never stretches vertically. - Open
config.getLayoutConfig().setFlexLayout(true)if the template centers the chart withdisplay:flex. - Bind the SVG string with
factory.bind("svg", svg)—bind()is pure data injection and never re-parses markup.
String svg = JChartRendererFactory.renderChart(JChartType.LINE, option); JQuickPdfFactory factory = new JQuickPdfFactory(config); factory.bind("svg", svg); byte[] bytes = factory.executeResource(TEMPLATE); CJK font configuration
CJK text needs a font that actually contains the glyphs. The engine ships with a Chinese font and lets you switch it through JFontConfig; if a glyph is missing the character is silently dropped or drawn as a box, and the failure looks like a data problem rather than a font problem.
- Set the font once on
JPdfConfig.getFontConfig()before rendering. - Chart SVG text is a separate pipeline — register the same family on
JOptionor chart labels fall back to Helvetica. - Prefer the full-width
¥over¥; the latter is often absent from CJK faces.
JPdfConfig config = new JPdfConfig(); config.getFontConfig().setDefaultFontName("Noto Sans CJK SC"); Template syntax cheat sheet
| Syntax | Meaning |
|---|---|
<svg style="width:479pt">${svg}</svg> | Embed a vector chart produced by JChartRendererFactory. |
// comment | Line comment. Keep <!-- ... --> comments short — long ones can blank the render. |
<p style="fontSize:9pt; bold:true"> | Inline styles use camelCase keys, CSS-like values. |
<lineSeparator style="color:#2E5B8A; thickness:1.5f"> | Horizontal rule with colour and thickness. |
<list style="listStyleType:decimal"><li> | Ordered or unordered list items. |
Gotchas worth remembering
- A
<td>holds text only — nested<p>,<image>or<table>is dropped. - There is no
colspan; emit one<td>per column and use'—'as filler. -
setFontSizetakes afloat(8.5f);setMarginstakesList<Integer>. - The footer format accepts exactly one
%d.
Output
Wrap up
You now have a template file, a Java class and a PDF — all three reproducible from a clean clone. Swap the placeholder blocks for real data (or for real seal images) and the layout does not move.
Series navigation: ← #19 Pie Chart of Overdue Loan Share by Bucket · #21 Multi-Line Chart of Ticket Resolution SLA Trends →
Full index: README.md · Source: jquick-pdf
Appendix — files in this demo
| Role | Path |
|---|---|
| Java class | jquick-pdfx/src/test/java/com/github/paohaijiao/biz/chart/JQuickBiz20CreditGradeCircleTest.java |
| Template | jquick-pdfx/src/test/resources/report/biz/20_credit_grade_circle.txt |
| Output PDF | D:/test/biz/20_credit_grade_circle.pdf |
| Preview image | images/20.png |


