Tuesday, December 30, 2008

TimeSeries chart, JFreeChart

TimeSeries Chart

Time series charts are created using data from the XYDataset, it returns x-values which are of type double, these values are converted by a class called DateAxis which converts the huge double returned from XYDataset into dates and back to the double as necessary. How the double values are converted into dates isn't important for us to know, the DateAxis just does it for us which we can use to create the chart. If you are really interested to find out, then type "number of milliseconds since midnight, 1 January 1970, XYDataset, JFreeChart" in to the Holy Grail of Searches(google ;)), you might find some convincing results.

Charts usually have a domain, a time series as the name of the interface says itself(TimeSerieschart), has a domain which will have a series of times, as a matter of fact it can have values ranging from days to months.

TimeSeries can let you make objects of different times and it lets you add up all of the them and gives a series of times back as one object.
A dataset, TimeSeriesCollection, can be created out of the TimeSeries object by adding the TimeSeries object to the TimeSeriesCollection object. Since, TimeSeriesCollection implements XYDataset, the TimeSeriesCollection becomes a dataset which can be used to create the chart.

A chart is created with default settings but it is highly customizable, for example, a renderer object can be obtained from the plot which can in-turn be obtained from the chart. Now this renderer can be changed to display series shapes at each datas point, in addition to the lines between data points.And secondly, a date format override can be set for the domain axis.

To modify the renderer, first a reference to the renderer is needed and secondly cast of the rendrer into a XYLineAndShapeRenderer is required.

eg. to set the default shape visible and to set default shape filled the following magic will suffice:

XYItemRenderer r = plot.getRenderer();
if(r instanceof XYLineAndShapeRenderer)
{
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
renderer.setDeafultShapeVisible(true);
renderer.setDefaultShapeFilled(true);
}

In other words, the above snippet of code puts a legend, which is picked for the series, at each data point.

To override the date format of the domain axis the follwing will do:

DateAxis axis =(DateAxis)plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("MM=yyyy"));

Once the above date format override is set,the axis will auto-select a DateTickUnit and will ignore the formatting from the tick unit and use the override format instead.

I am including the following program which also features in the JFreeCharts Developer's Guide. If some of you have gone through the guide, and if you happened to try out
the following code, you will notice that I have excluded the "public static JPanel createDemoPanel()" method from this code snippet because for this example the method doesn't
serve any purpose.

Other points not explained in the guide are:

In the code section where the renderer is being customized this line "renderer.setDefaultShapesFilled(true);" is there but it is not required because the shape that we have picked for
displaying a mark is not hollow type.

The exact code I worked on can't be displayed here because of my company policy but I would refine on somethings which I did different from what appears below:
As you can see below, the timeseries objects are being hand carved one after another, in my case, this done in a for loop based on matching criterion of some of the
persistent properties of POJOs. In other words, I filled up my time series objects with dates from database based on the business requirement. You could do the same, no matter whether you use
Hibernate with Spring and POJOs or some other framework, JFreeChart's TimeSeries isn't meant for one set of implementation and environment.



package name.your.own;

import java.awt.Color;
import java.text.SimpleDateFormat;
import javax.swing.JPanel;
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.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.RefineryUtilities;

public class TimeSeriesDemo extends ApplicationFrame
{

public TimeSeriesDemo(String title)
{
super(title);
XYDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
chartPanel.setMouseZoomable(true, false);
setContentPane(chartPanel);
}

private static JFreeChart createChart(XYDataset dataset)
{
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Legal & General Unit Trust Prices", // title
"Date", // x-axis label
"Price Per Unit", // y-axis label
dataset, // data
true, // create legend?
true, // generate tooltips?
false // generate URLs?
);
chart.setBackgroundPaint(Color.white);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
XYItemRenderer r = plot.getRenderer();

if (r instanceof XYLineAndShapeRenderer)
{
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
renderer.setDefaultShapesVisible(true);
renderer.setDefaultShapesFilled(true);
}

DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("MMM-yyyy"));
return chart;
}

private static XYDataset createDataset()
{
TimeSeries s1 = new TimeSeries("L&G European Index Trust", Month.class);
s1.add(new Month(2, 2001), 181.8);s1.add(new Month(3, 2001), 167.3);s1.add(new Month(4, 2001), 153.8);s1.add(new Month(5, 2001), 167.6);
s1.add(new Month(6, 2001), 158.8);s1.add(new Month(7, 2001), 148.3);s1.add(new Month(8, 2001), 153.9);s1.add(new Month(9, 2001), 142.7);
s1.add(new Month(10, 2001), 123.2);s1.add(new Month(11, 2001), 131.8);s1.add(new Month(12, 2001), 139.6);s1.add(new Month(1, 2002), 142.9);
s1.add(new Month(2, 2002), 138.7);s1.add(new Month(3, 2002), 137.3);s1.add(new Month(4, 2002), 143.9);s1.add(new Month(5, 2002), 139.8);
s1.add(new Month(6, 2002), 137.0);s1.add(new Month(7, 2002), 132.8);

TimeSeries s2 = new TimeSeries("L&G UK Index Trust", Month.class);
s2.add(new Month(2, 2001), 129.6);s2.add(new Month(3, 2001), 123.2);s2.add(new Month(4, 2001), 117.2);s2.add(new Month(5, 2001), 124.1);
s2.add(new Month(6, 2001), 122.6);s2.add(new Month(7, 2001), 119.2);s2.add(new Month(8, 2001), 116.5);s2.add(new Month(9, 2001), 112.7);
s2.add(new Month(10, 2001), 101.5);s2.add(new Month(11, 2001), 106.1);s2.add(new Month(12, 2001), 110.3);s2.add(new Month(1, 2002), 111.7);
s2.add(new Month(2, 2002), 111.0);s2.add(new Month(3, 2002), 109.6);s2.add(new Month(4, 2002), 113.2);s2.add(new Month(5, 2002), 111.6);
s2.add(new Month(6, 2002), 108.8);s2.add(new Month(7, 2002), 101.6);

TimeSeriesCollection dataset = new TimeSeriesCollection();

dataset.addSeries(s1);
dataset.addSeries(s2);

dataset.setDomainIsPointsInTime(true);

return dataset;
}

public static void main(String[] args)
{
TimeSeriesDemo demo = new TimeSeriesDemo("Time Series Demo 1");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
}


The above examples and composition, entirely have been about still data, JFreeChart has interfaces for developing a chart system for live and streaming data as well, for example the guide talks about a little app for streaming the system resources usage in terms of dynamic charts. But the guide doesn't support it with high jinks because it could be very slow if you are crunching sizable amount of data because each time a dataset is updated, the ChartPanel reacts by redrawing the entire chart.