Thursday, September 29, 2011

Create Stacked Bar Charts in Java using JFreeChart

In this running example, I will show you how to create a Stacked Bar chart using just Java (JFreeChart API).

A stacked bar chart is one of those bar graphs where the parts are compared to the whole.




In this particular example, we will create a Stacked bar chart of some fictional read/write times on X-axis and some Timestamps on the Y axis. I will explain the important parts of the code during this process.

If you are using Eclipse(or Rational Software Architect as shown in the screenshots), you can create a simple project in your workspace(existing or new workspace) very fast.

Go to File -> New -> Java Project


Choose the default setting and click on Finish. The project gets created in the workspace.

Go to Package Explorer (if you can't see this, go to Window -> Show View -> Package Explorer to enable it) and right click on src folder and do New -> Package. Name the package 'test'


This will create a package with the name 'test'.

Now, right click on the test package, do New -> Class and give it the name 'StackedBarChartDemo'


Now, we have to add the jar files that are needed by the program to draw the charts. You can download the JFreeChart jar files from the link http://www.jfree.org/jfreechart/download.html
After you download the zip file containing the jar files, copy the two jar files jcommon.jar and jfreechart.jar (whatever version you download at your point of time) to a particular folder like C:\supportingJars.
In Eclipse, in Package Explorer view, right click on the project StackedBarChartExample -> Build Path -> Configure Build Path. In the window that pops up, select the Libraries tab, click on Add External JARs button and select the two jar files in the above folder.



Now, we have the necessary Jar files needed. So, lets put in some code.

Similar to creating a new Java class(StackedBarChartDemo.java), create three new classes - WITHOUT the main method. Name them RowKey.java, ColumnKey.java, ChartObject.java.


Your project should like something like this:



Copy the following code to RowKey.java
package test;

public class RowKey implements Comparable<String> {
 private String operation;

 public RowKey(String oper)
 {
  this.operation = oper;
 }
 public int compareTo(String arg0) {
  return operation.compareTo(arg0);
 }
 
 @Override
 public String toString()
 {
  return this.operation;
 }
}



Copy the following code to ColumnKey.java
package test;

public class ColumnKey implements Comparable<String> {
 private String time;
 public ColumnKey(String time)
 {
  this.time = time;
 }

 public int compareTo(String o) {
  return time.compareTo(o);
 }
 
 @Override
 public String toString()
 {
  return this.time;
 }
}
Copy the following code to ChartObject.java
package test;

public class ChartObject {

 private double readTime;
 private double writeTime;
 private String timeDetails;
 
 public ChartObject(double r, double w, String t)
 {
  this.readTime = r;
  this.writeTime = w;
  this.timeDetails = t;
 }
 public double getReadTime() {
  return readTime;
 }

 public void setReadTime(double readTime) {
  this.readTime = readTime;
 }

 public double getWriteTime() {
  return writeTime;
 }

 public void setWriteTime(double writeTime) {
  this.writeTime = writeTime;
 }

 public String getTimeDetails() {
  return timeDetails;
 }

 public void setTimeDetails(String timeDetails) {
  this.timeDetails = timeDetails;
 }
}
Here is the big picture:
We create RowKey class objects that represent the bars on the graph (read and write values in this particular example). These RowKey values will show up as the legend on the X-axis. ColumnKey objects represent the values on Y axis (timestamps in this case).

Copy the following code to StackedBarChartDemo.java
package test;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

public class StackedBarChartDemo extends ApplicationFrame {

 private static final long serialVersionUID = -7166735925893038359L;

 public static void main(final String[] args) {
  // Call the Constructor with the title of the Chart
  final StackedBarChartDemo demo = new StackedBarChartDemo(
    "Read-Write times Chart");
  // The window is drawn and shown to the User using the below 3 
  //lines.
  demo.pack();
  RefineryUtilities.centerFrameOnScreen(demo);
  demo.setVisible(true);
 }

 /**
  * This constructor creates some dummy ChartObjects first with 
  * some sample read time, write time and timestamps Then it 
  * invokes createDataset method to get a CategoryDataset 
  * object. Then it invokes the createChart method that uses 
  * the above dataset. Later, the dimensions of the window drawn
  * etc. are set
  * 
  * @param title
  */
 public StackedBarChartDemo(String title) {
  super(title);

  try {
   // Create sample ChartObjects with read time, write time 
   //and TimeStamp as constructor parameters
   ChartObject chartObject1 = new ChartObject(0.123, 0.123,
     "TimeStamp1");
   ChartObject chartObject2 = new ChartObject(0.234, 0.234,
     "TimeStamp2");
   ChartObject chartObject3 = new ChartObject(0.345, 0.345,
     "TimeStamp3");
   ChartObject chartObject4 = new ChartObject(0.456, 0.456,
     "TimeStamp4");
   List<ChartObject> chartObjectList = 
    new ArrayList<ChartObject>();
   chartObjectList.add(chartObject1);
   chartObjectList.add(chartObject2);
   chartObjectList.add(chartObject3);
   chartObjectList.add(chartObject4);

   final CategoryDataset dataset = 
    createDataset(chartObjectList);
   final JFreeChart chart = createChart(dataset);
   final ChartPanel chartPanel = new ChartPanel(chart);
   chartPanel.setPreferredSize
   (new java.awt.Dimension(900, 800));
   setContentPane(chartPanel);

  } catch (Exception e) {
   e.printStackTrace();
   return;
  }
 }

 /**
  * This method takes the List of ChartObjects as a parameter 
  * and creates three objects needed by the createCategoryDataset 
  * method of the DatasetUtilities class. The createCategoryDataset 
  * method needs 3 parameters. The first and second ones are 
  * Comparable[] and the third one is a double[][]. Since we 
  * created RowKey and ColumnKey objects to implement Comparable
  * interface, we use those objects to create the first two
  * parameters. The actual read/write values are used to populate 
  * a two dimensional  double[][].
  * 
  * @param chartObjectList
  * @return
  */
 private CategoryDataset createDataset
 (List<ChartObject> chartObjectList) {
  int chartObjectListSize = chartObjectList.size();

  // This is the Comparable[] that is used as the first 
  //parameter to the createCategoryDataset method
  // These values show up as the legend on the X-axis
  RowKey[] operations = 
   new RowKey[2];
  operations[0] = new RowKey("Read");
  operations[1] = new RowKey("Write");

  // This is the Comparable[] that is used as the second
  //parameter to the createCategoryDataset method
  ColumnKey[] timeStampArray = 
   new ColumnKey[chartObjectListSize];

  // These two double[] are used to populate a two 
  //dimensional double[][] that is used as the
  // third parameter to the createCategoryDataset method
  double[] readTimes = new double[chartObjectListSize];
  double[] writeTimes = new double[chartObjectListSize];

  // In this loop, the arrays are populated
  for (int i = 0; i < chartObjectListSize; i++) {
   timeStampArray[i] = 
    new ColumnKey(chartObjectList.get(i)
     .getTimeDetails());
   readTimes[i] = chartObjectList.get(i).getReadTime();
   writeTimes[i] = chartObjectList.get(i).getWriteTime();
  }

  // Populate the two dimensional double[][] using the 
  //two double[] created above
  double[][] data = new double[][] {readTimes,writeTimes};

  // Invoke the createCategoryDataset method by passing 
  //the three required parameters
  return DatasetUtilities.createCategoryDataset
  (operations, timeStampArray, data);
 }

 private JFreeChart createChart(final CategoryDataset dataset)
 {
  // Use the dataset created in the above method and get the
  // JFreeChartObject.
  // The PlotOrienation can be set to HORIZONTAL or VERTICAL.
  // The tooltip is shown if the second last boolean is
  // set to true. This tooltip shows the deails when the 
  // cursor is hovered over a particular bar in the chart
  final JFreeChart chart = 
   ChartFactory.createStackedBarChart(
    "Read/Write times", "TimeStamp", 
    "Time in Seconds", dataset,
    PlotOrientation.HORIZONTAL, true, true, false);

  chart.setBackgroundPaint(new Color(249, 231, 236));

  // Set colors etc. here
  CategoryPlot plot = chart.getCategoryPlot();
  plot.getRenderer()
   .setSeriesPaint(0, new Color(128, 0, 0));
  plot.getRenderer()
  .setSeriesPaint(1, new Color(0, 0, 255));
  return chart;

 }

}


Exhaustive comments are added in the above class. The gist:
In the main method, you invoke the Constructor that creates some sample ChartObjects with read/write times and timestamps. Then the main method invokes the local createDataset method to get a CategoryDataset object(of the JFreeChart API). Then it invokes the local createChart method that uses the above dataset and invokes the JFreeChartAPI's createStackedBarChart method.

The createCategoryDataset method needs 3 parameters. The first and second ones are Comparable[] and the third one is a double[][]. Since we created RowKey and ColumnKey objects to implement Comparable interface, we use those objects to create the first two parameters. The actual read/write values are used to populate a two dimensional double[][].

Build the project in eclipse (Ctrl+B). Right click on the StackedBarChartDemo.java class and Run As -> Java Application. If everything goes well, you should see a stacked bar chart like this below.



Links:

http://www.jfree.org/jfreechart/download.html