Graphing Sales Performance Data

By Kuldip Pabla, March 22, 2010

In addition to using JavaFX technology to create games and other consumer applications, you can develop business applications with JavaFX. This sample uses simple animation and UI-control chart Components to help you visualize annual sales performance data.

The TableInsights application displays a table of sales performance data for each month over one-year period for various regions. It uses the UI control ScrollView at the bottom to navigate left and right to view the data table. The regional data for each month is displayed in rows and monthly data for each region is displayed in columns. As the mouse cursor hovers over a "region" or a "month." the numerical data is graphed by using the charting capabilities of JavaFX. The data is visualized as bar graphs. When you click Regions at the upper-right corner conner cell, a line graphs that uses the LineChart component is displayed to visualize the performance data. This example demonstrates the ability of JavaFX to enhance visualization and to help interpret large amounts of data.

Understanding the Code

The sample source code demonstrates the best practices for making use of the UI control ScrollView. ScrollView is used to pan horizontally after the stage is set. CSS properties are dynamically changed to add color and enhance visualization. The CSS properties are defined in the magic.css file, and can be easily customized. As the mouse cursor hovers over the title column or row, the CSS properties are altered to change the color and the charting capability of JavaFX is used to create horizontal or vertical bar graphs. The classes that reside in the javafx.scene.chart.part help you to set up the look and feel of the chart.

The scrollview function within the TableNode class implements the scroll feature of this sample. javafx.scene.control.ScrollBarPolicy and javafx.scene.control.ScrollView help you set up and control scrolling.

Source Code
public class TableNode extends CustomNode {
    public var colHeaders = [""] on replace {
        numcols = sizeof colHeaders;
    };
    public var rowHeaders = [""] on replace {
        numrows = sizeof rowHeaders;
    };
    public var data = [];
    var numrows: Integer = 10;
    var numcols: Integer = 12;
    var dataGrid = Group {};
    var rowButtons = Group {};
    var colButtons = Group {};

    function reset() {
        for (i in [0 .. sizeof dataGrid.content]) {
            def c = dataGrid.content[i] as DataCell;
            c.vertBars = c.horiBars = false;
        }
    }

    function onCellSelect(cell : HeaderCell) {
        reset();
        def row = cell.row;
        def col = cell.col;
        def data = cell.data;
        //println ("row: {row} - col:{col} - data:{data}");
        if (data == -1) {
            def index1 = (row-1)*numcols;
            def index2 = index1 + numcols-1;
            for (i in [index1 .. index2]) {
                def c = dataGrid.content[i] as DataCell;
                c.horiBars = visible;
            }
        } else {
            for (i in [0 .. sizeof dataGrid.content-1]) {
                if ( i mod numcols == col) {
                    def c = dataGrid.content[i] as DataCell;
                    c.vertBars = visible;
                }
            }
        }

    }
	
	var scrollview = ScrollView {
		styleClass : "magScrollView";
		layoutInfo: LayoutInfo { width: 640 height: 373 }
		layoutX: 80
		layoutY: 0
		node: Group {content: [colButtons, dataGrid]}
		vbarPolicy: ScrollBarPolicy.NEVER;
	};
	
    override function create() : Node {
        blocksMouse = true;

        insert HeaderCell {
            data: -1 // indicates column
            content: "Regions";
            row: 0
            col: 0
        } into rowButtons.content;
        // Table Header Cells
        for(col in [0..numcols-1]) {
            insert HeaderCell {
                data: 1 // indicates row
                content: colHeaders[col]
                row: 0
                col: col
                onMousePressed: function(e) {
                    def enode = e.node as HeaderCell;
                    enode.stroke = Color.web("0x00A2FF");
                    enode.strokeWidth = 5;
                    onCellSelect(enode);
                }
                onMouseExited: function (e) {
                    def enode = e.node as HeaderCell;
                    enode.stroke = Color.web("0x222222");
                    enode.strokeWidth = 0.5;
                    reset();
                }

            }
            into colButtons.content;
        }

        // Row Header Cells
        for(row in [1..numrows]) {
            insert HeaderCell {
                data: -1
                content: rowHeaders[row-1]
                row: row
                col: 0
                onMousePressed: function(e) {
                    onCellSelect(e.node as HeaderCell);
                    def enode = e.node as HeaderCell;
                    enode.stroke = Color.web("0x00FF9C");
                    enode.strokeWidth = 5;
                }
                onMouseExited: function (e) {
                    def enode = e.node as HeaderCell;
                    enode.stroke = Color.web("0x222222");
                    enode.strokeWidth = 0.5;
                    reset();
                }
            }
            into rowButtons.content;
        }

        // actual data grid
        for(row in [0..numrows - 1]) {
            for(col in [0..numcols - 1]) {
                insert
                DataCell {
                    data: data[(row)*(numrows)+col] as Number
                    row: row
                    col: col
                }
                into dataGrid.content;
            }
        }

        Group {
            content: [rowButtons, scrollview];
        }
    }
}

Figure 1: TableNode Class

The LineChart in the MagicChart CustomNode implements charting functions. You can navigate to LineChart by clicking the Regions cell.

Source Code
public class MagicChart extends CustomNode{

    .... 

    var chartSeries : LineChart.Series[] = [
        for (region in [0..numrows-1]) {
            LineChart.Series {
                name:  rowHeaders[region]
                data: [
                    for (i in [0..numcols-1]) {
                       LineChart.Data {xValue: i yValue: data[region*numcols + i]}
                    }
                ]
            }
        }
    ];

    var chart = LineChart {
        layoutInfo : LayoutInfo { width: width - 10 height: height }
        legendSide: Side.TOP
        showSymbols: false
        
        xAxis: NumberAxis {
            lowerBound: 0
            upperBound: 11
            tickUnit: 1
            label: "Month"
            tickMarkLength: 20
        }
        yAxis:  NumberAxis {
            lowerBound: 0
            upperBound: 50
            tickUnit: 5
            label: "Sale"
            tickMarkLength: 10
            tickMarkVisible: false
        }
        data: chartSeries
    };
    
	....
	
}

Figure 2: MagicChart Class