天天看點

iReport+JasperReport柱狀圖

最近由于工作項目需要,嘗試着利用ireport+jasperreport進行報表開發。以前曾用過一款商用的報表開發工具,是以對這類工具的使用模式還是比較熟悉的,但還是遇到了很多麻煩和問題。最大的問題就是沒有文檔和售後技術支援,遇到問題隻能時到網上搜尋,但在網上也很難找到幾篇有用的能解決問題的文章,且大量抄來抄去的重複文章,找到的幾篇卻在關鍵的地方語焉不詳。沒辦法,問題的解決還得靠自己去琢磨。怎樣利用上述工具進行圖表開發,我整整琢磨嘗試了2天。網上沒找到有用的文檔,看jasperreport的demo,也不知其是以然。好在經過自己的琢磨和不斷試驗,整理出了一條圖表開發的思路和步驟。盡管還是有很多不清楚的地方,但眼下總結出來的已經足夠項目的開發應用了。

下面以柱狀圖(bar3DChart)為例,詳細說明圖表的開發過程。

1.      确定資料源類型和其中參數

我選擇的是JRDataSourceProvider類型的資料源。我也試過JDBC資料源,沒問題,隻是我的應用需求是要根據使用者的選擇展示,是以用JRDataSourceProvider更靈活和友善些,不必每次選擇都去查資料庫。

用iReport開發圖表,在定義圖表屬性中的資料時,需要添加category series,一個category series中包括series expression、category expression、Value expression、label expression等。當我開始看到這些東西時,真的時搞不明白是什麼意思。試來試去,基本上弄清楚了,關鍵是前3個,Value expression很明白是指資料的值,category expression代表分組,series expression則代表分組中的不同名額。舉例說明:我公司的一款資料産品銷往全國各地的不同電信營運商,圖表要按地區展示各營運商的産品套數,那麼這裡的地區就是category expression,營運商就是series expression,Value expression就是産品的套數了。

于是圖表資料源的參數有3個就夠了,即category、series和value。其他一些圖表可能情況略有不同,如餅圖(piechart),xylinechart等,如要做的通用些,可再加入些其他參數。我這裡有3個足夠了。

2.      開發資料源類

首先定義一個使用者圖表的bean:ChartDataBean,包括3個成員變量及相應的getter和setter。3個成員變量的名稱如前所述,無論圖表要顯示什麼樣的資料,都按這3個變量進行對應并指派即可。這樣的名稱也與iReport中圖表的資料相對應,不至于搞錯。如下:

public class ChartDataBean {

    String serieas;

    String category;

    float  value;

    public String getCategory() {

       return category;

    }

    public void setCategory(String category) {

       this.category = category;

    }

    public String getSerieas() {

       return serieas;

    }

    public void setSerieas(String serieas) {

       this.serieas = serieas;

    }

    public float getValue() {

       return value;

    }

    public void setValue(float value) {

       this.value = value;

    }

}

然後定義一個ChartDataSource類,該類應用了JRDataSource接口。該類用于在生成圖表時将資料傳入。如下:

public class ChartDataSource implements JRDataSource {

    private int m_nldx;

    private Vector<ChartDataBean> v;

    public ChartDataSource(){

    this(new Vector<ChartDataBean>());

    }

    public ChartDataSource(Vector<ChartDataBean> v){

    this.m_nldx=-1;

    this.v=v;

    }

    public Object getFieldValue(JRField arg0) throws JRException {

       Object o = null;

        String sName = arg0.getName();

        ChartDataBean resultset = v.elementAt(m_nldx);

        if(resultset == null)

               return null;

        if(sName.equals("serieas"))

               o =resultset.getSerieas();

        else if(sName.equals("category"))

               o = resultset.getCategory();

        else if(sName.equals("value"))

            o = resultset.getValue();

        return o;

    }

    public boolean next() throws JRException {

       // TODO Auto-generated method stub

       m_nldx ++;

        return (m_nldx < v.size());

    }

}

最後定義一個ChartDataSourceProvider類,該類繼承類JRAbstractBeanDataSourceProvider,如下:

public class ChartDataSourceProvider extends JRAbstractBeanDataSourceProvider {

    public ChartDataSourceProvider(Class arg0) {

       super(arg0);

       // TODO Auto-generated constructor stub

    }

    public ChartDataSourceProvider() {

       super(ChartDataBean.class);

       // TODO Auto-generated constructor stub

    }

    public JRDataSource create(JasperReport arg0) throws JRException {

       // TODO Auto-generated method stub

       ChartDataSource ds=new ChartDataSource(getQueryResult());

       return ds;

    }

    public void dispose(JRDataSource arg0) throws JRException {

       // TODO Auto-generated method stub

    }

    public static Vector<ChartDataBean> getQueryResult(){

        Vector<ChartDataBean> vector = new Vector<ChartDataBean>();

        ChartDataBean result = new ChartDataBean();

        result.setCategory("北京");

        result.setSerieas("聯通");

        result.setValue(1500);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("北京");

        result.setSerieas("電信");

        result.setValue(1600);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("北京");

        result.setSerieas("移動");

        result.setValue(2000);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("上海");

        result.setSerieas("聯通");

        result.setValue(1900);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("上海");

        result.setSerieas("電信");

        result.setValue(600);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("上海");

        result.setSerieas("移動");

        result.setValue(1200);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("天津");

        result.setSerieas("聯通");

        result.setValue(900);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("天津");

        result.setSerieas("電信");

        result.setValue(800);

        vector.addElement(result);

        result = new ChartDataBean();

        result.setCategory("天津");

        result.setSerieas("移動");

        result.setValue(1400);

        vector.addElement(result);

        return vector;

    }

}

以上getQueryResult()方法中加入了一些資料用于在iReport開發中測試預覽用。實際上我認為,ChartDataSourceProvider類隻是為iReport設計時從資料源中提取field資訊而用的,在報表生成過程中并不會用到該類。

3.    圖表設計

在前面工作的基礎上,我們就可以利用iReport進行圖表開發了。

首先建立一個報表檔案,檔案名最好能反應報表的内容,如bar3DChart;

然後定義報表資料源,在定義資料源前要先設定class path,通過菜單option->classpath,在classpath對話框中點選Add Folder,将你前面開發資料源類的java工程中放置編譯後的.class檔案的目錄添加到classpath清單中,如我的是web工程,class的目錄是D:/Myprj/prj1/WebRoot/WEB-INF/classes;

添加一個新資料源,通過菜單data->connection/datasource,打開connection/datasource,點選new按鈕,在connection properties對話框中選擇JRDataSourceProvider,點選next按鈕,在新打開的對話框中有兩個輸入項:name和jasperreports datasource provider class。Name可任意輸入,如:ChartDataSourceProvider;jasperreports datasource provider class則要輸入你前面定義的ChartDataSourceProvider類,一定要輸入全路徑類名,和你在java工程中的包名類名一緻:如com.project1.report.chart. ChartDataSourceProvider,輸入完成後可點選test按鈕測試一下,如彈出Connection test successful!對話框則表明資料源類添加成功,否則要檢查一下classpath是否正确設定。

注:這裡有個情況說明一下,如果ChartDataSourceProvider類是在iReport打開後編譯的,那麼即使classpath設定正确,類名輸入也無問題,還是測試失敗。這時就必須将iReport關閉後再打開,測試才會成功,資料源才可用,我也不知道這是為什麼。這個問題在我剛開始用iReport時困擾了我一天,我新加了一個資料源,但怎麼都連不通,反複檢查各種設定、路徑、名稱、大小寫等,就是不行,實在是莫名其妙呀!第二天上班,我開機後再試試,我什麼都沒改動,還是昨天那些東西,竟然連接配接成功了!我就琢磨了,是不是跟機器關開了一次有關系?機器一天都是開着的,下班才關,什麼都沒動,再開機問題就解決了?可也不能那麼邪乎吧,什麼東西做得非要重新開機機器?于是經過幾次試驗我就發現:資料源類在重新編譯後,必須重新啟動一次iReport。

注意要将新加的資料源設為預設。

讀取Field,菜單file->report query打開report query對話框,在DataSource Provider屬性頁中點選Get fields from data source按鈕,下面的清單裡會出現field的清單,有serieas、category、value和class,多了個class,不去管他;點選ok;

下面設計圖表了。

在報表title或summary band中加入一個圖表工具(chart tool),在彈出的圖表選擇對話框中選擇bar 3D,并點選ok确定。将band和圖表調整至合适的大小;

輕按兩下bar 3D圖表,打開bar3DChart對話框,在chart屬性頁中将Evaluation Time設為report(如選其他選項會出現我不想要的效果,我還沒去仔細琢磨),然後點選Edit chart properties按鈕,打開chart properties對話框;

在chart properties對話框選擇Chart Data屬性頁,在Chart Data屬性頁中選擇Details屬性頁;

點選Add按鈕添加一個category series,在彈出的對話框的data屬性頁中有4個需要定義的表達式(expression),我們定義前3個。如Series Expression,可以通過點選右邊的按鈕打開表達式編輯器,在編輯器下邊的objects and expression屬性頁中左邊第一個框中選Field,然後在第二個框中輕按兩下seriesas,在上邊的文本編輯區中就會出現表達式:$F{serieas}。點選應用按鈕(?我的iReport中顯示的是套用,我估計英文應該是apply,按咱們的習慣還是較應用吧)。其他兩個表達式如法炮制,結果應該是這樣的:series expression:$F{serieas}、category expression:$F{category}、Value expression:$F{value}、label expression:空白;點選ok;然後關閉chart properties對話框。

這時可以通過菜單build->execute report(Using active conn.)來預覽圖表了。

至此,在iReport中的圖表設計工作完成。圖表在web工程中的部署、載入、展現等與報表的方法是一樣的。

4. 圖表部署

我用的web伺服器是tomcat5,假定web工程部署在webapps/myproject/中,在webapps/myproject/下建立一個目錄reports用來放置報表檔案。将前面iReport編譯生成的bar3DChart.jasper檔案放在該目錄下。.jasper檔案預設生成在iReport的安裝目錄下。

部署和生成報表所需的幾個jar包也要放到myproject/WEB-INF/lib下,這些jar包有:jasperreports-2.0.4、iReport、jfreechart-1.0.9、jcommon-1.0.12等。版本可能不太一樣。注意的是,iReport和jasperreport的圖表功能實際上是用了jfreechart工具來實作的,是以一定要将jfreechart的jar包導入到工程中。

5. 圖表的生成和展示

開發servlet。我這裡以servlet方式生成圖表。Servlet如下:

public class ReportServlet extends BaseHttpServlet {

    public void service(

           HttpServletRequest request,

           HttpServletResponse response

           ) throws IOException, ServletException

       {

           PrintWriter out = response.getWriter();

           try                                                       

           {

              HttpSession session = request.getSession(false);

              //獲得圖表資料。圖表資料是在其他地方查表得到并生成Vector<ChartDataBean>的資料列,然後儲存在session中傳遞到servlet。

           Vector dataList = (Vector) session.getAttribute ("storedDataList");

    //載入圖表,一定要指對路徑和檔案名

File reportFile = new File(context.getRealPath ("/reports/bar3DChart.jasper"));

if (!reportFile.exists())

                throw new JRRuntimeException("File WebappReport.jasper not found. The report design must be compiled first.");

JasperReport jasperReport = (JasperReport)JRLoader.loadObject(reportFile.getPath());

//圖表的參數,及ireport中的定義變量,如報表标題、圖表日期等,也是于他處設定儲存在session中

Map parameters =(Map)session.getAttribute

                                     ("jasper_parameters");   //這裡導入圖表資料,并生成報表

JasperPrint jasperPrint =

                JasperFillManager.fillReport(

                   jasperReport,

                   parameters,

                   new ChartDataSource(dataList)

                   );

//JasperPrint jasperPrint =(JasperPrint)session.getAttribute

//(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE);

//注:以上的步驟實際上都可以在他處完成,最後将jasperPrint儲存到session中,這樣在servlet中隻需從session中取得jasperPrint即可(如上面注釋掉的這條語句),可以将servlet做成通用的類來展示輸出所有的報表。

//輸出html 用JRHtmlExporter

    JRHtmlExporter exporter = new JRHtmlExporter();

              Map imagesMap = new HashMap();

              response.setContentType("text/html");

                request.getSession().setAttribute("IMAGES_MAP", imagesMap);

               request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, jasperPrint);

                  exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);

              exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, out);

                  exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap);

                             exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI,"image?image=");

                exporter.setParameter(JRHtmlExporterParameter.BETWEEN_PAGES_HTML, "");

    exporter.exportReport();

}

           catch (JRException e)

           {

              out.println("<html>");

              out.println("<head>");

              out.println("<title>JasperReports - Web Application Sample</title>");

              out.println("<link rel=/"stylesheet/" type=/"text/css/" href=/"../stylesheet.css/" title=/"Style/">");

              out.println("</head>");

              out.println("<body bgcolor=/"white/">");

              out.println("<span class=/"bnew/">JasperReports encountered this error :</span>");

              out.println("<pre>");

              e.printStackTrace();

              out.println("</pre>");

              out.println("</body>");

              out.println("</html>");

           }

       }

6. 添加servlet映射

在web.xml中添加如下設定:

<servlet>

       <servlet-name>ReportServlet</servlet-name>

       <servlet-class>com.project1.report.ReportServlet </servlet-class>

    </servlet>

    <servlet>

       <servlet-name>ImageServlet</servlet-name>

        <servlet-class>net.sf.jasperreports.j2ee.servlets.ImageServlet</servlet-class>

    </servlet>

<servlet-mapping>

    <servlet-name>ImageServlet</servlet-name>

    <url-pattern>/image</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

       <servlet-name>ReportServlet</servlet-name>

       <url-pattern>/ReportServlet</url-pattern>

    </servlet-mapping>

ImageServlet幹嘛用的?注意到ReportServlet的代碼中有這麼一句:

exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI,"image?image=");

ImageServlet就是為這句而來的,html方式輸出報表時要用到的小圖示就在這裡,如果沒有ImageServlet或設定不對,你可以看看輸出的報表是什麼樣的,盡是紅叉叉!

Ok!至此大功告成!對于iReport和jasperreport的使用,還是有不少的問題和疑惑的,這還要在今後的實際應用中逐漸去琢磨、解決和了解。我還會繼續寫一些心得和體會,一方面是總結,另一方面,也是将自己的經驗拿出來共享,希望與我有着同樣的曾經的困惑的人能少走些彎路。

繼續閱讀