JFreeChart 是 Java 生態最成熟的開源圖表庫,支援多種圖表類型、高度可客製化。本文以時間序列折線圖為例,示範從資料集建立、圖表配置、Servlet 整合到 JSP 顯示的完整流程。
XYDataset, CategoryDataset, PieDataset)JFreeChart 物件XYPlot, CategoryPlot) - 座標軸、格線、渲染器XYLineAndShapeRenderer, BarRenderer) - 線型、形狀、顏色package com.example.chart;
import java.awt.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import javax.servlet.http.HttpSession;
import org.jfree.chart.*;
import org.jfree.chart.axis.*;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.servlet.ServletUtilities;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.time.*;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleInsets;
public class LineXYChart {
public String generateChart(HttpSession session, PrintWriter pw) throws IOException {
// 1. 建立資料集
XYDataset dataset = createDataset();
// 2. 建立圖表 (時間序列折線圖)
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"股價走勢圖", // 標題
"日期", // X 軸標籤
"價格", // Y 軸標籤
dataset, // 資料集
true, // 顯示圖例
true, // 顯示工具提示
false // 不生成 URL
);
// 3. 客製化外觀
customizeChart(chart);
// 4. 渲染為 PNG 並儲存至 Session (由 ServletUtilities 管理)
ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection());
String fileName = ServletUtilities.saveChartAsPNG(chart, 800, 400, info, session);
// 5. 輸出 ImageMap (支援工具提示/點擊熱區)
ChartUtilities.writeImageMap(pw, fileName, info, false);
return fileName;
}
private void customizeChart(JFreeChart chart) {
// 背景與標題
chart.setBackgroundPaint(Color.WHITE);
chart.setTitle(new TextTitle("台股加權指數走勢", new Font("微軟正黑體", Font.BOLD, 18)));
chart.addSubtitle(new TextTitle("資料來源: 證交所", new Font("微軟正黑體", Font.PLAIN, 12)));
// 圖例字體
chart.getLegend().setItemFont(new Font("微軟正黑體", Font.PLAIN, 12));
// 取得繪圖區
XYPlot plot = (XYPlot) chart.getPlot();
plot.setBackgroundPaint(new Color(0xF5F5F5));
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
plot.setAxisOffset(new RectangleInsets(5, 5, 5, 5));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
// 渲染器設定:顯示資料點、填滿形狀
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
renderer.setBaseShapesVisible(true);
renderer.setBaseShapesFilled(true);
renderer.setDrawSeriesLineAsPath(true); // 平滑曲線
// Y 軸格式化:兩位小數
NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumFractionDigits(2);
rangeAxis.setNumberFormatOverride(nf);
// 工具提示格式
renderer.setToolTipGenerator(new StandardXYToolTipGenerator(
"{0}: {1} = {2}",
new SimpleDateFormat("yyyy-MM-dd"),
nf
));
// X 軸日期格式
DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
domainAxis.setDateFormatOverride(new SimpleDateFormat("MM/dd"));
domainAxis.setTickLabelFont(new Font("微軟正黑體", Font.PLAIN, 10));
}
private XYDataset createDataset() {
TimeSeriesCollection dataset = new TimeSeriesCollection();
TimeSeries series = new TimeSeries("加權指數", Day.class);
// 模擬資料 (實際應從資料庫/服務取得)
series.add(new Day(1, 1, 2024), 17500.25);
series.add(new Day(2, 1, 2024), 17620.50);
series.add(new Day(3, 1, 2024), 17480.30);
series.add(new Day(4, 1, 2024), 17710.80);
series.add(new Day(5, 1, 2024), 17850.10);
// ... 更多資料點
dataset.addSeries(series);
dataset.setDomainIsPointsInTime(true);
return dataset;
}
}
<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>/servlet/DisplayChart</url-pattern>
</servlet-mapping>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.example.chart.LineXYChart" %>
<%@ page import="java.io.PrintWriter" %>
<%
LineXYChart chartBean = new LineXYChart();
String fileName = chartBean.generateChart(session, new PrintWriter(out));
String chartUrl = request.getContextPath() + "/servlet/DisplayChart?filename=" + fileName;
%>
<!DOCTYPE html>
<html>
<head>
<title>JFreeChart 動態圖表示範</title>
<meta charset="UTF-8">
</head>
<body>
<h2>即時股價走勢圖</h2>
<!-- usemap 對應 ImageMap 實現熱區/提示 -->
<img src="<%= chartUrl %>" width="800" height="400" border="0" usemap="<%= fileName %>" />
</body>
</html>
| 需求 | 實作方式 |
|---|---|
| 多條線對比 | 建立多個 TimeSeries 加入同一 TimeSeriesCollection |
| 區域填色 | renderer.setSeriesFillPaint(seriesIndex, new Color(0,0,255,50)) |
| 標記特殊點 | 使用 XYItemAnnotation 或自訂 Renderer 在特定座標繪製 |
| 對數座標軸 | plot.setRangeAxis(new LogarithmicAxis("Label")) |
| 匯出高解析度 | ChartUtilities.saveChartAsPNG(file, chart, 1600, 900, info) |
| 中文顯示 | 統一指定支援中文字體 (微軟正黑體、Noto Sans CJK) |
傳統「後端生成圖片 → `` 顯示」模式存在狀態管理、擴展性、互動性限制。新專案建議:
JFreeChart 仍適合:PDF 報表嵌入圖表、伺服器端批次生成靜態圖片、無前端環境的後台任務。