DEV Community

paohaijiao
paohaijiao

Posted on

Gauge Chart for the Composite Credit Score

Gauge Chart for the Composite Credit Score

jquick-pdf by example — demo #9 of 184. Every article in this series ships one complete, runnable demo: a template file and a Java class. No snippets, no omissions.

A single dial that answers one question: how healthy is this borrower, on a 0-100 scale?

This document belongs to the Credit report — 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/creditreport/09_guage.pdf
  • A template file (report/credit_report_09_guage.txt) with 1 bound placeholder
  • A detail grid of 13 rows × 6 columns, laid out with width:100% so it fills the text area
  • One self-contained Java class (JQuickCreditReport09GuageTest) that binds the data and renders the file

Bound placeholders

${svg}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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/credit_report_09_guage.txt) instead of a Java string, so designers can edit it without touching code.

<pdf>
<body>
  <div style="textAlignment:center; marginBottom:22px">
    <lineSeparator style="strokeColor:#455A64"></lineSeparator>
    <h1 style="fontSize:24; color:#263238; textAlignment:center; bold:true; marginTop:12px; marginBottom:8px">'企业信用评级报告'</h1>
    <p style="fontSize:9; color:#90a4ae; characterSpacing:1; textAlignment:center; marginBottom:4px">'报告编号: CR-2023-08975  |  评估日期: 2023年11月15日'</p>
    <p style="fontSize:11; color:#263238; textAlignment:center; marginBottom:12px">'评估机构: 四川省企业信用评估中心'</p>
    <lineSeparator style="strokeColor:#455A64"></lineSeparator>
  </div>

  <div style="marginBottom:22px">
    <h2 style="fontSize:14; color:#263238; bold:true; marginBottom:10px">'四川省泡海椒科技有限公司'</h2>
    <table style="width:600px; fontSize:10">
      <tr>
        <th style="backgroundColor:#eceff1; color:#263238; bold:true; padding:7px; textAlignment:left; width:180px">'项目'</th>
        <th style="backgroundColor:#eceff1; color:#263238; bold:true; padding:7px; textAlignment:left; width:420px">'内容'</th>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'统一信用代码'</td>
        <td style="padding:7px; textAlignment:left">'91110108MA01XX1234'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'成立日期'</td>
        <td style="padding:7px; textAlignment:left">'2018年5月20日'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'注册资本'</td>
        <td style="padding:7px; textAlignment:left">'1000万元人民币'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'法定代表人'</td>
        <td style="padding:7px; textAlignment:left">'泡海椒'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'所属行业'</td>
        <td style="padding:7px; textAlignment:left">'软件和信息技术服务业'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; bold:true">'企业规模'</td>
        <td style="padding:7px; textAlignment:left">'中型(150-200人)'</td>
      </tr>
    </table>
  </div>

  <div style="marginBottom:22px">
    <h2 style="fontSize:13; color:#263238; bold:true; marginBottom:10px">'历年财务数据(单位:万元)'</h2>
    <table style="width:600px; fontSize:10">
      <tr>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:left; borderBottom:1px solid #263238">'财务指标'</th>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:right; borderBottom:1px solid #263238">'2020年'</th>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:right; borderBottom:1px solid #263238">'2021年'</th>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:right; borderBottom:1px solid #263238">'2022年'</th>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:right; borderBottom:1px solid #263238">'2023年'</th>
        <th style="backgroundColor:transparent; color:#263238; bold:true; padding:7px; textAlignment:right; borderBottom:1px solid #263238">'增长率'</th>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; borderBottom:1px solid #eceff1">'营业收入'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'8,560'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'12,340'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'18,920'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'25,680'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'+44.2%'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; borderBottom:1px solid #eceff1">'净利润'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'1,240'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'2,150'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'3,890'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'5,420'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'+63.8%'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; borderBottom:1px solid #eceff1">'总资产'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'15,800'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'22,450'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'31,200'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'42,800'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'+39.5%'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; borderBottom:1px solid #eceff1">'净资产'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'9,200'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'13,800'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'19,500'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'26,400'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'+42.1%'</td>
      </tr>
      <tr>
        <td style="padding:7px; textAlignment:left; borderBottom:1px solid #eceff1">'毛利率'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'42.5%'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'45.8%'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'48.2%'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'51.6%'</td>
        <td style="padding:7px; textAlignment:right; borderBottom:1px solid #eceff1">'+9.1%'</td>
      </tr>
    </table>
  </div>

  <div style="marginBottom:22px">
    <h2 style="fontSize:13; color:#263238; bold:true; marginBottom:12px">'关键经营指标'</h2>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'01'</div>
      <div style="width:540px; fontSize:11">'营收增长率:连续三年保持40%以上增长,2023年达到25,680万元'</div>
    </div>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'02'</div>
      <div style="width:540px; fontSize:11">'利润率提升:净利率从14.5%提升至21.1%,盈利能力显著增强'</div>
    </div>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'03'</div>
      <div style="width:540px; fontSize:11">'研发投入:研发费用年均增长38.9%,占营收比例达13.3%'</div>
    </div>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'04'</div>
      <div style="width:540px; fontSize:11">'人才结构:技术研发人员占比42%,硕士以上学历占35%'</div>
    </div>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'05'</div>
      <div style="width:540px; fontSize:11">'客户质量:服务15家上市公司,客户续约率92%,大客户满意度4.8/5.0'</div>
    </div>
    <div style="display:flex; marginBottom:14px">
      <div style="width:50px; fontSize:16; bold:true; color:#b0bec5">'06'</div>
      <div style="width:540px; fontSize:11">'资产质量:资产负债率维持在38%左右,流动比率2.8,偿债能力良好'</div>
    </div>
  </div>

  <div style="marginBottom:22px">
    <h2 style="fontSize:13; color:#263238; bold:true; marginBottom:12px">'综合评分仪表盘'</h2>
    <div style="display:flex; justifyContent:space-between">
      <svg style="width:400px;height:400px">${svg}</svg>
      <div style="width:300px; padding:12px; backgroundColor:#eceff1; borderRadius:2px">
        <h3 style="fontSize:12; color:#263238; bold:true; marginBottom:8px">'评级指标说明'</h3>
        <div style="fontSize:10; marginBottom:6px">
          <span style="color:#455A64; bold:true">'● 财务状况(90分): '</span>
          '营收稳定增长'
        </div>
        <div style="fontSize:10; marginBottom:6px">
          <span style="color:#2ecc71; bold:true">'● 经营能力(95分): '</span>
          '客户留存率高'
        </div>
        <div style="fontSize:10; marginBottom:6px">
          <span style="color:#e74c3c; bold:true">'● 偿债能力(85分): '</span>
          '负债率合理'
        </div>
        <div style="fontSize:10; marginBottom:6px">
          <span style="color:#f39c12; bold:true">'● 发展潜力(92分): '</span>
          '研发投入增加'
        </div>
      </div>
    </div>
  </div>

  <div style="marginBottom:22px; backgroundColor:#eceff1; borderRadius:2px; padding:20px; textAlignment:center">
    <h2 style="fontSize:13; color:#263238; bold:true; textAlignment:center; marginBottom:8px">'综合评价'</h2>
    <p style="fontSize:11; textAlignment:center; marginBottom:8px">
      '四川省泡海椒科技有限公司作为国家级高新技术企业和专精特新"小巨人"企业,在云计算和大数据领域表现出色。'
      '公司财务状况健康,经营能力突出,技术创新能力强,具备良好的发展潜力。信用评级为AAA级,展望稳定。'
    </p>
    <p style="fontSize:8; color:#90a4ae; textAlignment:center">'认证通过'</p>
  </div>

  <div style="textAlignment:center; marginTop:20px">
    <lineSeparator style="strokeColor:#455A64"></lineSeparator>
    <p style="fontSize:8; color:#b0bec5; textAlignment:center; marginTop:8px">'本报告依据公开信息和专业评估模型生成,仅供参考 | 报告生成时间: 2025年09月06日'</p>
    <p style="fontSize:8; color:#b0bec5; textAlignment:center">'© 2023 企业信用评估中心 版权所有 | 认证编号: CE-2023-001'</p>
  </div>
</body>
</pdf>
Enter fullscreen mode Exit fullscreen mode

Step 2 — the Java class

jquick-pdfx/src/test/java/com/github/paohaijiao/biz/report/JQuickCreditReport09GuageTest.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.report;

import com.github.paohaijiao.JOption;
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.guage.GuageConfig;
import com.github.paohaijiao.guage.JGuageOption;
import org.junit.Test;

import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 统一信用报告 —— 风格 09:仪表盘(石墨灰)
 *
 * <p>以 {@code report/credit_report_09_guage.txt} 为模板,把仪表盘渲染成 SVG 后
 * 绑定到模板中的 {@code ${svg}} 占位符,输出到 {@code D:\test\creditreport\09_guage.pdf}。</p>
 *
 * @author Martin
 * @version 1.0.0
 */
public class JQuickCreditReport09GuageTest {

    public static final String path = JQuickConstant.path;

    @Test
    @SuppressWarnings("deprecation")
    public void report09Guage() throws IOException {
        GuageConfig scoreConfig = GuageConfig.builder()
                .score(86)
                .pointerColor(new Color(69, 90, 100))
                .backgroundColor(new Color(240, 242, 245))
                .title("CREDIT SCORE")
                .build();
        JGuageOption guageOption = JGuageOption.builder().scoreMeter(scoreConfig).build();
        JOption option = new JOption();
        option.setGuageOption(guageOption);

        String svg = JChartRendererFactory.renderChart(JChartType.Guage, option);

        File dir = new File(path + "creditreport");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(path + "creditreport/09_guage.pdf");
        JPdfConfig config = new JPdfConfig();
        // 开启 flex 行布局:图表与其右侧的指标说明面板在宽度允许时并排,而不是换行堆叠。
        config.getLayoutConfig().setFlexLayout(true);
        JQuickPdfFactory factory = new JQuickPdfFactory(config);
        factory.bind("svg", svg);
        byte[] bytes = factory.executeResource("report/credit_report_09_guage.txt");
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3 — run it

The class is a JUnit test, so run it with Maven:

mvn -pl jquick-pdfx test -Dtest=JQuickCreditReport09GuageTest
Enter fullscreen mode Exit fullscreen mode

…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 JQuickCreditReport09GuageTest().report09Guage();
}
Enter fullscreen mode Exit fullscreen mode

Output: D:/test/creditreport/09_guage.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 with display: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);
Enter fullscreen mode Exit fullscreen mode

Flex layout: what it does and does not do

display:flex is opt-in: without config.getLayoutConfig().setFlexLayout(true) the children stack vertically. Even with it enabled, children whose fixed widths sum beyond the text area wrap to the next line — so a two-column layout must budget its pixels.

  • Enable flex on JPdfConfig before creating the factory.
  • Use fixed px widths that sum to less than the text width (~483px on A4 with 40pt margins).
  • Nested div two-column layouts are unreliable here — a full-width single column is safer.
JPdfConfig config = new JPdfConfig();
config.getLayoutConfig().setFlexLayout(true);
Enter fullscreen mode Exit fullscreen mode

Template syntax cheat sheet

Syntax Meaning
<list style="listStyleType:decimal"><li> Ordered or unordered list items.
<td style="padding:4px; border:1px solid #C3CDD8"> Cell styling. A <td> holds text only — no nested blocks.
factory.bind("k", v) Injects plain text into the context; markup inside a value is dropped.
'literal text' A single-quoted string is a literal — whatever is inside is printed verbatim.
${var} An expression. It must sit outside the quotes: 'No: '${docNo}.

Gotchas worth remembering

  • Keep <!-- ... --> comments short — long ones can silently blank the render.
  • Use the full-width ¥, not ¥, or the character may render as ?.
  • Compute money with BigDecimal, round once, and derive 大写 from the rounded value.
  • Flex children whose fixed widths exceed the text area wrap instead of sitting side by side.

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: ← #8 Funnel Chart: Where Credit Applications Drop Off · #10 Heatmap of Monthly Revenue Across Regions →

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/report/JQuickCreditReport09GuageTest.java
Template jquick-pdfx/src/test/resources/report/credit_report_09_guage.txt
Output PDF D:/test/creditreport/09_guage.pdf
Preview image images/09.png

Top comments (4)

Collapse
 
fgrandon profile image
Francisco Grandón •

Separating the JPdfX template markup from the Java runner while keeping the rendering engine on Apache PDFBox instead of iText is a clean architectural choice for automated report generation. Binding the vector gauge directly through the ${svg} placeholder keeps the output crisp without bitmap scaling artifacts. How does jquick-pdf handle font subsetting when rendering CJK character sets alongside Latin figures?

Collapse
 
paohaijiao profile image
paohaijiao •

FBox does it: each font is loaded as a PDType0Font with embedSubset enabled, so the subset is written when the PDF is saved. The only strict rule is to load a font only once per document; otherwise the same 6 MB font gets embedded twice.

When CJK text and Latin figures are mixed, they are resolved per code point through the fontFamilyNames chain and then merged into runs. Noto Sans KR/JP and SimHei contain Latin digits, so 매출 1,280억 원 is one run and one subset. Our Arabic font does not contain Latin digits, so they are drawn with Noto Sans, producing a second subset.

for more than language demo you can visit via following the url

jquick.org/en/jquick-pdf/demo/185_...
jquick.org/en/jquick-pdf/#demo-i18...

Collapse
 
alexcodebytes profile image
Oleksandr •

The 'chart is not a bitmap — it's an SVG string embedded in the template' is the key design choice. It 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. That's a much better approach than rendering a PNG and hoping it looks okay.

Collapse
 
paohaijiao profile image
paohaijiao •

Yeah, and it goes a bit further than it looks. The text inside the SVG is kept as real text objects instead of outlines, so the axis labels remain selectable and searchable. A chart that does not fit is pushed to the next page, not clipped.

If you want to see it in practice, most of the demos put charts inside tables and multi-page reports, and every PDF can be downloaded here: jquick.org/en/jquick-pdf/