Using Jfreechart to Display Recent Changes in a Time Series

Using JFreeChart to display recent changes in a time series

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

How do I update my JFreeChart TimeSeries chart every 10 seconds?

Your DynamicTimeSeriesCollection constructor specified 60 moments, each of one Second duration. As a result, each call to advanceTime() moves the time by one second. One approach is to invoke advanceTime() to match your next reading. Starting from the original example, the following change produces the effect shown for 10-second intervals:

@Override
public void actionPerformed(ActionEvent e) {
newData[0] = randomValue();
for (int i = 0; i < 10; i++) {
dataset.advanceTime();
dataset.appendData(newData);
}
}

image

See also this related discussion of setTimeBase() regarding series initialization. For example, this implementation of gaussianData() changes the data every 10 seconds during the first COUNT seconds.

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

Dynamic JFreeChart TimeSeries

Your original question asked how to capture CPU data and display it in a chart. Because the relevant method has low latency, it's possible to invoke the method in the action listener of a javax.swing.Timer as shown here and above.

Data access with unpredictable latency may block the event dispatch thread, in which case you should use a SwingWorker instead, as shown here.

To integrate the chart into your application, refactor the example's constructor into a factory method and adopt an approach such as one of these:

  • CardLayout, shown here.

  • JTabbedPane, shown here.

  • JInternalFrame, shown here and here.

JFreechart : how to customize each time series in a single dataset

I finally ended up dropping the idea to use a single dataset with multiple series, and I used instead several timeseries, as suggested in the comments to my question, taking this code as model.

    public class Charts extends JFrame implements ChartMouseListener {

private TimeSeries priceSeries = new TimeSeries("Price");
private TimeSeries navSeries = new TimeSeries("Nav");

private ChartPanel chartPanel;

private Crosshair xCrosshair;
private Crosshair yCrosshair;
private Crosshair yNavCrosshair;
private Crosshair yDivCrosshair;

public Charts (List<CompanySummaryEuHistorical> sortedCos, String title) {
super(title);
setContentPane(createContent(sortedCos));
}

private JPanel createContent(List<CompanySummaryEuHistorical> sortedCos) {

// create series
createSeries(sortedCos);
XYDataset priceData = new TimeSeriesCollection(priceSeries);

XYLineAndShapeRenderer r0 = new XYLineAndShapeRenderer();
r0.setSeriesPaint(0, new Color(0, 0, 0x00));
r0.setSeriesShapesVisible(0, false);

XYLineAndShapeRenderer r2 = new XYLineAndShapeRenderer();
r2.setSeriesPaint(0, new Color(255, 41, 194));
r2.setSeriesShapesVisible(0, false);

XYLineAndShapeRenderer r3 = new XYLineAndShapeRenderer();
r3.setSeriesPaint(0, new Color(255, 49, 40));
r3.setSeriesShapesVisible(0, false);

// create chart with price and nav data series
JFreeChart chart = ChartFactory.createTimeSeriesChart(
title, "Date", "Value", priceData, true, true, true);
plot = (XYPlot) chart.getPlot();

plot.setBackgroundPaint(new Color(192, 196, 196));
NumberAxis rangeAxis1 = (NumberAxis) plot.getRangeAxis();
rangeAxis1.setLowerMargin(0.40); // Leave room for volume bars
rangeAxis1.setNumberFormatOverride(NumberFormat.getCurrencyInstance(getCountry(sortedCos)));
plot.setRenderer(0, r0);
renderer = (XYLineAndShapeRenderer) plot.getRenderer();
renderer.setDefaultToolTipGenerator(new StandardXYToolTipGenerator(
StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,
new SimpleDateFormat("dd/MM/YYYY"), NumberFormat.getCurrencyInstance()));

// nav series in bars
plot.setDataset(1, new TimeSeriesCollection(navSeries));
Charts.NavRender myRenderer = new NavRender();
myRenderer.setShadowVisible(false);
myRenderer.setDefaultToolTipGenerator(new StandardXYToolTipGenerator(
StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT,
new SimpleDateFormat("dd/MM/YYYY"), NumberFormat.getNumberInstance()));
plot.setRenderer(1, myRenderer);

// div series on second axis
NumberAxis rangeAxis2 = new NumberAxis("Value");
rangeAxis2.setUpperBound(rangeAxis1.getUpperBound()/10); // Leave room for price line
plot.setRangeAxis(1, rangeAxis2);
plot.setDataset(2, new TimeSeriesCollection(divSeries));
plot.mapDatasetToRangeAxis(2, 1);
plot.setRenderer(2, r2);


// create crosshair
this.chartPanel = new ChartPanel(chart);
this.chartPanel.addChartMouseListener(this);
CrosshairOverlay crosshairOverlay = new CrosshairOverlay();
this.xCrosshair = new Crosshair(Double.NaN, Color.GRAY, new BasicStroke(0f));
this.xCrosshair.setLabelVisible(true);
this.xCrosshair.setLabelGenerator(crshr ->
new SimpleDateFormat("dd/MM/YYYY").format(crshr.getValue()));

this.yCrosshair = new Crosshair(Double.NaN, Color.GRAY, new BasicStroke(0f));
this.yCrosshair.setLabelVisible(true);

this.yNavCrosshair = new Crosshair(Double.NaN, Color.GRAY, new BasicStroke(0f));
this.yNavCrosshair.setLabelVisible(true);

this.yDivCrosshair = new Crosshair(Double.NaN, Color.GRAY, new BasicStroke(0f));
this.yDivCrosshair.setLabelVisible(true);

crosshairOverlay.addDomainCrosshair(xCrosshair);
crosshairOverlay.addRangeCrosshair(yCrosshair);

chartPanel.addOverlay(crosshairOverlay);
return chartPanel;
}

private JFreeChart createChart(XYDataset dataset) {
return ChartFactory.createTimeSeriesChart("Chart",
"Date", "Value", dataset);
}

private void createSeries(List<CompanySummaryEuHistorical> sortedCos) {
for (CompanySummaryEuHistorical c : sortedCos) {
Day d = new Day(c.getDate_bom().getDayOfMonth(), c.getDate_bom().getMonthValue(), c.getDate_bom().getYear());
priceSeries.add(d, c.getPrice());
navSeries.add(d, c.getNav());
divSeries.add(d, c.getDiv());}
}

@Override
public void chartMouseClicked(ChartMouseEvent event) {
// ignore
}

@Override
public void chartMouseMoved(ChartMouseEvent event) {
Rectangle2D dataArea = this.chartPanel.getScreenDataArea();
JFreeChart chart = event.getChart();
XYPlot plot = (XYPlot) chart.getPlot();
ValueAxis xAxis = plot.getDomainAxis();
double x = xAxis.java2DToValue(event.getTrigger().getX(), dataArea,
RectangleEdge.BOTTOM);
double y = DatasetUtils.findYValue(plot.getDataset(), 0, x);
double yNav = DatasetUtils.findYValue(plot.getDataset(1), 0, x);
double yDiv = DatasetUtils.findYValue(plot.getDataset(2), 0, x);

this.xCrosshair.setValue(x);
this.yCrosshair.setValue(y);
this.yNavCrosshair.setValue(yNav);
this.yDivCrosshair.setValue(yDiv);

}
}

JFreeChart advancing in time?

As suggested here, a javax.swing.Timer works well when collecting data in a continuous, synchronous manner. In contrast, collecting data in an ongoing, asynchronous manner may block the GUI thread. Switching to SwingWorker, as shown here, offers the chance to publish() only when needed. Ideally, your chosen library may offer a suitable callback, or you can simply wait for new data to arrive.

In either case, there will be time gaps in the data. The precise details of how you deal with this will depend on you use case, but I've seen some common strategies:

  • Use available features to navigate the entire dataset in a signal chart, as shown here, here and here; use null values, as suggested here, to interrupt the display if warranted; an example is illustrated here.

  • Segregate bursts of data into separate datasets and add navigation controls, as shown here and here.

How to display only last 24 hours from a last 72 hours JFreeChart TimeSeries

Instead of discarding old data, as suggested here, it looks like you want to display a contiguous subset of the data, as if looking through a window. While SlidingCategoryDataset provides this feature for a CategoryDataset, there is no built-in solution for an XYDataset. SlidingXYDataset, seen here, maybe an alternative. In the variation of SliderDemo2 illustrated below, I've added three days of data to a single series with a one day window. I've retained the slider for easy review of earlier values.

static final int COUNT = 3 * 24 * 60;
public static final int WINDOW = 24 * 60;
public static final int FIRST = 2 * 24 * 60;

this.slider.setValue(FIRST);

image

As an aside, note that some classes have moved since the patch was submitted:

import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

Can't I just use setMaximumItemAge()?

Not in isolation; addOrUpdate(), et al., calls removeAgedItems(false) before notifying listeners.

I am using Second.

Threes days of Second data, e.g., would imply setMaximumItemAge(3 * 24 * 60 * 60).

A sliding window might not be available to me since I am saving the chart as a JPEG.

You can pass the desired firstItemIndex as a parameter to the SlidingXYDataset constructor; you can update the index later via setFirstItemIndex() if needed.

JFreeChart timeseries is not refreshing

OK, I think I have spotted your problem, but I can't be completely sure without seeing how you use all this code.

Your main issue lies here:

public JPanel getChartPanel(){

EventQueue.invokeLater(new Runnable() {
public void run() {
PrepareChart chart = new PrepareChart();
System.out.println("In caht getter methoed");
chart.start();
}
});
return new ChartPanel(chart);
}

In your Runnable, you recreate a new instance of PrepareChart and you start it. This does not make any sense:

  1. Your enclosing PrepareChart instance is never started (hence you don't see it updated dynamically)
  2. The instance you create in your runnable cannot be reached by anyone/anything so that instance if lost forever in the AWT event-queue.

So instead, I would only be using the following:

public JPanel getChartPanel() {

EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
start();
}
});
return new ChartPanel(chart);
}

This is a small main method that I wrote which seemed to do the trick.

public static void main(String[] args) {
PrepareChart prepareChart = new PrepareChart();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(prepareChart.getChartPanel());
frame.pack();
frame.setVisible(true);
}

Consider renaming your class ChartPanel because it is conflicting with the names of JFreeChart which is confusing. Also, I don't see the use of it since you could perform all that directly on the ChartPanel returned by PrepareChart.

Btw, it is quite odd to put the call to start() in a getter-method.



Related Topics



Leave a reply



Submit