Geo Chart of Application Volume by Region
Geo Chart of Application Volume by Region jquick-pdf by example — demo #18 of 184. Every article in this series ships one complete, runnable demo: a template file and a Java class. No snippets, no omissions. Mapping
Geo Chart of Application Volume by Region
jquick-pdf by example — demo #18 of 184. Every article in this series ships one complete, runnable demo: a template file and a Java class. No snippets, no omissions.
Mapping受理量 onto administrative regions to decide where to open the next service counter.
This document belongs to the Business chart series set.
What you'll build
- A PDF written to
D:/test/biz/18_region_geo.pdf - A template file (
report/biz/18_region_geo.txt) with 1 bound placeholder - A detail grid of 23 rows × 4 columns, laid out with
width:100%so it fills the text area - One self-contained Java class (
JQuickBiz18RegionGeoTest) 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/18_region_geo.txt) instead of a Java string, so designers can edit it without touching code.
<pdf>
<body>
<div style="borderBottom:2px solid #1F4E79; paddingBottom:8px; marginBottom:10px; keepTogether:true">
<div style="display:flex; justifyContent:space-between">
<div style="width:150px">
<p style="color:#1F4E79; fontSize:10pt; bold:true">'省公共信用信息平台'</p>
</div>
<div style="width:250px; textAlignment:center">
<h1 style="color:#1F4E79; fontSize:16pt; bold:true; textAlignment:center">'区域业务受理量分布'</h1>
</div>
<div style="width:150px; textAlignment:right">
<p style="color:#6c757d; fontSize:9pt">'2025年1月15日'</p>
</div>
</div>
</div>
<div style="marginBottom:10px">
<h2 style="color:#1F4E79; fontSize:13pt; bold:true; borderBottom:1px solid #1F4E79; paddingBottom:3px; marginBottom:8px">'一、各市(州)业务受理量明细'</h2>
<table style="width:560px; fontSize:9pt">
<tr>
<td style="width:200px; bold:true; padding:6px 4px; borderBottom:1px solid #E3E8EE">'地区'</td>
<td style="width:130px; bold:true; padding:6px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'受理量(件)'</td>
<td style="width:110px; bold:true; padding:6px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'同比增长'</td>
<td style="width:120px; bold:true; padding:6px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'全省占比'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'成都市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'42,180'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'6.2%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'23.8%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'绵阳市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'14,260'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'9.4%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8.0%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'宜宾市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'12,840'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'11.8%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'7.2%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'南充市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'11,520'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'7.6%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'6.5%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'泸州市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'10,360'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8.9%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5.8%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'凉山彝族自治州'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'9,180'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'12.6%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5.2%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'达州市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8,940'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'6.8%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5.0%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'乐山市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8,120'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5.4%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'4.6%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'德阳市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'7,860'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8.2%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'4.4%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'眉山市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'6,540'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'10.6%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3.7%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'遂宁市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5,920'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'7.2%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3.3%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'内江市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5,680'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5.9%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3.2%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'自贡市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'5,240'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'4.8%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3.0%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'广元市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'4,860'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'9.1%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2.7%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'巴中市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'4,320'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'11.2%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2.4%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'雅安市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3,980'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'8.6%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2.2%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'资阳市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3,760'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'6.4%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2.1%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'广安市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3,520'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'7.8%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2.0%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'攀枝花市'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3,240'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'3.6%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'1.8%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'甘孜藏族自治州'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2,860'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'15.8%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'1.6%'</td>
</tr>
<tr>
<td style="padding:5px 4px; borderBottom:1px solid #E3E8EE">'阿坝藏族羌族自治州'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'2,410'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'14.2%'</td>
<td style="padding:5px 4px; textAlign:right; borderBottom:1px solid #E3E8EE">'1.4%'</td>
</tr>
<tr>
<td style="padding:6px 4px; bold:true; borderBottom:2px solid #1F4E79">'全省合计(21个市州)'</td>
<td style="padding:6px 4px; textAlign:right; bold:true; borderBottom:2px solid #1F4E79">'177,590'</td>
<td style="padding:6px 4px; textAlign:right; bold:true; borderBottom:2px solid #1F4E79">'7.5%'</td>
<td style="padding:6px 4px; textAlign:right; bold:true; borderBottom:2px solid #1F4E79">'100.0%'</td>
</tr>
</table>
</div>
<div style="marginBottom:10px">
<h2 style="color:#1F4E79; fontSize:13pt; bold:true; borderBottom:1px solid #1F4E79; paddingBottom:3px; marginBottom:8px">'二、全省区域分布图'</h2>
<div style="display:flex; justifyContent:center">
<svg style="width:700pt">${svg}</svg>
</div>
<p style="fontSize:9pt; color:#6c757d; italic:true; marginTop:6px">'图 1 四川省21个市(州)业务受理量分布(底图:四川省行政区划)'</p>
</div>
<div style="marginBottom:10px">
<h2 style="color:#1F4E79; fontSize:13pt; bold:true; borderBottom:1px solid #1F4E79; paddingBottom:3px; marginBottom:8px">'三、区域均衡分析'</h2>
<list style="listStyleType:none; paddingLeft:0px; fontSize:10pt">
<li style="marginBottom:6px; padding:8px; backgroundColor:#E8F0F7; borderLeft:3px solid #1F4E79"><span style="bold:true; color:#1F4E79">'成都平原经济区领跑:'</span>'成都市受理 42,180 件、占全省 23.8%,绵阳、德阳、眉山合计 28,660 件,四市合计占全省 39.9%。'</li>
<li style="marginBottom:6px; padding:8px; backgroundColor:#E8F0F7; borderLeft:3px solid #1F4E79"><span style="bold:true; color:#1F4E79">'民族地区增速领先:'</span>'甘孜藏族自治州同比增长 15.8%、阿坝藏族羌族自治州 14.2%、凉山彝族自治州 12.6%,均显著高于全省 7.5% 的平均增速。'</li>
<li style="marginBottom:6px; padding:8px; backgroundColor:#E8F0F7; borderLeft:3px solid #1F4E79"><span style="bold:true; color:#1F4E79">'尾部地区基数偏低:'</span>'阿坝 2,410 件、甘孜 2,860 件、攀枝花 3,240 件,三地合计仅占全省 4.8%,业务体量仍有较大提升空间。'</li>
<li style="marginBottom:6px; padding:8px; backgroundColor:#E8F0F7; borderLeft:3px solid #1F4E79"><span style="bold:true; color:#1F4E79">'增速分化明显:'</span>'21 个市(州)全部实现同比增长,但增速区间自 3.6%(攀枝花市)延伸至 15.8%(甘孜藏族自治州),区域间差异较大。'</li>
<li style="marginBottom:6px; padding:8px; backgroundColor:#E8F0F7; borderLeft:3px solid #1F4E79"><span style="bold:true; color:#1F4E79">'建议:'</span>'对攀枝花、自贡等低增速地区开展专项督导,同时将甘孜、阿坝的线上受理经验向全省推广,缩小区域服务能力差距。'</li>
</list>
</div>
<lineSeparator style="strokeColor:#DCE3EC"></lineSeparator>
<p style="color:#6c757d; fontSize:8pt; marginTop:8px">'数据来源:省公共信用信息平台 | 统计口径:2024年1月 - 12月 | 生成时间:2025年1月15日'</p>
</body>
</pdf>
Step 2 — the Java class
jquick-pdfx/src/test/java/com/github/paohaijiao/biz/chart/JQuickBiz18RegionGeoTest.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 ([email protected])
*/
package com.github.paohaijiao.biz.chart;
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.geo.GeoOption;
import org.junit.Test;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* 业务场景 Demo 18:区域业务分布表(政务/信用平台)
*
* <p>业务场景:省公共信用信息平台按行政区划汇总业务受理量,用于省社会信用体系建设联席会议的区域均衡分析。</p>
* <p>实际图表类型:{@link JChartType#Geo} 地图(GeoJSON 行政区划图,底图区域为四川省 21 个市/州)。</p>
*
* <p>模板:{@code report/biz/18_region_geo.txt};地图以 SVG 文本形式绑定到模板的 {@code ${svg}} 占位符,
* 输出到 {@code D:\test\biz\18_region_geo.pdf}。</p>
*
* <p>图表尺寸说明:地图按渲染器默认画布 800pt × 600pt(宽 × 高)绘制;模板中 {@code <svg>} 只声明宽度、
* 不声明高度,宽度被页面自动收窄时高度会按原比例同步缩放,不会纵向拉伸变形。</p>
*
* @author Martin
* @version 1.0.0
*/
public class JQuickBiz18RegionGeoTest {
public static final String path = JQuickConstant.path;
/**
* 从 classpath 读取文本内容(GeoJSON 底图)
*/
private String readFromClasspath(String fileName) throws IOException {
StringBuilder content = new StringBuilder();
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
if (is == null) {
throw new IOException("File not found in classpath: " + fileName);
}
String line;
while ((line = br.readLine()) != null) {
content.append(line).append("\n");
}
}
return content.toString();
}
@Test
public void biz18RegionGeo() throws IOException {
JOption option = new JOption();
GeoOption geoOption = new GeoOption();
// 读取 classpath 下的四川省行政区划 GeoJSON(21 个市/州)作为底图
geoOption.setGeoJsonContent(readFromClasspath("sample/test.geojson"));
// 地图填充色沿用政务系统低饱和主色(深蓝)
geoOption.setBackgroundColor("#1F4E79");
option.setGeoOption(geoOption);
String svg = JChartRendererFactory.renderChart(JChartType.Geo, option);
File dir = new File(path + "biz");
if (!dir.exists()) {
dir.mkdirs();
}
FileOutputStream fileOutputStream = new FileOutputStream(path + "biz/18_region_geo.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/18_region_geo.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=JQuickBiz18RegionGeoTest
…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 JQuickBiz18RegionGeoTest().biz18RegionGeo();
}
Output: D:/test/biz/18_region_geo.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);
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
JPdfConfigbefore creating the factory. - Use fixed
pxwidths that sum to less than the text width (~483px on A4 with 40pt margins). - Nested
divtwo-column layouts are unreliable here — a full-width single column is safer.
JPdfConfig config = new JPdfConfig();
config.getLayoutConfig().setFlexLayout(true);
Template syntax cheat sheet
| Syntax | Meaning |
|---|---|
<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. |
<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. |
Gotchas worth remembering
- 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. - Keep
<!-- ... -->comments short — long ones can silently blank the render.
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: ← #17 Bar Chart of Accepted, Approved and Returned Applications · #19 Pie Chart of Overdue Loan Share by Bucket →
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/JQuickBiz18RegionGeoTest.java |
| Template | jquick-pdfx/src/test/resources/report/biz/18_region_geo.txt |
| Output PDF | D:/test/biz/18_region_geo.pdf |
| Preview image | images/18.png |
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.