Live Updates in PrimeFaces Line Chart

In the Facelets file:

<p:layoutUnit position="center">
    <h:form>
        <p:poll 
            interval="3" 
            update=":chartPanel" 
            autoStart="true" /> 
    </h:form>
    <p:panelGrid 
        columns="1" 
        id="chartPanel">
        <p:lineChart 
            xaxisLabel="Time"
            yaxisLabel="Position"
            value="#{chartController.linearModel}"
            legendPosition="nw" 
            animate="true"
            style="height:400px;width: 1000px;"/>
    </p:panelGrid>
</p:layoutUnit>

The controler:

import java.io.Serializable;
import javax.inject.Named;
import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.ChartSeries;

@Named
public class ChartController implements Serializable {

    private CartesianChartModel model;

    public ChartController() {
        createLinearModel();
    }

    private void createLinearModel() {
        model = new CartesianChartModel();
        model.addSeries(getStockChartData("Stock Chart"));
    }

    private ChartSeries getStockChartData(String label) {
        ChartSeries data = new ChartSeries();
        data.setLabel(label);
        for (int i = 1; i <= 20; i++) {
            data.getData().put(i, (int) (Math.random() * 1000));
        }
        return data;
    }

    public CartesianChartModel getLinearModel() {
        return model;
    }

}

Based on this sample.

What I need is, instead of the for loop, to have an incrementing index to be shown, i.e., each second should be displayed on the x axis, which should increment dynamically per second. Can anyone help?

Comments:

If I have understood your requirement, You should get time of system and store Current Second into "currentSecond1" variable. then inside getStockChartData() get time of system again and store it into "currentSecond2" and then change your for loop from currentSecond1 to currentSecond2.

Note that You should put your class to @ViewScoped and change @Named to @ManagedBean.

In this case for the first request currentSecond1 variable will store current second and for subsequent requests it will not change and instead currentSecond2 will change.
You can change interval="3" to interval="1" to refresh chart each second.

Posted by Mehdi Heidarzadeh on November 12, 2012 at 02:54 AM PST #

The @Named annotation is the correct one, no need to change this. This isn't relevant to the question anyway.

Do you want to generate the chart data asynchronously and have the chart model present a snapshot of that data per request?

If this is the case then you can use a @Singleton EJB that uses an @Schedule( second = "*/1", minute = "*", hour = "*", persistent = false) to generate data.

package de.grimme.pf.pf341test.realtimechart;

import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import static javax.ejb.ConcurrencyManagementType.CONTAINER;
import static javax.ejb.LockType.READ;
import static javax.ejb.LockType.WRITE;
import javax.ejb.Lock;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import org.primefaces.model.chart.ChartSeries;

/**
*
* @author a.bailey
*/
@ConcurrencyManagement(CONTAINER)
@Startup
@Singleton
public class ChartDataGenerator {

int currentTime = 0;
int[] data;

@PostConstruct
public void init() {
data = new int[100];
for (currentTime = 0; currentTime < data.length; currentTime++) {
data[currentTime] = (int) (Math.random() * 1000);
}
}

@Lock(WRITE)
@Schedule( second = "*/1", minute = "*", hour = "*", persistent = false)
public void myTimer() {
System.err.println(currentTime);
currentTime++;
System.arraycopy(data, 1, data, 0, data.length - 1);
data[data.length - 1] = (int) (Math.random() * 1000);
}

@Lock(READ)
public void getChartData(ChartSeries chartData) {
for (int i = 0; i < data.length; i++) {
chartData.getData().put((currentTime - (data.length - 1) + i), data[i]);
}
}
}

You can inject the EJB into your chart controller and setup the chart data using it.

I apologise for the formatting of my code, no idea how you do that on here

Posted by Andy Bailey on November 12, 2012 at 04:52 AM PST #

In addition you might want to change the chart attribute animated="true" to animated="false" because this can cause the chart draw time to exceed the poll interval.

Posted by Andy Bailey on November 12, 2012 at 07:00 AM PST #

Hi,

View:

<h:form>
<p:poll
interval="1"
update="data"
listener="#{chartController.update()}"
autoStart="true" />
<p:lineChart
id="data"
xaxisLabel="Time"
yaxisLabel="Position"
value="#{chartController.linearModel}"
legendPosition="nw"
animate="false"
style="height:400px;width: 1000px;"/>
</h:form>

Controller:

@Named
@SessionScoped
public class ChartController implements Serializable {

private CartesianChartModel model;
private ChartSeries data;

public ChartController() {
createLinearModel();
}

private void createLinearModel() {
model = new CartesianChartModel();
model.addSeries(getStockChartData("Stock Chart"));
}

private ChartSeries getStockChartData(String label) {
if (data == null) {
data = new ChartSeries();
for (int i = 1; i <= 20; i++) {
data.getData().put(i, (int) (Math.random() * 1000));
}
}
data.setLabel(label);
return data;
}

public CartesianChartModel getLinearModel() {
return model;
}

public void update() {
data.getData().put(data.getData().size() + 1, (int) (Math.random() * 1000));
}
}

Hope this helps...

Posted by guest on November 12, 2012 at 07:46 AM PST #

New version:

View:

<h:form>
<p:poll
interval="1"
update="data"
listener="#{chartController.update()}"
autoStart="true" />
<p:lineChart
id="data"
xaxisLabel="Time"
yaxisLabel="Position"
value="#{chartController.linearModel}"
legendPosition="nw"
animate="false"
style="height:400px;width: 1000px;"/>
</h:form>

Controller:

@Named
@SessionScoped
public class ChartController implements Serializable {

private CartesianChartModel model;
private ChartSeries data;
private int counter = 0;

public ChartController() {
createLinearModel();
}

private void createLinearModel() {
model = new CartesianChartModel();
model.addSeries(getStockChartData("Stock Chart"));
}

private ChartSeries getStockChartData(String label) {
if (data == null) {
data = new ChartSeries();
for (int i = 1; i <= 20; i++) {
data.getData().put(counter++, (int) (Math.random() * 1000));
}
}
data.setLabel(label);
return data;
}

public CartesianChartModel getLinearModel() {
return model;
}

public void update() {
if (data.getData().size() > 10) {
data.getData().remove(data.getData().keySet().toArray()[0]);
}
data.getData().put(counter++, (int) (Math.random() * 1000));
}
}

Posted by Tex on November 12, 2012 at 08:07 AM PST #

Thanks a lot for all the help everyone! This is great and I'm learning a lot. What I'm ultimately trying to achieve is that an external change (i.e., via a TinkerForge device) should cause the chart to be updated. I.e., in the first second, I turn the device to the right, so the chart should go up to the rotation position provided by the device, in the second second, I turn the device to the left, so the chart should go down to the rotation position provided by the device, etc.

Posted by Geertjan on November 13, 2012 at 12:34 AM PST #

This gets more interesting and sounds more like a PUSH scenario which fortunately PrimeFaces 3.4.1 supports.

This assume that the changes are in real time and not a recording.
In the case of a recording being played back then the solutions involving polling are adequate.

With real time data capture and playback then PUSH is the only practical solution if you want to stay completely within the browser.

Posted by Andy Bailey on November 13, 2012 at 01:01 AM PST #

Yes, changes are in real time. As I turn a device, I should see the chart change to reflect the current position of the turned device. Any details on how to achieve this?

Posted by Geertjan on November 13, 2012 at 08:16 AM PST #

An app like this is likely to require extensions to already existing components and/or a slight rethink about the display. PrimeFaces PUSH, which uses Atmosphere, would be a good place to start: http://www.primefaces.org/showcase/push/index.jsf

Is chart a suitable display for this? Perhaps something based on the Raphael SVG Library would be more appropriate and could be wired up to accept updates directly through a push channel rather than bully the server with polling.

This sounds even more interesting! Certainly not a very complex undertaking as the complexity is already encapsulated in the libraries. The one thing to check into is pushing messages asynchronously, something that should be supported but not something I can at this moment confirm. I do remember the DEV Lead from PrimeFaces saying that this would be supported in the future.
I would also be more than happy to help out with in more concrete ways in the future.

Posted by Andy Bailey on November 14, 2012 at 02:43 AM PST #

Quick update: PrimeFaces PUSH supports asynchronous pushing and this is very simple to implement.

Posted by Andy Bailey on November 14, 2012 at 02:48 AM PST #

This has prickled my curiosity enough to create a quick POC project and add SVG animation support should the Proof of Concept work out.
Everything worked well enough that I blogged the results together with a GitHub Project that can be dowloaded: http://onthefaceofthings.blogspot.de/2012/11/primefaces-push.html

Geertjan, I hope this helps.

Posted by Andy Bailey on November 14, 2012 at 06:58 AM PST #

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
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today