JFreeChart - How to show real-time on the X-Axis of a TimeSeries chart
Asked Answered
E

1

2

I want to show live data on a TimeSeries chart with real time shown on the x-axis (or at least have the speed of the time the same as real-time).

Here is a SSCCE of the problem with random numbers as the live input. The time shown on the x-axis is much faster than real-time (assuming it is shown in hh:mm:ss format):

public class DynamicTimeSeriesChart extends JPanel {

    private DynamicTimeSeriesCollection dataset;
    private JFreeChart chart = null;

    public DynamicTimeSeriesChart(final String title) {

        dataset = new DynamicTimeSeriesCollection(1, 2000, new Second());
        dataset.setTimeBase(new Second(0, 0, 0, 1, 1, 1990)); // date 1st jan 0 mins 0 secs

        dataset.addSeries(new float[1], 0, title);
        chart = ChartFactory.createTimeSeriesChart(
            title, "Time", title, dataset, true,
            true, false);
        final XYPlot plot = chart.getXYPlot();

        ValueAxis axis = plot.getDomainAxis();
        axis.setAutoRange(true);
        axis.setFixedAutoRange(200000); // proportional to scroll speed
        axis = plot.getRangeAxis();

        final ChartPanel chartPanel = new ChartPanel(chart);
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
        add(chartPanel);
    }

    public void update(float value) {
        float[] newData = new float[1];
        newData[0] = value;
        dataset.advanceTime();
        dataset.appendData(newData);
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("testing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final DynamicTimeSeriesChart chart = new DynamicTimeSeriesChart("random numbers");
        frame.add(chart);
        frame.pack();
        frame.setVisible(true);
        Timer timer = new Timer(100, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        chart.update((float) (Math.random() * 10));
                    }
                });
            }
        });
        timer.start();
    }
}
Eros answered 23/1, 2014 at 4:1 Comment(0)
F
4

While it is acceptable to sleep() on the initial thread, Swing GUI objects should be constructed and manipulated only on the event dispatch thread. Instead, use a javax.swing.Timer to pace the updates, as shown here. A delay of 100 ms will update at approximately 10 Hz.

If the drift is unacceptable, poll the host's clock from another thread at half the nominal rate and update the dataset using EventQueue.invokeLater(). Ensure that the host is synchronized to an NTP server.

Addendum: Based on your update, note that all Swing GUI objects must be constructed and manipulated only on the event dispatch thread, not just the javax.swing.Timer. The advantage of a Swing Timer is that it fires on the EDT. The variation below shows 10 seconds of 1 Hz data at approximately real time.

Addendum: You can adjust the time passed to setTimeBase() as shown here.

image

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import javax.swing.JFrame;
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.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.DynamicTimeSeriesCollection;
import org.jfree.data.time.Second;

/**
 * @see https://mcmap.net/q/429254/-jfreechart-how-to-show-real-time-on-the-x-axis-of-a-timeseries-chart
 */
public class DynamicTimeSeriesChart extends JPanel {

    private final DynamicTimeSeriesCollection dataset;
    private final JFreeChart chart;

    public DynamicTimeSeriesChart(final String title) {
        dataset = new DynamicTimeSeriesCollection(1, 1000, new Second());
        dataset.setTimeBase(new Second(0, 0, 0, 23, 1, 2014));
        dataset.addSeries(new float[1], 0, title);
        chart = ChartFactory.createTimeSeriesChart(
            title, "Time", title, dataset, true, true, false);
        final XYPlot plot = chart.getXYPlot();
        DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setFixedAutoRange(10000);
        axis.setDateFormatOverride(new SimpleDateFormat("ss.SS"));
        final ChartPanel chartPanel = new ChartPanel(chart);
        add(chartPanel);
    }

    public void update(float value) {
        float[] newData = new float[1];
        newData[0] = value;
        dataset.advanceTime();
        dataset.appendData(newData);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                final DynamicTimeSeriesChart chart
                    = new DynamicTimeSeriesChart("Alternating data");
                frame.add(chart);
                frame.pack();
                Timer timer = new Timer(1000, new ActionListener() {
                    private boolean b;

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        chart.update(b ? 1 : 0);
                        b = !b;
                    }
                });
                timer.start();
                frame.setVisible(true);
            }
        });
    }
}
Friesen answered 23/1, 2014 at 11:49 Comment(6)
I've changed the SSCCE code to use what you suggested with the Timer and EventQueue.invokeLater(). The problem still remains though. I don't how to 'poll the host's clock' or 'ensure the host is synchronized to an NTP server'. I don't know how to set the time of the chart.Eros
Comment to your addendum: Your example chart seems to show time at the speed of real-time, but when I change the rate of the Timer to anything other than 1 Hz, the chart shows time at a different speed. I want the chart to be able to show time at real speed regardless of the data update rate. Thanks for your efforts so far.Eros
Real time is 1 Hz; you may be able to coalesce events arriving at a different rate.Friesen
I want the chart to be able to visually update at a higher rate than 1Hz and show time at real speed. Do you know how that could be done?Eros
You may be able to get the desired effect using different tick units, mentioned here.Friesen
I tried changing the tick units using: axis.setTickUnit(new DateTickUnit(DateTickUnitType.SECOND, 1)); It didn't seem to change the speed of the time axis. It seems like whenever advanceTime() is called on a DynamicTimeSeriesCollection in your example, time goes forward 1 second. Maybe if I was able to change the amount of time that advanceTime() advanced time then that could be a solution to my problem.Eros

© 2022 - 2024 — McMap. All rights reserved.