一、JFreeChart項目簡介
JFreeChart是開放原始碼網站SourceForge.net上的一個JAVA項目,它主要用來各種各樣的圖表,這些圖
表包括:餅圖、柱狀圖(普通柱狀圖以及堆棧柱狀圖)、線圖、地區圖、分布圖、混合圖、甘特圖以及
一些儀錶盤等等。這些不同式樣的圖表基本上可以滿足目前的要求。為了減少篇幅本文主要介紹前面
三種類型的圖表,讀者可以觸類旁通去開發其他樣式的圖表。下面幾個是JFreeChart產生的這三種類
型圖表的結果:
圖1
圖2
圖3
上面的三個圖都是表示四個季度的某個產品的銷量資訊。在繼續下面小節之前必須先準備好開發環
境,因為是基於WEB瀏覽器的圖表展現,因此需要一個Servlet引擎或者是J2EE應用伺服器(例如
WebSphere,Tomcat等)。WEB環境的搭建就不累贅了,讀者根據喜好自行安裝。JFreeChart引擎本身
需要到SourceForge.net上下載,地址如下:
JFreeChart首頁: http://www.jfree.org/jfreechart/index.html
JFreeChart下載頁面:http://sourceforge.net/projects/jfreechart/
下載的時候需要注意的是必須下載兩個檔案:JFreeChart以及Jcommon。目前最新配套版本是:JFreeChart 0.9.11 Jcommon 0.8.6
這裡有點筆者在開發中遇見的問題需要注意的是:在使用Eclipse開發的時候會報一個莫名其妙的錯誤,錯誤可能指向某個類檔案的第一行。遇到這樣的問題一般是因為沒有把Jcommon的jar包設定到項目的類路徑中的緣故。具體的原因不祥。
二、解讀JFreeChart的源碼結構
在開始使用JFreeChart之前我們有必要先大概瞭解一下JFreeChart本身的結構以及它所帶一些例子程
序,這樣有助於我們下一步自行開發。下載JFreeChart包後已經帶有非常豐富的例子,因為JFreeChart
這個項目本身的使用文檔非常少,因此學習它最好的辦法就是學習它所帶的例子源碼。在包
org.jfree.chart.demo中有幾十個檔案用於展示JFreeChart所能支援的所有圖表的結果。如果你的JDK是比
較新的情況下可能在運行這些例子時會有問題,現象如下:
java.lang.UnsatisfiedLinkError: initDDraw
at sun.awt.windows.Win32OffScreenSurfaceData.initDDraw(Native Method)
at sun.awt.windows.Win32OffScreenSurfaceData.(Win32OffScreenSurfaceData.java:141)
at sun.awt.Win32GraphicsDevice.(Win32GraphicsDevice.java:58)
at sun.awt.Win32GraphicsEnvironment.makeScreenDevice(Win32GraphicsEnvironment.java:168)
at sun.java2d.SunGraphicsEnvironment.getScreenDevices(SunGraphicsEnvironment.java:240)
at sun.awt.Win32GraphicsEnvironment.getDefaultScreenDevice(Win32GraphicsEnvironment.java:61)
at java.awt.Window.init(Window.java:224)
at java.awt.Window.(Window.java:268)
at java.awt.Frame.(Frame.java:398)
at javax.swing.JFrame.(JFrame.java:198)
at org.jfree.chart.demo.JFreeChartDemo.(JFreeChartDemo.java:148)
at org.jfree.chart.demo.JFreeChartDemo.main(JFreeChartDemo.java:285)
Exception in thread "main"
這個錯誤是由於新版的Swing大量的使用了微軟的DirectDraw的技術來提高畫圖的效能,而可能你的顯
卡在這時候會跟你鬧點情緒或者顯卡本身並不支援這樣的一個技術。難道就沒有辦法了嘛?要解決這
個問題也非常簡單,我們可以屏蔽掉DirectDraw,不讓Swing使用該技術就可以了。在運行這些代碼時
給虛擬機器指定參數-Dsun.java2d.noddraw即可。
這時可能你又該納悶了,不說是基於Web的圖表嘛,怎麼又扯到Swing上了?這是因為為了使開發人員
容易上手,無需配置任何運行環境,所以這些例子都是基於GUI方式的用於展現給開發人員如果產生一
個圖表,我們要學習的也就是如何利用這個引擎組建圖表而不是怎麼來顯示一個圖表。當我們把產生
的圖表對象Export到一個影像檔即可在Web上發布。
下面我們來介紹JFreeChart中幾個核心的對象類:
| 類名 |
類的作用以及簡單描述 |
| JFreeChart |
圖表對象,任何類型的圖表的最終表現形式都是在該對象進行一些屬性的定製。JFreeChart引擎本身提供了一個工廠類用於建立不同類型的圖表對象 |
| XXXXXDataset |
資料集對象,用於提供顯示圖表所用的資料。根據不同類型的圖表對應著很多類型的資料集對象類 |
| XXXXXPlot |
圖表區域對象,基本上這個對象決定著什麼樣式的圖表,建立該對象的時候需要Axis、Renderer以及資料集對象的支援 |
| XXXXXAxis |
用於處理圖表的兩個軸:縱軸和橫軸 |
| XXXXXRenderer |
負責如何顯示一個圖表對象 |
| XXXXXURLGenerator |
用於產生Web圖表中每個項目的滑鼠點選連結 |
| XXXXXToolTipGenerator |
用於產生圖象的協助提示,不同類型圖表對應不同類型的工具提示類 |
基本上我認為JFreeChart項目本身的類結構的設計並不是很好,首先在建立圖表的時候用到了大量的
Factory 方法,這樣做雖然可以簡化建立圖表對象的代碼,但是對項目本身或者開發人員來講自行擴充一
種新的圖表都仍然是一件很麻煩的事情;其次除圖表對象本身外其餘的類過於複雜,使用者必須去了
解每個類型的圖表對象應該對應哪些Axis、Plot、Renderer類,並且必須非常熟悉這些類的建構函式中
每個參數的具體含義。這些問題都大大困擾很多初學者。不過,雖然存在很多問題,但是JFreeChart
本身仍不失為一個非常優秀的圖表引擎,況且項目本身也在逐漸的發展中。
在非常簡略的介紹了JFreeChart本身的代碼結構後,下面我們開始動手實驗幾個常用的圖表並把他們
放到web上。
三、使用JFreeChart產生各種樣式的圖表
限於篇幅的問題我們在這裡只實現兩種常用的圖表,其他類型圖表讀者可以觸類旁通。我們先給出柱
狀圖的實現,餅圖的實現再來跟柱狀圖進行比較。
1 柱狀圖
package lius.chart.demo;
import java.io.*;
import org.jfree.data.*;
import org.jfree.chart.*;
import org.jfree.chart.plot.*;
/**
* 該類用於示範最簡單的柱狀圖產生
* @author Winter Lau
*/
public class BarChartDemo {
public static void main(String[] args) throws IOException{
CategoryDataset dataset = getDataSet2();
JFreeChart chart = ChartFactory.createBarChart3D(
"水果產量圖", // 圖表標題
"水果", // 目錄軸的顯示標籤
"產量", // 數值軸的顯示標籤
dataset, // 資料集
PlotOrientation.VERTICAL, // 圖表方向:水平、垂直
true, // 是否顯示圖例(對於簡單的柱狀圖必須是false)
false, // 是否產生工具
false // 是否產生URL連結
);
FileOutputStream fos_jpg = null;
try {
fos_jpg = new FileOutputStream("D://fruit.jpg");
ChartUtilities.writeChartAsJPEG(fos_jpg,100,chart,400,300,null);
} finally {
try {
fos_jpg.close();
} catch (Exception e) {}
}
}
/**
* 擷取一個示範用的簡單資料集對象
* @return
*/
private static CategoryDataset getDataSet() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(100, null, "蘋果");
dataset.addValue(200, null, "梨子");
dataset.addValue(300, null, "葡萄");
dataset.addValue(400, null, "香蕉");
dataset.addValue(500, null, "荔枝");
return dataset;
}
/**
* 擷取一個示範用的組合資料集對象
* @return
*/
private static CategoryDataset getDataSet2() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(100, "北京", "蘋果");
dataset.addValue(100, "上海", "蘋果");
dataset.addValue(100, "廣州", "蘋果");
dataset.addValue(200, "北京", "梨子");
dataset.addValue(200, "上海", "梨子");
dataset.addValue(200, "廣州", "梨子");
dataset.addValue(300, "北京", "葡萄");
dataset.addValue(300, "上海", "葡萄");
dataset.addValue(300, "廣州", "葡萄");
dataset.addValue(400, "北京", "香蕉");
dataset.addValue(400, "上海", "香蕉");
dataset.addValue(400, "廣州", "香蕉");
dataset.addValue(500, "北京", "荔枝");
dataset.addValue(500, "上海", "荔枝");
dataset.addValue(500, "廣州", "荔枝");
return dataset;
}
}
程式運行結束後產生的圖片檔案效果如所示:
圖4
如果是使用簡單的資料即使用getDataSet方法擷取資料集時產生的圖片檔案如下:
圖5
2 餅圖
對於餅圖而言,資料集的擷取用的不是同一個資料集類,另外餅圖不支援同一個類別的項目中還有子
項目這樣的資料。我們只給出建立餅圖的代碼,至於寫圖表到一個檔案則與柱狀圖一致,無需重複。
package lius.chart.demo;
import java.io.*;
import org.jfree.data.*;
import org.jfree.chart.*;
/**
* 用於示範餅圖的產生
* @author Winter Lau
*/
public class PieChartDemo {
public static void main(String[] args) throws IOException{
DefaultPieDataset data = getDataSet();
JFreeChart chart = ChartFactory.createPie3DChart("水果產量圖", // 圖表標題
data,
true, // 是否顯示圖例
false,
false
);
//寫圖表對象到檔案,參照柱狀圖產生源碼
}
/**
* 擷取一個示範用的簡單資料集對象
* @return
*/
private static DefaultPieDataset getDataSet() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("蘋果",100);
dataset.setValue("梨子",200);
dataset.setValue("葡萄",300);
dataset.setValue("香蕉",400);
dataset.setValue("荔枝",500);
return dataset;
}
}
產生的餅圖檔案效果如下:
圖6
四、將產生的圖表移到瀏覽器上
為了將產生的圖表直接傳給用戶端瀏覽器,只需要將前面兩個例子中的檔案流換成是通過
HttpServletResponse對象擷取到的輸出資料流,詳細代碼清單如下:
package lius.chart.demo;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import org.jfree.data.*;
import org.jfree.chart.*;
/**
* 示範通過servlet直接輸出圖表
* @author Winter Lau
*/
public class ChartDemoServlet extends HttpServlet {
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
res.setContentType("image/jpeg");
DefaultPieDataset data = getDataSet();
JFreeChart chart = ChartFactory.createPie3DChart("水果產量圖",
data,
true,
false,
false
);
ChartUtilities.writeChartAsJPEG(res.getOutputStream(),
100,chart,400,300,null);
}
/**
* 擷取一個示範用的簡單資料集對象
* @return
*/
private static DefaultPieDataset getDataSet() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("蘋果",100);
dataset.setValue("梨子",200);
dataset.setValue("葡萄",300);
dataset.setValue("香蕉",400);
dataset.setValue("荔枝",500);
return dataset;
}
}
進階主題
很多情況我們不僅僅要求可以在瀏覽器上顯示一個圖表,我們更需要客戶可以直接在圖表上做一下交
互的操作,例如擷取資訊提示,點擊圖表某個部分進行更詳細資料的展示等等。例如前面產生的簡單
柱狀圖,使用者需要在看到柱狀圖後點擊某種水果例如是蘋果即可看到各個地區蘋果產量的情況。為此
就要求該圖形具有互動操作的功能。在HTML中為了讓一個映像具有可互動的功能就必須給該映像定
義一個Map對象。下表節選一段具有該功能的HTML代碼
onclick="javascript:clickChart('100');return false;">
onclick="javascript: clickChart ('200');return false;">
onclick="javascript: clickChart ('300');return false;">
onclick="javascript: clickChart ('400');return false;">
clickChart ('Diet');return false;" shape=RECT coords=329,17,374,255 href="?series=0&category=Diet">
由此就產生了一個問題:如果根據一個映像來產生對應的MAP對象。我們回頭看看剛才的代碼,在
建立一個圖表對象時候有兩個參數,我們舉柱狀圖的例子來講這兩個參數就是ChartFactory.
createBarChart3D方法中的最後兩個參數,這兩個參數的類型都是布爾值。這兩個參數意思分別是:是
否建立工具提示(tooltip)以及是否產生URL。這兩個參數分別對應著MAP中一個AREA的title屬性以及
href屬性。
可是我想知道的是怎麼來產生這個MAP啊!哈哈,不要著急,JFreeChart已經幫我們做好產生MAP對
象的功能。為了產生MAP對象就要引入另外一個對象:ChartRenderingInfo。因為JFreeChart沒有直接的
方法利用一個圖表對象直接產生MAP資料,它需要一個中間對象來過渡,這個對象就是
ChartRenderingInfo。是產生MAP資料的流程圖:
圖7
如所示,ChartUtilities類是整個流程的核心,它周圍的對象都是一些例如資料對象或者是檔案等。
這個流程簡單描述如下:首先建立一個ChartRenderingInfo對象並在調用ChartUtilities的writeChartAsJPEG
時作為最後一個參數傳遞進去。調用該方法結束後將產生一個影像檔以及一個填充好MAP資料的
ChartRenderingInfo對象,有了這個對象我們還是沒有辦法擷取具體的MAP資料,我們還必須藉助於
ChartUtilities的writeImageMap方法來將ChartRenderingInfo對象讀取出來,擷取MAP資料的代碼片斷如
下:
PrintWriter w = null;
FileOutputStream fos_jpg = null;
FileOutputStream fos_cri = null;
try{
//根據不同類型的圖表使用不同類,以下是針對餅圖的操作
PiePlot plot = (PiePlot) chart.getPlot();
plot.setURLGenerator(new StandardPieURLGenerator(url));
//設定工具提示
plot.setToolTipGenerator(new StandardPieToolTipGenerator());
fos_jpg = new FileOutputStream(“d://fruit.jpg”);
ChartUtilities.writeChartAsJPEG(
fos_jpg,
100,
chart,
400,
300,
info);
fos_cri = new FileOutputStream(__d://fruit.map__);
w = new PrintWriter(fos_cri);
ChartUtilities.writeImageMap(w, __mapname__, info);
w.flush();
}finally{
try{
w.close();
}catch(Exception e){}
try{
fos_cri.close();
}catch(Exception e){}
try{
fos_jpg.close();
}catch(Exception e){}
}
開啟檔案D:/fruit.map,檔案的內容就是要寫到頁面上的MAP資料。把產生的影像檔以及MAP資料文
件寫到頁面上即可完成熱點圖表的功能。至於怎麼結合兩者之間的關係例像的useMap屬性值必須
與MAP對象的名稱結合起來,必鬚根據實際的應用情況進行相應的處理。筆者建議把二者通過標籤
庫封裝起來,影像檔的名稱以及MAP對象的名稱由標籤庫統一進行控制,這樣可以保證二者的一
致性。