DEV Community

paohaijiao
paohaijiao

Posted on

Funnel Chart: Where Credit Applications Drop Off

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

From application to disbursement, five stages, each with an absolute count and a conversion rate.

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/08_funnel.pdf
  • A template file (report/credit_report_08_funnel.txt) with 1 bound placeholder
  • A detail grid of 6 rows × 1 columns, laid out with width:100% so it fills the text area
  • One self-contained Java class (JQuickCreditReport08FunnelTest) 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_08_funnel.txt) instead of a Java string, so designers can edit it without touching code.

<pdf>
<body>
  <div style="backgroundColor:#BF360C; color:white; paddings:16px; marginBottom:18px">
    <h1 style="fontSize:21; color:white; textAlignment:center; bold:true; marginBottom:6px">'企业信用评级报告'</h1>
    <p style="fontSize:10; color:#ffe0b2; textAlignment:center; marginBottom:3px">'报告编号: CR-2023-08975  |  评估日期: 2023年11月15日'</p>
    <p style="fontSize:10; color:#ffe0b2; textAlignment:center">'评估机构: 四川省企业信用评估中心'</p>
  </div>

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

  <div style="marginBottom:18px">
    <h2 style="fontSize:13; color:#BF360C; bold:true; marginBottom:10px">'历年财务数据(单位:万元)'</h2>
    <div style="display:flex; flexWrap:wrap">
      <div style="width:190px; padding:10px; border:1px solid #FF5722; borderRadius:6px; margin:4px">
        <div style="fontSize:11; bold:true; color:#BF360C; marginBottom:6px">'营业收入'</div>
        <div style="fontSize:10; marginBottom:3px">'2020年 8,560'</div>
        <div style="fontSize:10; marginBottom:3px">'2021年 12,340'</div>
        <div style="fontSize:10; marginBottom:3px">'2022年 18,920'</div>
        <div style="fontSize:10">'2023年 25,680'</div>
      </div>
      <div style="width:190px; padding:10px; border:1px solid #FF5722; borderRadius:6px; margin:4px">
        <div style="fontSize:11; bold:true; color:#BF360C; marginBottom:6px">'净利润'</div>
        <div style="fontSize:10; marginBottom:3px">'2020年 1,240'</div>
        <div style="fontSize:10; marginBottom:3px">'2021年 2,150'</div>
        <div style="fontSize:10; marginBottom:3px">'2022年 3,890'</div>
        <div style="fontSize:10">'2023年 5,420'</div>
      </div>
      <div style="width:190px; padding:10px; border:1px solid #FF5722; borderRadius:6px; margin:4px">
        <div style="fontSize:11; bold:true; color:#BF360C; marginBottom:6px">'总资产'</div>
        <div style="fontSize:10; marginBottom:3px">'2020年 15,800'</div>
        <div style="fontSize:10; marginBottom:3px">'2021年 22,450'</div>
        <div style="fontSize:10; marginBottom:3px">'2022年 31,200'</div>
        <div style="fontSize:10">'2023年 42,800'</div>
      </div>
      <div style="width:190px; padding:10px; border:1px solid #FF5722; borderRadius:6px; margin:4px">
        <div style="fontSize:11; bold:true; color:#BF360C; marginBottom:6px">'净资产'</div>
        <div style="fontSize:10; marginBottom:3px">'2020年 9,200'</div>
        <div style="fontSize:10; marginBottom:3px">'2021年 13,800'</div>
        <div style="fontSize:10; marginBottom:3px">'2022年 19,500'</div>
        <div style="fontSize:10">'2023年 26,400'</div>
      </div>
      <div style="width:190px; padding:10px; border:1px solid #FF5722; borderRadius:6px; margin:4px">
        <div style="fontSize:11; bold:true; color:#BF360C; marginBottom:6px">'毛利率'</div>
        <div style="fontSize:10; marginBottom:3px">'2020年 42.5%'</div>
        <div style="fontSize:10; marginBottom:3px">'2021年 45.8%'</div>
        <div style="fontSize:10; marginBottom:3px">'2022年 48.2%'</div>
        <div style="fontSize:10">'2023年 51.6%'</div>
      </div>
    </div>
    <p style="fontSize:9; color:#BF360C; marginTop:8px">'同比增长: 营业收入 +44.2%  |  净利润 +63.8%  |  总资产 +39.5%  |  净资产 +42.1%  |  毛利率 +9.1%'</p>
  </div>

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

  <div style="marginBottom:18px">
    <h2 style="fontSize:13; color:#BF360C; bold:true; textAlignment:center; marginBottom:10px">'客户转化漏斗图'</h2>
    <div style="textAlignment:center">
      <svg style="width:460px;height:400px">${svg}</svg>
    </div>
    <div style="display:flex; justifyContent:center; marginTop:10px">
      <div style="width:140px; padding:8px; fontSize:10">
        <span style="color:#D84315; bold:true">'● 财务状况(90分): '</span>
        '营收稳定增长'
      </div>
      <div style="width:140px; padding:8px; fontSize:10">
        <span style="color:#2ecc71; bold:true">'● 经营能力(95分): '</span>
        '客户留存率高'
      </div>
      <div style="width:140px; padding:8px; fontSize:10">
        <span style="color:#e74c3c; bold:true">'● 偿债能力(85分): '</span>
        '负债率合理'
      </div>
      <div style="width:140px; padding:8px; fontSize:10">
        <span style="color:#f39c12; bold:true">'● 发展潜力(92分): '</span>
        '研发投入增加'
      </div>
    </div>
  </div>

  <div style="marginBottom:18px; background:linear-gradient(135deg, #fbe9e7 0%, #ffccbc 100%); borderLeft:4px solid #FF5722; padding:14px">
    <h2 style="fontSize:13; color:#BF360C; bold:true; marginBottom:8px">'综合评价'</h2>
    <p style="fontSize:11; textAlignment:left; marginBottom:8px">
      '四川省泡海椒科技有限公司作为国家级高新技术企业和专精特新"小巨人"企业,在云计算和大数据领域表现出色。'
      '公司财务状况健康,经营能力突出,技术创新能力强,具备良好的发展潜力。信用评级为AAA级,展望稳定。'
    </p>
    <p style="fontSize:8; color:#d32f2f; textAlignment:right">'认证通过'</p>
  </div>

  <div style="textAlignment:center; marginTop:20px">
    <lineSeparator style="strokeColor:#D84315"></lineSeparator>
    <p style="fontSize:9; color:#6c757d; textAlignment:center; marginTop:8px">'本报告依据公开信息和专业评估模型生成,仅供参考 | 报告生成时间: 2025年09月06日'</p>
    <p style="fontSize:9; color:#6c757d; 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/JQuickCreditReport08FunnelTest.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.funnel.*;
import org.junit.Test;

import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;

/**
 * 统一信用报告 —— 风格 08:漏斗图(深橙)
 *
 * <p>以 {@code report/credit_report_08_funnel.txt} 为模板,把漏斗图渲染成 SVG 后
 * 绑定到模板中的 {@code ${svg}} 占位符,输出到 {@code D:\test\creditreport\08_funnel.pdf}。</p>
 *
 * @author Martin
 * @version 1.0.0
 */
public class JQuickCreditReport08FunnelTest {

    public static final String path = JQuickConstant.path;

    @Test
    public void report08Funnel() throws IOException {
        JFunnelOption funnelOption = JFunnelOption.createDefaultFunnel();
        funnelOption = funnelOption
                .title(new Title().text("客户转化漏斗").subtext("2023年度数据"))
                .funnel(new Funnel()
                        .width(600)
                        .topY(80)
                        .bottomY(200)
                        .gap(2)
                        .borderColor(Color.GRAY)
                ).series(Collections.singletonList(
                        new Series()
                                .name("转化")
                                .type("funnel")
                                .data(Arrays.asList(
                                        new DataItem("线索获取", 12000),
                                        new DataItem("商务洽谈", 6000),
                                        new DataItem("方案确认", 2800),
                                        new DataItem("合同签订", 900)
                                ))))
                .colors(
                        new Color(191, 54, 12),
                        new Color(216, 67, 21),
                        new Color(255, 87, 34),
                        new Color(255, 138, 101)
                );
        JOption option = new JOption();
        option.setFunnelOption(funnelOption);

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

        File dir = new File(path + "creditreport");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(path + "creditreport/08_funnel.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_08_funnel.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=JQuickCreditReport08FunnelTest
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 JQuickCreditReport08FunnelTest().report08Funnel();
}
Enter fullscreen mode Exit fullscreen mode

Output: D:/test/creditreport/08_funnel.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

Making the table fill the text area

Tables default to content width, which leaves a ragged right margin on a formal document. Setting width:100% on the <table> makes it span the full text area; percentage widths on the first row's cells then distribute the remaining space across columns.

  • Set width:100% on <table> and give explicit widths only to the leading columns.
  • Label columns need around 100px so labels like Taxpayer ID do not wrap.
  • Leave the last column without a width so it absorbs rounding remainder.
<table style="width:100%; fontSize:9pt">
  <tr>
    <td style="width:100px; padding:5px">'Taxpayer ID'</td>
    <td style="padding:5px">${taxNo}</td>
  </tr>
</table>
Enter fullscreen mode Exit fullscreen mode

Template syntax cheat sheet

Syntax Meaning
<image src='D:/.../x.png'> Embed an image. src is a raw path — it is not evaluated as an expression.
<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.

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.
  • setFontSize takes a float (8.5f); setMargins takes List<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: ← #7 Donut Chart for Revenue Structure by Business Line · #9 Gauge Chart for the Composite Credit Score →

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/JQuickCreditReport08FunnelTest.java
Template jquick-pdfx/src/test/resources/report/credit_report_08_funnel.txt
Output PDF D:/test/creditreport/08_funnel.pdf
Preview image images/08.png

Top comments (0)