Flexible Swing Reporting Using JIDE Aggregate and Pivot Tables Blog

Version 2


    Most classical report writers don't fit well in Swing. Reporting solutions can be adapted to Swing, but the adaptation is time consuming to implement, and often provides a less than desirable solution. This article explores an alternative to the classical reporting that provides the end user with more power for less development effort.

    Knowledge of Swing and JTable is required to use the techniques described in the article. The discussion covers:

    • Report: Creating and using JIDE Aggregate and Pivot tables.
    • Formula: Adding a new column based on a formula.
    • Memorize: Saving and restoring user report changes.
    • Export: Exporting the tables to Excel

    Swing's JTable solution

    For a Java developer, the JTable presents the first reporting solution for Swing. Data is read and translated for a data source, the translated data is then wrapped in a TableModel, and finally the TableModel is used with a JTable. The following figure shows a simple JTable flow:

    missing image
    Figure 1. JTable application flow

    Tools like GlazedLists (http://publicobject.com/glazedlists) provide filtering and sorting, and resolve real-time concurrency issues. Commercial products like JDataGrid provide Excel worksheet look and feel, the ability to read Excel spreadsheets into theTableModel as well as utilities to translate the data from common data file types into TableModels.

    From a Swing developer's perspective, the JTable solution is easy to implement, and with the addition of third party libraries, further functionality can be provided to the end user. However, the user still sees the data as a flat grid that can only be manipulated by moving and sorting columns.

    JIDE Aggregate and Pivot solution

    JIDE is a third party Swing library vendor. Among JIDE's array of components are JTable alternatives called Aggregate and Pivot (see Pivot example at http://www.jidesoft.com/products/pivot.htm). The Aggregate and Pivot tables are proxies that provide a higher level abstraction implemented with multiple JTables. Aggregate and Pivot are the components that will be demonstrated as a quick reporting alternative for desktop clients. Figure 2 shows a modified JTable flow, using the JIDE model.

    missing image
    Figure 2. Modified application flow with JIDE

    Getting started

    The following are links to the libraries and code for this article.

    Note: OpenCSV and Common Lang are used, but can easily be replaced with other libraries.

    Sample classes

    The sample code is decomposed into the following sections:

    • SwingReportDemo: Starting point of the application. Contains the GUI code and acts as the moderator for all the other classes.
    • Data: SalesDetail.txt is sample sales data that exists in a CSV (comma-separated values) formatted text file.
    • Report: A Report interface provides a service interface to the header, data types, and data. CsvReport and NullReport are implementations of the Report Interface.
    • ReportTableModel: The table model used to wrap the Report.
    • CalculatedTableModel (JIDE): Provides calculated columns behavior to the table model.
    • JideView: Common interface points shared between Aggregate and Pivot, which allows the SwingReportDemo to treat the Aggregate and Pivot views the same.
    • AggregateView: Controls the look, feel, and behavior of the Aggregate table.
    • PivotView: Controls the look, feel, and behavior of the Pivot table.

    Figure 3 presents the code in diagram form.

    missing image
    Figure 3. SwingReportDemo class layout

    Table data

    The table data involves:

    • Data: The data for the sample is stored in SalesDetail.txt. The first row is the default table header, the second row is the corresponding Java type, and the remaining rows are the report data. The following is a small portion of header, class, and line data:
      Date,Desc,Qty,.... java.util.Date,java.lang.String,java.lang.Double,.... Thu Dec 03 19:00:00 CDT 2009,Cabinet Pulls,4.0,.... 
    • Loading and translation:MainAppAggregate.getReport() is the starting point for loading and translation. The file is loaded as a resource from/jideReport/SalesDetail.txt. CsvReportcontains services to translate CSV data to a Report.
    • Table model: ReportTableModel wraps aReport, and also extends JIDE'sDefaultContextSensitiveTableModel. JIDE provides more than 2 dozen table model classes that extend Java'sAbstractTableModel. JIDE models can be used together by piping one into another to supply additionally functionality. JIDE provides models for caching, filtering, directly reading database data, and adding calculations.

      Note: Be careful with the chaining of models. Understanding the sequence of chaining, and how the different models impact each other is essential. In the following chaining, the CachedTableModel is piped intoFilterableTableModel rather than the other way around. If FilterableTableModel were piped intoCachedTableModel, as the filtering is updated theCachedTableModel will be invalidated, which is less than optimal.

      PivotDataModel( new FilterableTableModel( new CachedTableModel( new CalculatedTableModel( new ContextSensitiveTableModel(data, columnNames)))) 
    • Calculation: A significant part of the report is the ability to use expressions to extend the data model. JIDE'sCalculatedTableModel allows for the addition of new columns to a data model based on small expressions (or scripts). Expressions can utilize values in other columns and other expressions. The CalculatedTableModel is created by passing in another TableModel. In the sample,ReportTableModel is passed into theCalculatedTableModel constructor. (Note: The expression in the cell is based on cell values on the same row level. Expressions have no knowledge of previous or post row values.) The following demonstrates the data model:

      Data model with calculations:

      Column 1Column 2Computed Column 1

      Sample data:

      Sales PriceQuantity[Sales Price]*[Quantity]

    Table creation

    AggregateTable is created simply by passing in aTableModel. Additional functionality can be added toAggregateTable.AggregateView.getAggregateTable() in the sample code demonstrates the addition of column header features that allow for auto resizing, modifying the display state of columns, filtering and grouping.

    Table functionality

    The initial AggregateTable look and feel is similar to a JTable. The following are features of interest.

    • Sort: Click on table header to sort.
    • Filter: Click on the pull down next to the column to filter on values in the column. In Figure 4, the "Item Type" column's filter choice is displayed.

      missing image
      Figure 4. Filter

    • Group, hide, and more: Right-clicking on the column header allows the user to group on the column, hide the column, reset the view, and other things (which options are displayed to the user is controlled inAggregateView.getAggregateTable()). Figure 5 illustrates this.

      missing image
      Figure 5. Group

    • Subtotals and grand totals: Once a column is grouped, the user can select the desired subtotals and grand totals. Figure 6 illustrates this.

      missing image
      Figure 6. Subtotals and grand totals

    Initial view

    Figure 7 shows a partial view of the initial report.

    missing image
    Figure 7. Initial view

    Modified view

    Figure 8 shows a partial view after the user right-clicked on the "Date Month" header and selected "Group this Column"; then did this again with "Item Type"; and then selected to create a "Date Month" subtotal.

    missing image
    Figure 8. Modified view

    Application features

    A toolbar was added to the demo application to allow the user to:

    • Add a formula
    • Export to Excel
    • Save and restore user layout changes
    • Switch between the detail aggregate view and the summary pivot view

    Add a formula

    As of this writing, JIDE does not provide an interface for users to enter a formula; therefore you will need to provide your own GUI if you want to allow users to enter formulas dynamically. The example in the article only applies a simple expression.

    To add a computed column, create a newExpressionCalculatedColumn and add the column to theCalculatedTableModel. The following is the code from the sample:

    final ExpressionCalculatedColumn commission = new ExpressionCalculatedColumn(getAggregateView().getTableModel(), "Commission", "([Amount])*.01"); getCalculatedModel().addColumn(commission); getCalculatedModel().fireTableStructureChanged(); 

    Note: There are numerous reasons to add computed columns. The SwingReportDemo.getCalculatedModel() decomposes each Date column into Year, Quarter, and Month columns. The additional columns allow the user more flexibility in grouping and subtotaling. The user can right-click on the header and hide columns; therefore you needn't be too concerned about making too much data available.

    Export to Excel

    The SwingReportDemo.uiExport() function demonstrates the use of JIDE Excel export feature. Since Aggregate and Pivot tables have separate export functions, the export task is delegated to the appropriate View classes (AggregateView or PivotView).

    Note: If your focus is true Excel functionality, products like JDataGrid from http://www.zfqjava.com/ do a great job of both importing and exporting Excel formatted files. JDataGrid tries to reproduce the Excel environment in Java without utilizing the OLE solution space.

    Save and restore user layout changes

    Once the user has modified the report layout, the layout can be saved and reused. JIDE does the layout persistence to XML. The sample code uses a file chooser, but persistence to a data store is more practical.

    Try: Play around with the report by grouping, subtotaling, and hiding columns, then save the report layout. Restart the application and load the saved report layout.

    Important: The layout will not save filters. The reason filters are not saved is that filters are customizable and difficult to generalize for XML.

    Switch views

    The Switch button allows the user to swap between Aggregate and Pivot views of the same data. The pivot view replicates much of the same behavior that exists in Excel pivot grid. Merely drag and drop columns to change the report layout, and right-click on the headers to see more Pivot column features. getPivotView()returns the Pivot view that is displayed:

    PivotView getPivotView() { if (m_pivotView == null) { final String[] header = { "Date Month" }; final String[] row = { "Item Type", "Item Name" }; final String[] data = { "Amount" }; m_pivotView = PivotView.newUi(getCalculatedModel(), header, row, data); } return m_pivotView; } 

    Figure 9 displays the Pivot view on switch.

    missing image
    Figure 9. Pivot report on switch

    The good, the bad, and the ugly

    JIDE is no panacea for reporting. The following are the things I like, and issues to be aware of when developing with JIDE.

    • Knowledge reuse: Re-use Java Swing knowledge, technology and techniques.
    • Integration: Allows the user more flexibility at the report application level, without all the integration issues of the classical report writer.
    • Now, not tomorrow: Quick solution for many problems without engagement of a report writer.
    • Reuse of techniques between reports: Copy and pasting is rampant in classical report writing. If you see someone write reports over time, the style and approach changes drastically. With limited or no refactoring, classical reporting becomes a mess to maintain.
    • Support: JIDE response to bugs and requests is exceptional. JIDEs' goal is a release every other week, which reduces the wait time for an enhancement and fix. The following is a link to JIDE's enhancements and bug fix history: http://www.jidesoft.com/history/
    • Expressions: JIDE uses an expression parser from an open source project called prefuse (http://prefuse.org). Even though prefuse has low memory overhead, and is very responsive, prefuse is also very limited. Prefuse only deals with primitive types in numeric calculations (hence forget about specialized numeric types like Amount). Aggregate and Pivot will fail to respond if a divide by zero issue occurs.
    • Lack of aggregated functions: Aggregated functions like 'running totals' or 'balance' require two-pass calculations. Two-pass aggregations are commonplace in reporting. If your users can't live without the second pass calculations, JIDE won't work for you.
    • Summary Calculator: From a developer's perspective, much of JIDE's extensibility seems to revolve around context and catalog patterns. However, SummaryCalculator, used in Pivot tables, does not follow the catalog and context design patterns, and hence has limited usage. JIDE's Pivot table ships with numerous built in summary calculations. However,SummaryCalculator limitations will become a problem if more than a single additional summary calculation is required.
    • Printable objects: Classical report writers are developed from a print page perspective, while Swing is geared to the UI. Therefore don't have high expectations when trying to print JIDE tables. Nice looking printable objects are not only a problem with JIDE, but a problem with every Swing object I have ever used. Some products like DataGrid provide a printable version of their Swing objects, yet DataGrid printing is still less than perfect. Printing is a non-trivial problem to solve. Even popular products like Eclipse IDE started off doing a horrible job of printing, and have only gotten marginally better over time. Hence, do not expect the world when trying to print JIDE tables, it's a Java problem, and a problem that third party print libraries do not and cannot readily resolve.

    Final thoughts

    A more complete Swing based report solution could be developed by completing and generalizing the report code. Furthermore, as the JIDE components are improved, those new capabilities can be utilized to improve the example application. For now, the sample provides a great start on providing end users more flexibility inside a Swing application, while reducing headaches associated with incorporating a standard report writer.