在 Web 專案中動態生成統計圖表(折線圖、長條圖、餅圖等)並嵌入 JSP/前端頁面,是 Java 後端常見需求。JFreeChart 作為老牌開源圖表庫,配合內建 DisplayChart Servlet,能輕鬆實現「後端生成圖片 → 暫存 → 前端以 URL 取圖」的流程。本文提供完整的 web.xml 配置、Java 服務層代碼、JSP 調用範例,並針對中文顯示、坐標軸優化、虛線樣式等實戰細節詳細註解。
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.4</version> <!-- 或更新版 -->
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jcommon</artifactId>
<version>1.0.24</version>
</dependency>
JFreeChart 內建 org.jfree.chart.servlet.DisplayChart 用於從 Session/暫存目錄讀取圖片流輸出給瀏覽器。
<servlet>
<servlet-name>DisplayChart</servlet-name>
<servlet-class>org.jfree.chart.servlet.DisplayChart</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DisplayChart</servlet-name>
<url-pattern>/DisplayChart</url-pattern>
</servlet-mapping>
package com.example.chart;
import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.servlet.ServletUtilities;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import java.awt.*;
import java.io.IOException;
import java.util.Map;
public class ChartService {
/**
* 生成多系列折線圖,返回前端可直接用的圖片 URL
*
* @param seriesNames 系列名稱陣列 (圖例)
* @param scores 二維數據 [seriesIndex][categoryIndex]
* @param categories 類別標籤 (X 軸)
* @param title 圖表標題
* @return 圖片訪問路徑,如 "/myapp/DisplayChart?filename=chart_12345.png"
*/
public String generateLineChartUrl(String[] seriesNames, double[][] scores,
String[] categories, String title) {
// 1. 數據集準備
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (int s = 0; s < seriesNames.length; s++) {
for (int c = 0; c < categories.length; c++) {
// 過長類別名截斷,避免 X 軸擁擠
String cat = categories[c].length() > 8 ? categories[c].substring(0, 8) : categories[c];
dataset.addValue(scores[s][c], seriesNames[s], cat);
}
}
// 2. 建立圖表
JFreeChart chart = ChartFactory.createLineChart(
title, null, null, dataset,
PlotOrientation.VERTICAL, true, true, false);
// 3. 全局樣式
chart.setBackgroundPaint(Color.WHITE);
chart.getLegend().setPosition(RectangleEdge.RIGHT);
chart.getLegend().setItemFont(new Font("微軟正黑體", Font.BOLD, 12));
chart.setTitle(new TextTitle(title, new Font("微軟正黑體", Font.BOLD, 16)));
// 4. 繪圖區深度客製化
CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setBackgroundAlpha(0.8f);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinesVisible(true);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
plot.setAxisOffset(new RectangleInsets(0, 0, 0, 10)); // 圖表與 Y 軸留白
plot.setNoDataMessage("無資料可顯示");
// 5. X 軸 (CategoryAxis) 設定
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setTickLabelFont(new Font("微軟正黑體", Font.BOLD, 12));
domainAxis.setLabelFont(new Font("微軟正黑體", Font.BOLD, 14));
domainAxis.setMaximumCategoryLabelLines(2); // 允許換行
domainAxis.setCategoryMargin(0.2); // 類別間距
// 6. Y 軸 (NumberAxis) 設定
NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setAutoRangeIncludesZero(true);
rangeAxis.setUpperMargin(0.15); // 頂部留白 15%
rangeAxis.setTickLabelFont(new Font("微軟正黑體", Font.BOLD, 12));
rangeAxis.setLabelFont(new Font("微軟正黑體", Font.BOLD, 14));
// 7. 系列渲染器:資料點、線型、標籤
LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setShapesVisible(true); // 顯示資料點形狀
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setBaseItemLabelFont(new Font("微軟正黑體", Font.PLAIN, 11));
// 系列 0:實線,寬 2px
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
// 系列 1:虛線 (10px 實, 6px 空)
if (seriesNames.length > 1) {
renderer.setSeriesStroke(1, new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 1.0f, new float[]{10f, 6f}, 0.0f));
}
// 系列 2:點虛線 (6px 實, 6px 空)
if (seriesNames.length > 2) {
renderer.setSeriesStroke(2, new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 1.0f, new float[]{6f, 6f}, 0.0f));
}
// 8. 輸出 PNG 並取得暫存檔名
String fileName = null;
try {
// 寬 700px, 高 300px
fileName = ServletUtilities.saveChartAsPNG(chart, 700, 300, null, null);
} catch (IOException e) {
throw new RuntimeException("圖表生成失敗", e);
}
// 9. 組裝 URL (專案上下文路徑需根據實際部署調整)
// 假設專案 context-path 為 /myapp
return "/myapp/DisplayChart?filename=" + fileName;
}
}
// Spring MVC Controller 範例
@GetMapping("/chart/score")
public String showChart(Model model) {
String[] series = {"班級A", "班級B"};
double[][] data = {{85, 90, 78, 92}, {80, 88, 82, 89}};
String[] categories = {"國文", "英文", "數學", "自然"};
String chartUrl = chartService.generateLineChartUrl(series, data, categories, "期中考成績趨勢");
model.addAttribute("chartUrl", chartUrl);
return "chart/view"; // 對應 view.jsp
}
<!-- view.jsp -->
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h2>成績趨勢圖</h2>
<img src="<%= request.getContextPath() %><%= chartUrl %>" alt="成績圖表" />
</body>
</html>
| 需求 | 實現方式 |
|---|---|
| 中文顯示 (無亂碼) | 指定支援中文字體:new Font("微軟正黑體", Font.BOLD, 12) (Windows) 或 Noto Sans CJK (Linux/Docker 需安裝字體包) |
| X 軸標籤傾斜/換行 | domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45) 或 setMaximumCategoryLabelLines(2) |
| Y 軸範圍固定/自動 | rangeAxis.setRange(0, 100) 固定;setAutoRangeIncludesZero(true) 自動含零 |
| 虛線/點線區分系列 | BasicStroke(float[] dashPattern) 陣列定義「畫-空」長度 |
| 資料點顯示數值 | renderer.setBaseItemLabelsVisible(true) + StandardCategoryItemLabelGenerator |
| 圖片暫存清理 | DisplayChart Servlet 預設用 Session 綁定,Session 失效自動清理;或配置定時任務掃描暫存目錄 |
fontconfig + google-noto-sans-cjk-ttc-fonts 或掛載字體卷。ServletUtilities.saveChartAsPNG 使用 UUID 命名,極低衝突機率;極高併發建議改寫輸出至 ByteArrayOutputStream 直接回傳 byte[] 或 Base64,避免磁碟 I/O。ChartRenderingInfo、BufferedImage,建議使用 try-with-resources 或手動 flush()。JFreeChart 雖 API 略顯老舊,但功能極其完整,配合 DisplayChart Servlet 即可快速構建「後端生成、前端引用」的圖表服務。掌握 CategoryPlot、Renderer、Axis 三大核心客製化入口,即能應對 90% 以上的業務圖表需求。若專案追求現代化、前端渲染(ECharts/Chart.js),亦可改為後端僅提供 JSON 數據,前端負責繪製,架構更清晰。