JavaFX Charts for Kirk Pepperdine's VisualVM Plugin

Kirk Pepperdine wrote a VisualVM plugin sometime ago for displaying Java's memory pool statistics:

Sources are here: http://java.net/projects/memorypoolview 

Let's replace those charts with some of the cool charts provided by JavaFX. In real life, of course, the charts below are all squiggly and moving like a stock chart:

The data displayed above isn't real. The blog entry you're currently reading only deals with embedding JavaFX charts into Kirk's VisualVM plugin. It does not deal with the question about how to update those charts when the memory pool changes. That is a different discussion for another time. So, to get started, from the above, as you can see, I like this NetBeans IDE sample for JavaFX, which, as you can see from the above, would be a cool replacement for those blue lines in Kirk's plugin:

Of course, the above is an image; in real life it is an animated stock line like you might expect in stock applications. The above is one of the standard JavaFX samples that comes with NetBeans IDE, named "ChartAdvancedStockLine".

Let's start by transforming Kirk's plugin so that it is embedded in a nice comfy JavaFX environment. That entails two steps:

  1. Wrap the jfxrt.jar (the JavaFX Runtime JAR) into the module. Though a preferable approach in a modular application, such as VisualVM, would be to create a separate module for the JAR, it is quite convenient to stick it into the module where the JavaFX functionality is going to be found. That way we'll only have one NBM to distribute, instead of two. So, right-click the module, choose Properties, go to Libraries | Wrapped JARs, click Add JAR, and browse to the "jfxrt.jar" in your JavaFX distribution.

  2. Next, we need to include the JavaFX native libraries in our module. At some stage, maybe already in JDK 7 Update 4, those native libraries will at least be in the JDK. And, hopefully, ultimately, they'll be in the JRE (or in some additional JRE) so that the user will already have the native libraries available. Of course, since VisualVM is a JDK tool, having the JavaFX native libraries in the JDK will be exactly what is needed. However, in my case, I am on JDK 7 Update 2, so I am going to include the native libraries (for Windows only, since I'm on Windows right now as I make this plugin) for Windows only, just to prove that it works. Switch to the Files window in NetBeans IDE and copy all the DLL's into "release/modules/bin", which should be a directory you create for the first time. (In "release/modules/ext" you'll find the JAR you wrapped in the previous step.) Again, note that this is a temporary step, since the DLL's will ultimately be in the JDK already.

Now we're good to go. Let's start coding. In fact, not much coding, mostly refactoring, as you'll see.

  1. From the JavaFX example illustrated above, i.e., "ChartAdvancedStockLine", copy the variables at the top of the (only) class in the sample, i.e., "ChartAdvancedStockLine.java" to the top of Kirk's plugin's "MemoryPoolPanel.java" file:
    private XYChart.Series hourDataSeries;
    private XYChart.Series minuteDataSeries;
    private NumberAxis xAxis;
    private Timeline animation;
    
    private double hours = 0;
    private double minutes = 0;
    private double timeInHours = 0;
    private double prevY = 10;
    private double y = 10;
  2. Next copy the bulk of the code into the same class in Kirk's plugin, i.e., copy "createChart()", "nextTime()", "plotTime()", "play()", and "stop()", as shown below, i.e., the below is literally copied and pasted from the sample code:
    protected LineChart createChart() {
        xAxis = new NumberAxis(0, 24, 3);
        final NumberAxis yAxis = new NumberAxis(0, 100, 10);
        final LineChart lc = new LineChart(xAxis, yAxis);
        // setup chart
        lc.setId("lineStockDemo");
        lc.setCreateSymbols(false);
        lc.setAnimated(false);
        lc.setLegendVisible(false);
        lc.setTitle("ACME Company Stock");
        xAxis.setLabel("Time");
        xAxis.setForceZeroInRange(false);
        yAxis.setLabel("Share Price");
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "$", null));
        // add starting data
        hourDataSeries = new XYChart.Series();
        hourDataSeries.setName("Hourly Data");
        minuteDataSeries = new XYChart.Series();
        minuteDataSeries.setName("Minute Data");
        // create some starting data
        hourDataSeries.getData().add(new XYChart.Data(timeInHours, prevY));
        minuteDataSeries.getData().add(new XYChart.Data(timeInHours, prevY));
        for (double m = 0; m < (60); m++) {
            nextTime();
            plotTime();
        }
        lc.getData().add(minuteDataSeries);
        lc.getData().add(hourDataSeries);
        return lc;
    }
    
    private void nextTime() {
        if (minutes == 59) {
            hours++;
            minutes = 0;
        } else {
            minutes++;
        }
        timeInHours = hours + ((1d / 60d) * minutes);
    }
    
    private void plotTime() {
        if ((timeInHours % 1) == 0) {
            // change of hour
            double oldY = y;
            y = prevY - 10 + (Math.random() * 20);
            prevY = oldY;
            while (y < 10 || y > 90) {
                y = y - 10 + (Math.random() * 20);
            }
            hourDataSeries.getData().add(new XYChart.Data(timeInHours, prevY));
            // after 25hours delete old data
            if (timeInHours > 25) {
                hourDataSeries.getData().remove(0);
            }
            // every hour after 24 move range 1 hour
            if (timeInHours > 24) {
                xAxis.setLowerBound(xAxis.getLowerBound() + 1);
                xAxis.setUpperBound(xAxis.getUpperBound() + 1);
            }
        }
        double min = (timeInHours % 1);
        double randomPickVariance = Math.random();
        if (randomPickVariance < 0.3) {
            double minY = prevY + ((y - prevY) * min) - 4 + (Math.random() * 8);
            minuteDataSeries.getData().add(new XYChart.Data(timeInHours, minY));
        } else if (randomPickVariance < 0.7) {
            double minY = prevY + ((y - prevY) * min) - 6 + (Math.random() * 12);
            minuteDataSeries.getData().add(new XYChart.Data(timeInHours, minY));
        } else if (randomPickVariance < 0.95) {
            double minY = prevY + ((y - prevY) * min) - 10 + (Math.random() * 20);
            minuteDataSeries.getData().add(new XYChart.Data(timeInHours, minY));
        } else {
            double minY = prevY + ((y - prevY) * min) - 15 + (Math.random() * 30);
            minuteDataSeries.getData().add(new XYChart.Data(timeInHours, minY));
        }
        // after 25hours delete old data
        if (timeInHours > 25) {
            minuteDataSeries.getData().remove(0);
        }
    }
    
    public void play() {
        animation.play();
    }
    
    public void stop() {
        animation.pause();
    }
    
  3. Now we'll add our first bit of unique code to the class, though the body (i.e., the animation stuff) is directly copied from the JavaFX sample:
    private void initAndShowGUI() {
        final JFXPanel fxPanel = new JFXPanel();
        add(fxPanel, BorderLayout.CENTER);
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                fxPanel.setScene(new Scene(createChart()));
                animation = new Timeline();
                animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000 / 60), new EventHandler() {
                    @Override
                    public void handle(ActionEvent actionEvent) {
                        // 6 minutes data per frame
                        for (int count = 0; count < 6; count++) {
                            nextTime();
                            plotTime();
                        }
                    }
                }));
                animation.setCycleCount(Animation.INDEFINITE);
                play();
            }
        });
    }

    So, we use the JFXPanel, since that's what it's for, i.e., it is a Swing container for embedding JavaFX code, in this case a Scene defined by our LineChart, which is returned by the "createChart()" method, which we moved into the class in the previous step. The initialization of the animation functionality is a direct copy/paste from the sample.


  4. At this stage, when you install the plugin, the JavaFX chart is running inside VisualVM! Now you need to customize the JavaFX chart to display the same info as in Kirk's original code. For example, use the "memoryPoolUpdated" method, which is an override from something Kirk created, to do something like this:
    @Override
    public void memoryPoolUpdated(MemoryPoolModel model) {
        long[] dataPoints = new long[2];
        dataPoints[0] = model.getCommitted();
        dataPoints[1] = model.getUsed();
    //        chart.addValues(System.currentTimeMillis(), dataPoints);
        animation.getKeyFrames().add(
             new KeyFrame(Duration.millis(System.currentTimeMillis()), 
             new EventHandler() {
        ...
        ...
        ...

    The point is, when the memory pool changes, you need to update the JavaFX chart. That synchronization is outside the scope of what I'm trying to do here, i.e., I want to show how the JavaFX charts can be embedded in VisualVM. If you're at this stage of the story, you're welcome to develop Kirk's plugin further, to show the JavaFX chart changing as the memory pool changes.

When I run it at this point in the proceedings, Kirk's plugin looks as follows in VisualVM:

Looking forward to what Kirk thinks of this and also to seeing this plugin come to a happy resolution in the JavaFX ecosystem!

Comments:

This is seriously cool stuff, I see lots of potential ideas coming out of this <rubs hands with glee>

Posted by Martijn Verburg on May 09, 2012 at 09:15 AM PDT #

Yup! The most obvious goodness of JavaFX for serious applications lies in its chart technology, which is almost trivial to embed in existing (i.e., Swing) apps. So don't be misled by the rotating yellow circle demos and the brick breaker and puzzles and other trivialities like that. Think about it as a business technology, but you need to dig a bit to be able to validate that perspective.

Posted by Geertjan on May 09, 2012 at 09:52 AM PDT #

For the brave of heart and generous of spirit:

http://vimeo.com/41131627

Posted by Geertjan on May 09, 2012 at 10:02 AM PDT #

Nice!!! The VisualVM way of updating the chart is to put the data into an array and then update the data from that array. I'm not familiar with the JavaFX API's but is EventHandler() the needed method to update this?

animation.getKeyFrames().add(
new KeyFrame(Duration.millis(System.currentTimeMillis()),
new EventHandler() { //implementation to update goes here?

memoryPoolUpdated() is called when the underlying model changes. In this case the model changes when the MBean cache is flushed. To write this up I'd have memoryPoolUpdated() call into the EventHandler() (which could be instantiated in the constructor). Once the wiring is completed it would be nice to have this checked into the reference project on java.net

Posted by Kirk on May 09, 2012 at 08:41 PM PDT #

Geertjan could You explain a bit more embedding JavaFX scenes in many TopComponents ? The code above works very well, but when I create another TC with another chart the issue arises when I close one TC, on the other the whole FX Platform is also closed making other TC "unrenderable".
In the 2.2beta there is a new function Platform.setImplicitExit(false) which I have to insert before FX platform initialization (eg. creation of JFXPanel) - but it works, when i put it in *each* TC.

What is a preferred 'flow' of the FX platform libraries in the netbeans rcp ? Where to put above code; where explicit exit the platform ?

Posted by slawek on June 09, 2012 at 11:28 PM PDT #

Brilliant stuff!

Posted by oilamah on June 25, 2012 at 11:28 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
19
21
22
23
24
25
26
27
28
29
30
   
       
Today