Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

How to Chart real time streaming data using AreaChart in JAVAFX 2

947710Jul 5 2012 — edited Jul 9 2012
Requirement- Build an animated AreaChart with real time streaming data. Maybe plot 300 data points every 1 sec.

Details- So I need to read real time streaming data from a medical device, of a patient's breathing pattern and display it in a waveform fashion using AreaChart in JavaFX. I'm new to JavaFX and so I built a small POC, to see how concurrency and animation works in JavaFX.

The concept works and I'm happy with the basic test, as far as implementing the functionality. But I'm not happy with the performance I'm getting from the code below.

In the working code below, I create a separate thread to simulate data fetching from the medical device. The thread just generates a random number and adds it to a ConcurrentLinkedQueue.

The JavaFX Application thread pulls this data out from queue, via the Timeline, and adds it to an AreaChart Series.

This sort of gives me the animation I need and the data is being added in run time. You can copy-paste this code and test it..It should work.

BUT the performance is not impressive - CPU goes to 56% usage - I have a Intel Core 2 Duo @ 2.53 GHZ and 4GB ram on my laptop. My graphics card is Mobile Intel 4 Series express with latest driver.

How can I improve this animation or plotting of real time data, in order to get better performance?

NOTE: I'm willing to compromise on the animation, if its the bottle neck. I'm open to an implementation like shown here
http://smoothiecharts.org/ where the waveform is just prebuilt and just streaming from right to left.





import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
import javafx.util.Duration;

+/**+
* A chart that fills in the area between a line of data points and the axes.
* Good for comparing accumulated totals over time.
*+
* @see javafx.scene.chart.Chart
* @see javafx.scene.chart.Axis
* @see javafx.scene.chart.NumberAxis
* @related charts/line/LineChart
* @related charts/scatter/ScatterChart
*/
+public class AreaChartSample extends Application {+
private Series series;
private int xSeriesData=0;
private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private Timeline timeline2;
private SequentialTransition animation;

+private void init(Stage primaryStage) {+
Group root = new Group();
primaryStage.setScene(new Scene(root));

NumberAxis xAxis = new NumberAxis();
xAxis.setAutoRanging(true);

NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(true);

+//-- Chart+
final AreaChart<Number,Number> sc = new AreaChart<Number,Number>(xAxis,yAxis);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");

+//-- Chart Series+
series=new AreaChart.Series<Number,Number>();
series.setName("Area Chart Series");
series.getData().add(new AreaChart.Data<Number, Number>(5d, 5d));
sc.getData().add(series);


root.getChildren().add(sc);



+}+

+@Override public void start(Stage primaryStage) throws Exception {+
init(primaryStage);
primaryStage.show();

+//-- Prepare Executor Services+
executor = Executors.newCachedThreadPool();
addToQueue=new AddToQueue();
executor.execute(addToQueue);


+//-- Prepare Timeline+
prepareTimeline();


+}+

+public static void main(String[] args) { launch(args); }+

+private class AddToQueue extends Thread {+

+public void run(){+

+try {+
Thread.currentThread().setName(Thread.currentThread().getId()"-DataAdder");+
+//-- Add Random numbers to Q+
dataQ.add(Math.random());
Thread.sleep(50);

executor.execute(addToQueue);

+} catch (InterruptedException ex) {+
Logger.getLogger(AreaChartSample.class.getName()).log(Level.SEVERE, null, ex);
+}+

+}+
+}+

+//-- Timeline gets called in the JavaFX Main thread+
+private void prepareTimeline(){+
+//-- Second slower timeline+
timeline2 = new Timeline();
+//-- This timeline is indefinite.+
timeline2.setCycleCount(Animation.INDEFINITE);

timeline2.getKeyFrames().add(
+new KeyFrame(Duration.millis(100), new EventHandler<ActionEvent>() {+
+@Override public void handle(ActionEvent actionEvent) {+
addDataToSeries();

+}+
+})+
+);+

+//-- Set Animation- Timeline is created now.+
animation = new SequentialTransition();
animation.getChildren().addAll(timeline2);
animation.play();

+}+

+private void addDataToSeries(){+

for(int i=0;i<20;i+){ //-- add 20 numbers to the plot+
+if(dataQ.isEmpty()==false) {+
series.getData().add(new AreaChart.Data(xSeriesData+,dataQ.remove()));+

+//-- Get rid of a bunch from the chart+
+if (series.getData().size() > 1000) {+
series.getData().remove(0,999);
+}+

+}+
+else{+
return;
+}+
+}+
+}+


+}+

Edited by: 944707 on Jul 5, 2012 11:11 PM

Edited by: 944707 on Jul 6, 2012 9:15 AM

Comments

Paulzip

Post code, most people on these forums refuse to download files included zips.

What are you trying to achieve exactly?  Can you use XMLTable / XMLQuery to parse the doc instead?

Paulzip

Could be...

Problem (memory leak using DBMS_XMLPARSER):

Unpublished Bug:8918821 – MEMORY LEAK IN DBMS_XMLPARSER IN QMXDPLS_SUBHEAP closed as “not a bug”. The problem is caused by the fact that the XML document is created with XMLDOM.CREATEELEMENT, but after creation XMLDOM.FREEDOCUMENT is not called. This causes the XML used heaps to remain allocated. Every new call to XMLDOM.CREATEELEMENT will then allocate a new heap, causing process memory to grow over time, and hence cause the ORA-4030 error to occur in the end.

Solution:

To implement a solution for this issue, use XMLDOM.FREEDOCUMENT to explicitly free any explicitly or implictly created XML document, so the memory associated with that document can be released for reuse.

Mark S NHS

@Hi Paulzip

Thanks for your post.

I have a PL/SQL application that uses the dbms_xmlparser and xlst processor to unpack a CLOB field holding XML (originally from an HL7 message).  It would need a major rewrite to not use the dbms_xmlparser and xslprocessor functions.

I am

Here are snippets of the pl/sql code showing what I am trying to do-EPDSDEV databasePL/SQL code

Calling routine (snippets)....

..

cursor c1 is select  xml,              seqno      from    hl7.xml_from_tie_deploy      where  status = 'UNPROCESSED'      and rownum < 1000      order by seqno;

begin

open c1; fetch c1 into l_clob, l_seqno;....while not c1%notfound loop

-- Create a parser.

      l_parser := dbms_xmlparser.newParser;

-- Parse the document and create a new DOM document.

      dbms_xmlparser.parseClob(l_parser, l_clob);
  
     -- Free any resources associated with the document before it is used
      dbms_xmldom.freeDocument(l_doc);
  
      l_doc := dbms_xmlparser.getDocument(l_parser);

-- Free resources associated with the CLOB and Parser now they are no longer needed.
     dbms_xmlparser.freeParser(l_parser);

..

l_nl := dbms_xslprocessor.selectNodes(dbms_xmldom.makeNode(l_doc),l_rootname||'/MSH');

inMSH IN dbms_xmldom.DOMNode,   -- inMSH is l_nl

..

dbms_xslprocessor.valueOf(inMSH,'MSH.1/text()',l_fieldsep); -- fieldsep   This is the function which sometimes retrieves a null value

..

      -- Free any resources associated with the document now it      -- is no longer needed.     

dbms_xmldom.freeDocument(l_doc);

<<fetch_next>>fetch c1 into l_clob, l_seqno;

end loop

Paulzip

Whilst I understand you say it's a big rewrite, the approach you are using is very inefficient.  You are loading the full DOM into memory, a full parse tree into memory, yet you may only be reading a few XPath values.  That's where XMLTable excels, it doesn't do that if you are using absolute XPath.

Try moving the contents of your code into a sub procedure,  If it is some memory leak bug, scoping might fix it - however I suspect the problem is with your code.  You should also handle freeing of resources on exceptions.  Your code isn't very robust or modularised to be honest, here's my version of your code....

declare

  cursor c1 is

    select  xml, seqno    

    from    hl7.xml_from_tie_deploy    

    where  status = 'UNPROCESSED' and rownum < 1000    

    order by seqno;

  procedure ProcessXML(pClob clob) is

    l_parser dbms_xmlparser.Parser;

    l_doc dbms_xmldom.DOMDocument;

    procedure FreeResources is

    begin

      if l_doc is not null then

        dbms_xmldom.freeDocument(l_doc);

        l_doc := null;      -- Just in case there is garbage collection leaks

      end if;

      if l_parser is not null then

        dbms_xmlparser.freeParser(l_parser);

        l_parser := null;

      end if;

    end;

  begin

-- Create a parser.

    l_parser := dbms_xmlparser.newParser;

-- Parse the document and create a new DOM document.

    dbms_xmlparser.parseClob(l_parser, l_clob);

 

    l_doc := dbms_xmlparser.getDocument(l_parser);

    l_nl := dbms_xslprocessor.selectNodes(dbms_xmldom.makeNode(l_doc),l_rootname||'/MSH');

    --inMSH := .....       NOT SURE WHAT YOU ARE DOING HERE

    dbms_xslprocessor.valueOf(inMSH,'MSH.1/text()',l_fieldsep); -- fieldsep   This is the function which sometimes retrieves a null value

          -- Free any resources associated with the document now it      -- is no longer needed.    

    FreeResources;

  exception

    when OTHERS then

      FreeResources;

      raise;  

  end;

begin

  open c1;

  loop

    fetch c1 into l_clob, l_seqno;

    exit when not c1%notfound;

    ProcessXML(l_clob);

  end loop;

  close c1;

end;

cormaco

I have a PL/SQL application that uses the dbms_xmlparser and xlst processor to unpack a CLOB field holding XML (originally from an HL7 message). It would need a major rewrite to not use the dbms_xmlparser and xslprocessor functions.

All you need to parse a CLOB to XMLTYPE is to call  XMLTYPE(l_clob) and then use XMLQUERY or XMLTABLE to extract the data

Mark S NHS

Hi Paulzip and cormaco

Many thanks for your tips. I will look into using XMLTABLE. Can I use XMLQUERY and XMLTABLE against the same XMLTYPE object? Which is more efficient? I need to do some sophisticated XML querying to unpack repeating nodes/segments - would I need to use XMLQUERY to do this?

Paulzip

Mark S NHS wrote:

Hi Paulzip and cormaco

Many thanks for your tips. I will look into using XMLTABLE. Can I use XMLQUERY and XMLTABLE against the same XMLTYPE object? Which is more efficient? I need to do some sophisticated XML querying to unpack repeating nodes/segments - would I need to use XMLQUERY to do this?

Yes you can.

As a general rules, the difference is XMLQuery returns query results as XML, XMLTable returns results as relation data.

In your situation with unpacking node sequences into relational data, I'd recommend XMLTable.

1 - 7
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Aug 6 2012
Added on Jul 5 2012
5 comments
21,840 views