37

How can I use JFreeChart to display just the most recent data in a continually updated time series?

Addenda: A complete, working example that incorporates the accepted answer is shown here. See also this variation having two series. See also this Q&A regarding setTimeBase().

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045

3 Answers3

48

The JFreeChart class DynamicTimeSeriesCollection is a good choice.

Addendum: As noted by @Bahadır, the last point of the series was persistently zero. @Don helpfully suggests advancing the time and then appending the data.

dataset.advanceTime();
dataset.appendData(newData);

enter image description here

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.Timer;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.DynamicTimeSeriesCollection;
import org.jfree.data.time.Second;
import org.jfree.data.xy.XYDataset;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.chart.ui.UIUtils;

/**
 * @see http://stackoverflow.com/a/15521956/230513
 * @see http://stackoverflow.com/questions/5048852
 */
public class DTSCTest extends ApplicationFrame {

    private static final String TITLE = "Dynamic Series";
    private static final String START = "Start";
    private static final String STOP = "Stop";
    private static final float MINMAX = 100;
    private static final int COUNT = 2 * 60;
    private static final int FAST = 100;
    private static final int SLOW = FAST * 5;
    private static final Random random = new Random();
    private Timer timer;

    public DTSCTest(final String title) {
        super(title);
        final DynamicTimeSeriesCollection dataset =
            new DynamicTimeSeriesCollection(1, COUNT, new Second());
        dataset.setTimeBase(new Second(0, 0, 0, 1, 1, 2011));
        dataset.addSeries(gaussianData(), 0, "Gaussian data");
        JFreeChart chart = createChart(dataset);

        final JButton run = new JButton(STOP);
        run.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String cmd = e.getActionCommand();
                if (STOP.equals(cmd)) {
                    timer.stop();
                    run.setText(START);
                } else {
                    timer.start();
                    run.setText(STOP);
                }
            }
        });

        final JComboBox combo = new JComboBox();
        combo.addItem("Fast");
        combo.addItem("Slow");
        combo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if ("Fast".equals(combo.getSelectedItem())) {
                    timer.setDelay(FAST);
                } else {
                    timer.setDelay(SLOW);
                }
            }
        });

        this.add(new ChartPanel(chart) {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(640, 480);
            }
        }, BorderLayout.CENTER);
        JPanel btnPanel = new JPanel(new FlowLayout());
        btnPanel.add(run);
        btnPanel.add(combo);
        this.add(btnPanel, BorderLayout.SOUTH);

        timer = new Timer(FAST, new ActionListener() {
            float[] newData = new float[1];

            @Override
            public void actionPerformed(ActionEvent e) {
                newData[0] = randomValue();
                dataset.advanceTime();
                dataset.appendData(newData);
            }
        });
    }

    private float randomValue() {
        return (float) (random.nextGaussian() * MINMAX / 3);
    }

    private float[] gaussianData() {
        float[] a = new float[COUNT];
        for (int i = 0; i < a.length; i++) {
            a[i] = randomValue();
        }
        return a;
    }

    private JFreeChart createChart(final XYDataset dataset) {
        final JFreeChart result = ChartFactory.createTimeSeriesChart(
            TITLE, "hh:mm:ss", "milliVolts", dataset, true, true, false);
        final XYPlot plot = result.getXYPlot();
        ValueAxis domain = plot.getDomainAxis();
        domain.setAutoRange(true);
        ValueAxis range = plot.getRangeAxis();
        range.setRange(-MINMAX, MINMAX);
        return result;
    }

    public void start() {
        timer.start();
    }

    public static void main(final String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                DTSCTest demo = new DTSCTest(TITLE);
                demo.pack();
                UIUtils.centerFrameOnScreen(demo);
                demo.setVisible(true);
                demo.start();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for this great sample. Do you know a way to prevent the last point of the line to be at zero? – Bahadır Yağan Apr 28 '11 at 13:29
  • @Bahadır: It appears to be an intentional effect of `advanceTime()`. I've never had a use case for a different baseline value. Any thoughts? Don't forget to up-vote answers you found useful. :-) – trashgod Apr 28 '11 at 16:30
  • "You last voted on this answer Apr 25 at 13:11" I always do. By the way, I fixed this by using TimeSeriesCollection instead of the dynamic one. Then replaced dataset.appendData with add, and setting a life time for the dataset. It gives pretty much the same functionality. – Bahadır Yağan Apr 28 '11 at 20:21
  • @Bahadır: Ah, I see; thank you for that, as well as the alternative approach. – trashgod Apr 28 '11 at 21:48
  • @Bahadir, can i request you to please share the code you have implemented? – Sundhas Jun 28 '11 at 18:46
  • one thing i want to ask you @trashgod that can we store this graph and make it visible in our webpage? if so how ? – Sundhas Jun 28 '11 at 20:11
  • @Sundhas I would use trashgod's code with Don's fix if I was doing it all over again. But I can find it tomorrow when I am at work, if you still want it. – Bahadır Yağan Jun 28 '11 at 20:16
  • 1
    @Sundhas saving a graph is a different question, please create a new question for that. – Bahadır Yağan Jun 28 '11 at 20:18
  • @Sundhas: For a snapshot, use `ChartUtilities` with servlet/JSP, as suggested [here](http://stackoverflow.com/questions/2545421/displaying-jfreechart-in-a-web-page-using-struts2); for live, I'd use Java Web Start. @Bahadır: Thanks for confirming. – trashgod Jun 28 '11 at 20:22
  • @trashgod I dont want to save it as a snapshot, i want the moving graph in my webpage. – Sundhas Jun 28 '11 at 21:27
  • @Sundhas: Interactive? I'd go with an applet via [JWS](http://stackoverflow.com/tags/java-web-start/info). – trashgod Jun 28 '11 at 23:51
  • @trashgod How to add one more line and one more y-axis on the left with DynamicTimeSeries? – Forrest Aug 28 '16 at 10:56
  • @DươngAnhKhoa: Start with the variation cited in the [question](http://stackoverflow.com/q/5048852/230513) and this [example](http://stackoverflow.com/a/13362809/230513). – trashgod Aug 28 '16 at 13:21
16

You can also eliminate the zero by first advanceTime(), then appendData. (swap the way they are doing it in the example).

frogatto
  • 28,539
  • 11
  • 83
  • 129
Don
  • 176
  • 1
  • 2
  • @Bahadır: Good question. When mine was the only answer, I accepted it to keep my rate up; but no points were awarded. After accepting, @Don got +15 for an answer that helped us all, and I got +2 for accepting. I'd call that win-win-win-win! :-) Sorry I overlooked this comment earlier when responding to @Sundhas. – trashgod Jun 29 '11 at 06:25
  • 5
    @trashgod For future readers, it's more helpful if your answer is accepted. It was confusing for a moment or two, until I realised this *was* an FAQ-style question and the complete answer is below! – Duncan Jones Apr 24 '13 at 07:12
  • @DuncanJones: Good point; updated in the question. I'm leaving this as the accepted answer, as it's cited elsewhere. – trashgod Apr 24 '13 at 11:05
8

One alternative approach to @thrashgod's answer would be to use TimeSeriesCollection and setting item age on the TimeSeries. Below code can setup a graph to show last 1 hour of data with 1 minute intervals.

private TimeSeriesCollection dataset;
private TimeSeries sensorSeries;
sensorSeries = new TimeSeries("name", Minute.class);
sensorSeries.setMaximumItemAge(60);
dataset = new TimeSeriesCollection();
dataset.addSeries(sensorSeries);

..and you will add the data as it comes with:

sensorSeries.add(new Minute(new Date()), newData);
Bahadır Yağan
  • 5,577
  • 3
  • 35
  • 39