JavaFX Charting API - Business Functionality at Last

This weekend I dived back into JavaFX again. JavaFX 1.2 has been out for a while now, and I have wanted to try out JavaFX again, ever since I took 1.0 for a spin about a year ago. At that time there was no support for JavaFX on Linux. This, combined with the fact that it JavaFX had no UI controls for data input, meant that I had no practical use for it since I am not really a visual designer but more of an application developer. Luckily now ,JavaFX runs officially on Linux, and whats more 1.2 introduced the first version of the charting API, somethings that worth looking at!

Flex Charting only option before

 Charting is one of those things, like Internationalization that gets left to the end. Unlike internationalization,optimization and security :), it usually pays big time to do charting when it comes to convincing the CFO of the customer to pay you :) Its the tip of the iceberg stuff which they see and usually, sadly, rate the whole system on. Until now, Flex has been the only real option available, for quickly generating some eye-candy and maybe adding a zero on to the invoice :)

 The big minus for me with Flex, besides not being completely open source, was the lack of available tools to develop Flex apps on Linux. Coding mxml and action script by hand, without code completion and syntax highlighting, made it a difficult task. With JavaFX both Netbeans and Eclipse have great, and improving, tool support. This makes learning the language so much easier and its much quicker to produce usable code. I just hope that soon its easy to integrate JavaFX with our Maven build scripts and processes.

JavaFX Charting API Demo

In the demo below I make use of a 3D pie chart, a line chart and a bar chart. The basic demo is to demonstrates some "drill down" capabilities. I.E one can drill down from the pie chart to the line chart and then the bar chart.

To see the different charts:

  • click on a slice in the pie chart,
  • a data point in the line chart and
  • a bar in the bar chart.

Sorry for the lack of visual clues, like the cursor changing when hovering over the relevant graphic but I couldn't figure out how to change the cursor to a hand just for the data points, all my effort resulted in the cursor changing to a hand anywhere on the graph. It is possible however to have different chart generated for different data points as the Data objects have an action function associated for each.

Charting Demo

JavaFX Charting API still requires more work

 The JavaFX charting api still requires some work. The line chart does not have a convenient DateAxisValue which is used quiet often. Also you need to stipulate the min and max values for the charts yourself for the value axis. If you don't the graphs don't show. It would be better if the framework worked out some reasonable defaults for you. This is one of the short-comings of JavaFX the language. You cannot stipulate mandaorty fields that need to be initialised before the object can be used. A user needs to consult the documentation, or just have to quess, as I didn't find this in the API docs.

Animations and transitions are real easy to do in JavaFX, the transitions available at present aren't as impressive as with Flex but I am sure these will come. In the demo I make use of a simple fade transition to transission between graphs.

JavaFX not Ready for Use

While creating this demo application I initially got carried away and took a first stab at writing a model,view updater framework to allow the new UI controls to read and write their textual representation to and from their backing objects. This for the first time made me have to deal with JavaFX as a language rather than its easy to use declarative format with which one can do so much nifty graphical stuff. I soon found out that JavaFX is far from ready for use in any kind of production environment. I hit 5 bugs, 2 show-stopper ones, at least for me, in 16 hours. The bugs were:

  • the chart api throws an error whenever it repaints itself,this seemed to have no negative effect (minor),
  • using the lookup function on the Scene object to retrieve node references, kind of like getNodeById from javascript, returned null for any object that extends CustomNode, (critical for me, can work around it but the code is ugly - major.)
  • the init and postinit blocks in a class that extends CustomNode appear to be ignored. Any variable initialised with them are null after the code blocks execute (major),
  •  there appears to be a problem using "bind with inverse" in some cases (major)
  •  binding the height and width attributes of a chart meant that the gradient fill stopped working (minor).

JavaFX the Language - Please Don't Let Simplicty Kill It

Beside the bugs above, I found that the reflection API is hard to understand. There is no documentation on how to use it. It seems almost impossible to get the class object of an instantiated object, and have it be past around like any normal object.  You can create an object using the API , if you know the string name for the class,but if you get given an object at runtime and want to determine its class it seems impossible. I hope this is because JavaFX is still new and not an attempt to simplify the language for visual designers.

I hope that in trying to make JavaFX a "simple" scripting language we don't loose out on some powerful language features that we need and expect as Java developers. I eventually stopped building the more complex JavaFX example and settled for the more simple one presented.

 Demo Source Code



package javafxapplication2;


import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.chart.PieChart3D;
import javafx.scene.chart.PieChart;
import javafx.scene.effect.DropShadow;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.part.NumberAxis;
import  javafx.scene.paint.LinearGradient;
import javafx.scene.chart.part.CategoryAxis;
import javafx.scene.chart.part.ValueAxis;
import  javafx.scene.chart.BarChart3D;
import javafx.scene.chart.BarChart;
import  javafx.scene.paint.Stop;
import  javafx.animation.transition.FadeTransition;
import javafx.scene.Node;
import javafx.scene.Cursor;

var stage:Stage;


def gradientFill:LinearGradient =  LinearGradient {
            startX: 0
            startY: 0
            endX: 1
            endY: 1
            stops: [
                       Stop {
                               offset: 0.0
                               color: Color.DARKBLUE;
                       }

                       Stop{
                               offset: 1.0
                               color: Color.LIGHTSKYBLUE
                       }
                    ]
 }

var salesByCountry:PieChart.Data[] =[ PieChart.Data {
                                    label: "South Africa"
                                    value: 140
                                }

                                PieChart.Data{
                                            label: "Botswana"
                                            value:100
                                     }

                                PieChart.Data{
                                            label: "Zimbabwe"
                                            value:20
                                     }

                                PieChart.Data{
                                            label: "Mozambique"
                                            value:45
                                     }

                                ];



  var pieChart:PieChart3D = PieChart3D {
            chartBackgoundFill:gradientFill;
            height: 550
            width: 550
            var opacity1 =1.0
            data: salesByCountry;
            pieEffect: DropShadow {
                offsetX:5
                offsetY:5
            }
            pieValueLabelFill: Color.BLACK
            pieLabelFill: Color.BLACK
            title: "Sales By Country 6 Month to Aug 2009"
            titleFill:Color.WHITE;
        }


function fade(fromVal:Float,toVal:Float,node:Node){
       FadeTransition {
           fromValue:fromVal
           toValue:toVal
           node: node
           duration: 1s
       }.play();
}



var lineChart:LineChart =  LineChart{
       height: 550
       width: 550
       data: [LineChart.Series {  data: [
               LineChart.Data {xValue: 1 yValue:10},
               LineChart.Data{ xValue:2 yValue:20},
               LineChart.Data{xValue:3 yValue:30}
               LineChart.Data {xValue: 4 yValue:10},
               LineChart.Data{ xValue:5 yValue:15},
               LineChart.Data{xValue:6 yValue:5}
]
           name: "Botswana"
            },
            LineChart.Series {  data: [
                           LineChart.Data {xValue: 1 yValue:20},
                           LineChart.Data{ xValue:2 yValue:30},
                           LineChart.Data{xValue:3 yValue:10},
                           LineChart.Data {xValue:4  yValue:40},
                           LineChart.Data{ xValue:5 yValue:20},
                           LineChart.Data{xValue:6 yValue:20},
]
                       name: "South Africa"
                   }
       ]
       showSymbols:true;
       title: "Sales By Country By Month - South Africa & Botswana (6 Months to Aug 2009)"
       titleFill:Color.WHITE;
       chartBackgoundFill:gradientFill;

       xAxis: NumberAxis {
           tickUnit:1
           label: "Months"
           lowerBound: 1
           upperBound: 6
           formatTickLabel:function(num:Float):String{
               if (num==1.0) {return "Mar 2009";}
               else if (num==2.0) {return "Apr 2009"}
               else if (num ==3.0) {return "May 2009"}
               else if (num==4.0) {return "Jun 2009"}
               else if (num==5.0) {return "Jul 2009"}
               else {return "Aug 2009"};

           }

       }

       yAxis: NumberAxis{
           tickUnit:10
           label: "Sales"
           lowerBound:0
           upperBound:50
       }

     }

   var barChart:BarChart3D = BarChart3D {
     height: 550
     width: 550
     chartBackgoundFill: gradientFill;
     title: "Sales By Product Botswana and South Africa"
     titleFill:Color.WHITE
     categoryAxis: CategoryAxis{
              categories: [ "Redhat","Ubuntu","Suse"];
              cursor:Cursor.HAND;

      }
      data: [BarChart.Series {  data: [
               BarChart.Data { category:"Redhat" value:40},
               BarChart.Data{ category:"Ubuntu" value:20},
               BarChart.Data{category:"Suse" value:10}
           ]
           name: "Botswana"
            },
            BarChart.Series {  data: [
                           BarChart.Data {category:"Redhat" value:20},
                           BarChart.Data{ category:"Ubuntu" value:30},
                           BarChart.Data{category:"Suse" value:10}]
                       name: "South Africa"
                   }
       ]
       categoryGap:5;
       valueAxis: NumberAxis{
            lowerBound:0
            upperBound:50
       }


   }


    function showLineChart():Void{
          fade(1.0,0.0,pieChart);
          delete pieChart from stage.scene.content;
          insert lineChart into stage.scene.content;
          fade(0.0,1.0,lineChart);
    }

    function showPieChart():Void{
          fade(1.0,0.0,barChart);
          delete barChart from stage.scene.content;
          insert pieChart into stage.scene.content;
          fade(0.0,1.0,pieChart);
    }

    function showBarChart():Void{
          fade(1.0,0.0,lineChart);
          delete lineChart from stage.scene.content;
          insert barChart into stage.scene.content;
          fade(0.0,1.0,barChart);
    }

public function run(){
        for (data in pieChart.data){
                data.action = showLineChart;
        }
        for (series in lineChart.data){
                for (data in series.data)
                    data.action = showBarChart;
        }
        for (series in barChart.data){
                for (data in series.data)
                    data.action = showPieChart;
        }

        stage = Stage {
        title: "Chart Demo"
        scene: Scene {
            width: 550
            height: 550
            content: [
                                pieChart
                ]
        }
    };
}

 

Comments

You should look at yous browser.jnlp file. I have that error in FF
java.io.FileNotFoundException: JNLP file error: http://www.jumpingbean.co.za/files/JavaFXApplication2_browser.jnlp. Please make sure the file exists and check if "codebase" and "href" in the JNLP file are correct.
at sun.plugin2.applet.JNLP2Manager.loadJarFiles(Unknown Source)
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

To me that means that href, or codebase properties are not set propely, maybe You post them to standard NetBeans Localhost serlet?

After fixing that fill free to delete this comment :)

Mark Clarke's picture

thanks for the feedback. I have uploaded the files. Let me know if it works because I had a look at the config files and it has some file references in them. The applet works my side without these so its difficult to test with them.thanks

Mark Clarke's picture

Cheched at the office and it works on the Ubuntu machines. Tried on someone's windows machine at a client and it seemed to have problems, wasn't sure if it was the firewall. Get as far as the spinning Java logo. Oh well. Guess I got to learn a bit more about deploying JavaFX applets. I hope that most get a chance to see the demo. You will need the latest version of Java installed though as it uses the cutting edge release of JavaFX.

Thanks for the post.
I have also played around with the chart API and hit issues with bugs as well.

I'd encourage you to raise the defects in JIRA if you haven't already done so

http://javafx-jira.kenai.com/

Actually that is a cool resource you have here! I will bookmark this and make sure to read offten, seems professional xoxo