Skip to main content

Dealing with Data with JavaFX Visuals and the NetBeans Platform

Posted by sean.mi.phillips on November 13, 2013 at 5:12 AM PST

So you need to build a visualization of data that must loaded and interface dynamically.  Good looks are important so you chose JavaFX but you are starting from an existing Swing based application.  No problem right? 
But what if you have a lot of data to load into your Scene? How do you maintain a positive User Experience?

Maybe you are using the NetBeans Platform because... well.. it's pretty awesome.  Operating your application is very responsive up until the point at which you parse and display the data, but then there is a slight wait or even hang while the JavaFX scene is constructed.  The end visual is great but the user experience is tainted by a multi-second wait. 

This can be dealt with, you just need to visually acknowledge the user with some sort of animation to let him know... "Hey I'm still here don't worry!"  No problem.  The following pattern is how you can easily accomplish this either from a straight Swing interop or embedded from a NetBeans Platform application.

To walk through this tutorial, it is assumed you have the following:

  • NetBeans Platform with a multiview TopComponent created
  • The JavaFX runtime added as a wrapped library or additional plugin
  • Some data and a JavaFX scene to display
  • Understanding of the standard JFXPanel interop pattern.

This tutorial was accomplished using NetBeans 7.4 which is just great... go get it!  The version of JavaFX is a 2.x runtime taken from Java 1.7u45.  If you are working from the early releases of JDK 8 then you won't have to worry about wrapping the JavaFX runtime into your NetBeans Platform app... its automagic. 

The assumption taken by this tutorial is that you would like to attach your JavaFX visual to a custom File Type that extends the MultiView element TopComponent API.  This is a very powerful pattern that is typical in RCP development in general and one that I use extensively in all my NASA ground system software.  A problem encountered here is when your file based data sets are relatively large.   Not "Big Data" large... so maybe we'll call it "Medium Data".  The problem is that when you switch from your default 'Source' view to your custom 'Visual' view there is a hang of sorts while your JFXPanel is constructed and loaded.  Starting here in your TopComponent constructor:

public final class SomeBigDataFileVisualElement extends JPanel implements MultiViewElement {

    private SomeBigDataFile obj;
    private JToolBar toolbar = new JToolBar();
    private transient MultiViewElementCallback callback;

    public SomeBigDataFileVisualElement(Lookup lkp) {
        obj = lkp.lookup(SomeBigDataFileDataObject.class);
        assert obj != null;
        initComponents();
        toolbar.setFloatable(false);
        final JFXSomeBigDataFilePanel jfxpanel = new JFXSomeBigDataFilePanel();        
        add(jfxpanel, BorderLayout.CENTER);
    }

You would see a blank TopComponent while waiting for your data to load:

image

Hmm... first impression is that your app is broke.  What if you just need 5 or 6 more seconds?  A lot of naive users will just roll their eyes and harumpf.  Well that's because you put all your JavaFX scene building in  inside of a Platform.runLater() call inside of the JFXPanel constructor.  A typical pattern.  Instead try building your scene in a JavaFX Task thread separate.  Then in your JFXPanel constructor merely build a very light layout with a ProgressIndicator.  Try the following pattern:

    public JFXSomeBigDataPanel() {
        super();
        Platform.setImplicitExit(false);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                createScene();
                
            }
        });         
    }    
    private void createScene() {
        pane = new BorderPane();
        barPi.setOpacity(0.6);        
        barPi.setVisible(true);
        pane.setTop(barPi);
        scatterPi.setOpacity(0.6);        
        scatterPi.setVisible(true);
        pane.setCenter(scatterPi);
        scene = new Scene(pane);
        setScene(scene);        
    }  

Which intends to load two complicated JavaFX charts (a StackedBarChar and a categorized ScatterPlot.  This will display some nice spinners on the screen. 

image

Notice the little spinner above the big spinner?  Yeah I used a BorderPane... for the example and those are the default resizes that JavaFX does.  Plus trust me they are spinning.  Customize with whatever lightweight graphics you like.

Now move all your heavy lifting into a secondary method that encapsulates the effort within a javafx.concurrent.Task object:

    public void refreshScene(final SomeBigDataFileDataObject fileObj) {
        Task loadTask = new Task() {
            @Override
            protected Object call() throws Exception {
                dobj = fileObj;
                SomeBigDataFile dataFile = new SomeBigDataFile(dobj.getPrimaryFile().getPath(),true);
                barChartXAxis.setCategories(FXCollections.<String>observableArrayList(dataFile.getColumnLabelsContent()));                
                buildStackedBarChart();               
                buildScatterChart();
                Platform.setImplicitExit(false);
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        pane.setTop(stackedBarChart);
                        barPi.setVisible(false);
                        pane.setCenter(scatterChart);
                        scatterPi.setVisible(false);
                    }
                });
             return null;   
            }
        };
        new Thread(loadTask).start();
    }

Here's something that may catch some of you like it did me:  make sure your scene construction is done inside the Task call() method but outside the Platform.runLater() run() method.  Anything inside the Platform.runLater() will still block and will not be done Asynchronously.  The only thing you need inside your runLater() is what MUST be there which is anything that directly changes the screen itself, such as adding your fully constructed JavaFX components to the scene.

Then merely call your new refreshScene() method after you have added your JFXPanel component to your original Swing view.  I merely add this back into the original TopComponent constructor:

    public SomeBigDataFileVisualElement(Lookup lkp) {
        obj = lkp.lookup(SomeBigDataFileDataObject.class);
        assert obj != null;
        initComponents();
        toolbar.setFloatable(false);
        final JFXSomeBigDataFilePanel jfxpanel = new JFXSomeBigDataFilePanel();        
        add(jfxpanel, BorderLayout.CENTER);
        jfxpanel.refreshScene(obj);
    }

And voila you get some spinners display followed by your complete JavaFX scene.  For example straight from my NASA GGSS ground system software:

image

Bonus:

You now have a method that can be called whenever you wish to update your view, like say if your file data is changed either manually or by external process.  Mechanisms are provided by the NetBeans Platform to do this and explaining such is provided by the API and other Tutorials.