home | developer | tutorial | examples | download  
 

Tutorial

This tutorial introduces to the JOpenChart library. It is intended for users with previous knowledge of Java as a programming language. If you have no idea what Java is all about, check out the Java tutorials provided by Sun. Please mail me a sebastian.mueller@berlin.de if you find any bugs in this tutorial. Please note though, that this tutorial here is already aiming at the upcoming release 0.94. You have to get the latest CVS version for now, as I didn't find the time to create a new release, yet. The CVS release is perfectly stable though.

1. Our First Chart
2. Tweaking the Chart
3. Another Kind of Chart
4. And Yet Another
5. Diversifying Our Data
6. Interpolating Data
7. Plot me, please
8. Oh I'm Manipulatable and it swings
9. Some Further Advice

1. Our First Chart

   1:    import de.progra.charting.*;
   2:    import de.progra.charting.model.*;
   3:    import de.progra.charting.render.*;
   4:    import java.awt.*;
   5:    import java.io.*;
   6:    
   7:    [...]
   8:    
   9:    public static void makeFirstChart() {
  10:    
  11:        int[][] model = {{0, 100, 200000}};     // Create data array
  12:        
  13:        double[] columns = {0.0, 1.0, 2000.0};  // Create x-axis values
  14:        
  15:        String[] rows = {"DataSet 1"};          // Create data set title
  16:        
  17:        String title = "A First Test";          // Create diagram title
  18:        
  19:        int width = 640;                        // Image size
  20:        int height = 480;
  21:        
  22:        // Create data model
  23:        DefaultChartDataModel data = new DefaultChartDataModel(model, columns, rows);
  24:        
  25:        // Create chart with default coordinate system
  26:        DefaultChart c = new DefaultChart(data, title, DefaultChart.LINEAR_X_LINEAR_Y);
  27:        
  28:        // Add a line chart renderer
  29:        c.addChartRenderer(new LineChartRenderer(c.getCoordSystem(), data,
  30:                           RowColorModel.getInstance(data)), 1);
  31:        
  32:        // Set the chart size
  33:        c.setBounds(new Rectangle(0, 0, width, height));
  34:        
  35:        // Export the chart as a PNG image
  36:        try {
  37:            ChartEncoder.createEncodedImage(new FileOutputStream(System.getProperty("user.home")+"/first.png"), c, "png");
  38:        } catch(Exception e) {
  39:            e.printStackTrace();
  40:        }
  41:    }

This is our first simple example, which creates a line chart displaying some example values. In the beginning, we import some packages, which we will need. In the lines 10 - 22 we create our example data. The columns array is the array of x-axis values while model contains the y-axis values. The rows array contains a name for any data set we create. In this simple example, we have exactly on data set, ie. one array of y-axis values. The first interesting stuff happens in line 23. There, we create a DefaultChartDataModel which contains all our model data, ie. the x- and y-axis values and the data set titles.

In line 26 we create the DefaultChart object, which, as the name implies, encapsulates our first chart. As parameters, we pass the DefaultChartDataModel data, the chart title and a constant DefaultChart.LINEAR_X_LINEAR_Y which tells the DefaultChart object to create a standard coordinate system, with linear x- and y-axes.

Line 29 gets a bit more complicated. This time we add a ChartRenderer to the chart. Renderers are responsible for rendering the provided data in a specific way. We add a LineChartRenderer here, which will render, you guessed it, a line chart. The LineChartRenderer and almost all Renderer classes need three parameters. At first, we pass an instance of the coordinate system, which the DefaultChart object created for us, secondly, we pass the DefaultChartDataModel so the Renderer know what to draw on the screen. Last but not least, we create an instance of a RowColorModel and pass it to the LineChartRenderer. The RowColorModel maps data sets to colors. When adding a Renderer to a chart, the chart method expects a second parameter besides the Renderer itself, this number (1 in our example) is something like a z-coordinate, which is important if you add multiple Renderers. That's something we'll explore later. For our first example, we're almost done.

In line 33 we set the chart bounds. By doing that, we initiate the layout of the chart components. You cannot render a chart, before you have told him his size. Finally, in the try { ... }Êcatch(...) {...} statement, we export the chart as a PNG image to your home directory. The static methods in class ChartEncoder provide the means to export your chart in all available image formats. The first parameter is a FileOutputStream which the Encoder will write to, the second parameter is the chart object itself and the last one tells the Encoder which format to use.

That's it, the first chart is done. Try it out by copying the code.

2. Tweaking the Chart

   1:    public static void makeSecondChart() {
   2:    
   3:        int[][] model = {{0, 100, 200000},      // Create data array
   4:                         {0, 200, 100000}};
   5:        
   6:        double[] columns = {0.0, 1.0, 2000.0};  // Create x-axis values
   7:        
   8:        String[] rows = {"DataSet 1", "DataSet 2"}; // Create data set title
   9:        
  10:        String title = "A First Test";          // Create diagram title
  11:        
  12:        int width = 640;                        // Image size
  13:        int height = 480;
  14:        
  15:        // Create data model
  16:        DefaultChartDataModel data = new DefaultChartDataModel(model, columns, rows);
  17:        
  18:        data.setAutoScale(true);
  19:        
  20:        // Create chart with default coordinate system
  21:        DefaultChart c = new DefaultChart(data, title, DefaultChart.LINEAR_X_LINEAR_Y, "x-axis", "y-axis");
  22:        
  23:        // Add a line chart renderer
  24:        c.addChartRenderer(new LineChartRenderer(c.getCoordSystem(), data,
  25:                           RowColorModel.getInstance(data)), 1);
  26:        c.addChartRenderer(new PlotChartRenderer(c.getCoordSystem(), data,
  27:                           RowColorModel.getInstance(data)), 2);
  28:        
  29:        // Set the chart size
  30:        c.setBounds(new Rectangle(0, 0, width, height));
  31:        
  32:        // Export the chart as a PNG image
  33:        try {
  34:            ChartEncoder.createEncodedImage(new FileOutputStream(System.getProperty("user.home")+"/second.png"), c, "png");
  35:        } catch(Exception e) {
  36:            e.printStackTrace();
  37:        }
  38:    }

There are four new aspects in this example. Look at lines 3, 18, 21 and 26 and you'll know what I mean. In line 3 I create a two-dimensional data model using and int[]. This way we create two DataSets. We pass one columns array, that is, one array of x-axis values, but in the model array we put two arrays of y-axis values. Please note, that the columns array and the array of y-axis values should have the same number of entries. These two data sets will be automatically rendered using different colors and the chart will of course be layouted to fit both charts.

In line 18 is a simple method call data.setAutoScale(true). That means that the maximum and minimum axis values are automatically scaled to fit a nice value, mostly multiples (or fractions) of 5 and 10. If your maximum x-value happens to be 0.345 it might be rounded up to 0.5 for instance.

The change in line 21 is just as simple. I present an additional constructor for chart objects. You can pass two Strings which will be the axis labels. They are rendered aside of or below the axis arrows.

In lines 26 and following, I show a special future - the possibility to combine different ChartRenderer objects. That doesn't work with all kind of Renderers, e.g. you cannot combine a PieChartRenderer with any other type but you can combine line, plot and interpolation chart renderers. Just add all of them to the chart and give them different z-coordinates. That's all.

3. Another Kind of Chart

   1:    public static void makeThirdChart() {
   2:        
   3:        int[][] model = {{23, 43, 12}};
   4:        
   5:        // Strings as x-axis values
   6:        String[] columns = {"Conservatives", "Socialdemocrats", "Green Party"};
   7:        
   8:        String[] rows = {"Voting Results"};
   9:        
  10:        String title = "Parliamentary Elections 1997";
  11:        
  12:        int width = 640;
  13:        int height = 480;
  14:        
  15:        // Create the data model using Strings as x-axis values
  16:        ObjectChartDataModel data = new ObjectChartDataModel(model, columns, rows);
  17:        
  18:        // Create the chart
  19:        DefaultChart c = new DefaultChart(data, title);
  20:        
  21:        // Add pie chart renderer
  22:        c.addChartRenderer(new PieChartRenderer(data,
  23:                            RowColorModel.getInstance(data)), 1);
  24:
  25:        // Set chart bounds                         
  26:        c.setBounds(new Rectangle(0, 0, width, height));
  27:        
  28:        try {
  29:            ChartEncoder.createPNG(new FileOutputStream(System.getProperty("user.home")+"/third.png"), c);
  30:        } catch(Exception e) {
  31:            e.printStackTrace();
  32:        }
  33:    }

In this part of the tutorial I want to introduce you to the use of a different ChartRenderer, namely the PieChartRenderer. You can create and add this Renderer like any other one, but look at the line 16, where the data model is created. It is not a DefaultChartDataModel anymore but an ObjectChartDataModel. What's the difference anyway? Well, with this kind of data model you can use arbitrary objects as x-axis values. So far, they are always rendered using their toString(). You could for instance use Date objects, Strings etc. You can create all kinds of charts using this data model. The constructor parameters are basically the same as you'll see..

4. And Yet Another

   1:    public static void makeFourthTest() {
   2:        
   3:        int[][] model = {{23, 43, 12},
   4:                         {54, -15, 34},
   5:                         {99, 32, 45}};
   6:        
   7:        String[] columns = {"1997", "1998", "1999"};
   8:        
   9:        String[] rows = {"foobar.com", "@foo Ltd.", "bar.online"};
  10:        
  11:        String title = "Average Growth 1997 - 1999";
  12:        
  13:        int width = 640;
  14:        int height = 480;
  15:        
  16:        // Create the data model
  17:        ObjectChartDataModel data = new ObjectChartDataModel(model, columns, rows);
  18:        
  19:        // Create the chart with coordinate system and the given axis labels
  20:        DefaultChart c = new DefaultChart(data, title, DefaultChart.LINEAR_X_LINEAR_Y, "Year", "Growth");
  21:        
  22:        // Set the coordinate system, the format for the x-axis values and the coord. system layout
  23:        c.setCoordSystem(new CoordSystem(data, new DecimalFormat(), false, true, false));
  24:        
  25:        // Add a bar chart renderer
  26:        c.addChartRenderer(new BarChartRenderer(c.getCoordSystem(),
  27:                            data,
  28:                            RowColorModel.getInstance(data), new DecimalFormat(), new Font("sans", Font.ITALIC, 9), 1.0f), 1);
  29:        
  30:        c.setBounds(new Rectangle(0, 0, width, height));
  31:        
  32:        try {
  33:            ChartEncoder.createPNG(new FileOutputStream(System.getProperty("user.home")+"/fourth.png"), c);
  34:        } catch(Exception e) {
  35:            e.printStackTrace();
  36:        }
  37:    }

It gets more complicated this time. Again, I create an ObjectChartDataModel but things start to differ in line 23, where we manually set the chart's coordinate system. We pass a DecimalFormat and three boolean values to the constructor. The DecimalFormat object will change the way the numbers along the y-axis are rendered. You could for instance create a DecimalFormat displaying a currency sign next to the numbers on the axis or whatever else you like. The meaning of the boolean values is a bit more complicated. The first one defines, if the arrows on the axes should be rendered, if the second one is false, the labels will be painted on each tick and if the last one is false, not only the ticks will be painted, but a light grey horizontal line.

Afterwards we add a BarChartRenderer which has the same kind of constructor like the previous renderers. This time, we use a special constructor, only the BarChartRenderer has. It takes three additional parameters. The DecimalFormat is used to draw the numerical value on top of the corresponding bar, the Font object following determines the font and the last float number manipulates the standard bar width. A value of 1.0f is equivalent to the standard size, ie. the maximum size without overlapping, 0f means no bars at all (pretty useless) and any value above 1f will mean, that the bars overlap.

5. Diversifying Our Data

   1:    public static void makeFifthChart() {
   2:        
   3:        // Initializing some arrayss
   4:        int[] quadr = {0, 1, 4, 9, 16, 25, 36};
   5:        int[] exp = {1, 2, 4, 8, 16, 32, 64};        
   6:        double[] columns = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
   7:        
   8:        // Creating a data set array
   9:        DefaultDataSet[] ds = new DefaultDataSet[3];
  10:        
  11:        // Filling all DataSets
  12:        ds[0] = new DefaultDataSet(ChartUtilities.transformArray(new int[] {0, 6}),
  13:                                   ChartUtilities.transformArray(new double[] {0.0, 6.0}),
  14:                                   CoordSystem.FIRST_YAXIS,
  15:                                   "Linear Growth");
  16:                                   
  17:        ds[1] = new DefaultDataSet(ChartUtilities.transformArray(quadr),
  18:                                   ChartUtilities.transformArray(columns),
  19:                                   CoordSystem.FIRST_YAXIS,
  20:                                   "Quadratic Growth");
  21:        
  22:        ds[2] = new DefaultDataSet(ChartUtilities.transformArray(exp),
  23:                                   ChartUtilities.transformArray(columns),
  24:                                   CoordSystem.FIRST_YAXIS,
  25:                                   "Exponential Growth");
  26:        
  27:        String title = "Growth Factor Comparison";
  28:        
  29:        int width = 640;
  30:        int height = 480;
  31:        
  32:        DefaultChartDataModel data = new DefaultChartDataModel(ds);
  33:        
  34:        data.setAutoScale(true);
  35:        
  36:        DefaultChart c = new DefaultChart(data, title, DefaultChart.LINEAR_X_LINEAR_Y);
  37:        
  38:        c.addChartRenderer(new LineChartRenderer(c.getCoordSystem(),
  39:                           data,
  40:                           RowColorModel.getInstance(data)), 1);
  41:        
  42:        c.setBounds(new Rectangle(0, 0, width, height));
  43:        
  44:        try {
  45:            ChartEncoder.createPNG(new FileOutputStream(System.getProperty("user.home")+"/fifth.png"), c);
  46:        } catch(Exception e) {
  47:            e.printStackTrace();
  48:        }
  49:    }

With the last large example I show the possibility to create a data model with differently sized data sets. For doing that, you have to create the DataSets manually the way shown above. The DataSets need Number arrays but the utility method transformArray transforms arrays of primitive data types into object arrays. Additionally, you have to pass a constant describing the y-axis to which the data set should be attached. Up to now, only the first y-axis is fully implemented so you don't have much of a choice. The string parameter is the data set title which will be displayed in the chart's legend. With the release following version 0.93 you can also create ObjectChartDataModels this way.

6. Interpolating Data

   1:c.addChartRenderer(new InterpolationChartRenderer(c.getCoordSystem(),
   2:                   data,
   3:                   RowColorModel.getInstance(data)), 1);

The JOpenChart library implements a polynomial interpolation algorithm (Aitken-Neville) which can interpolate any values in the data model to a continuous line. You can do that simply by adding the InterpolationChartRenderer as shown above.

7. Plot me, please

   1:DefaultChartDataModel data = FunctionPlotter.createChartDataModelInstance(-10, 10, 2000, "1/x");

Another nice feature of the library is its ability to plot any mathematical function by giving it as a String object. You can create a data model by giving the minimum and maximum x-axis value, the number of computed points (2000 is a good value) and the function itself.

8. Oh I'm Manipulatable and it swings

   1:import java.awt.*;
   2:import java.awt.event.*;
   3:import javax.swing.*;
   4:import de.progra.charting.swing.*;
   5:import de.progra.charting.event.*;
   6:import de.progra.charting.model.*;
   7:import de.progra.charting.render.*;
   8:import de.progra.charting.*;
   9:
  10:public class GraphFrame extends javax.swing.JFrame implements ActionListener, ChartDataModelListener {
  11:
  12:    ChartPanel panel;
  13:    EditableChartDataModel data;
  14:    
  15:    // Initialize the Timer:
  16:    javax.swing.Timer t = new javax.swing.Timer(1000, this);
  17:    double time = 3.0;
  18:    
  19:    public GraphFrame() {
  20:        // Init some starting data
  21:        double[][] model = {{25.0, 22.0, 23.0},
  22:                            {13.0, 11.0, 12.0}};
  23:        
  24:        double[] columns = {0.0, 1.0, 2.0};
  25:        String[] columnString = {"1998", "1999", "2000"};
  26:        String[] rows = {"Int. Temp.", "Ext. Temp."};
  27:
  28:        String title = "Viewing Internal & External Temperature";
  29:
  30:        // Create an editable chart data model
  31:        data = new EditableChartDataModel(model, columns, rows);
  32:        
  33:        // Creating the Swing ChartPanel instead of DefaultChart
  34:        panel = new ChartPanel(data, title, DefaultChart.LINEAR_X_LINEAR_Y);
  35:        // Adding ChartRenderer as usual
  36:        panel.addChartRenderer(new LineChartRenderer(panel.getCoordSystem(), data, RowColorModel.getInstance(data)), 1);
  37:        // Register EventListener
  38:        data.addChartDataModelListener(this);
  39:        
  40:        // Start the Timer
  41:        t.start();
  42:        setSize(640, 480);
  43:        this.getContentPane().add(panel, BorderLayout.CENTER);
  44:    }
  45:
  46:    public void actionPerformed(ActionEvent evt) {
  47:        // The Timer generated an event -> update DataModel with random data
  48:        data.insertValue(0, new Double(Math.random() * 10.0 + 20.0), new Double(time));
  49:        data.insertValue(1, new Double(Math.random() * 7.0 + 10.0), new Double(time));
  50:        
  51:        time++;
  52:    }
  53:    
  54:    public void chartDataChanged(ChartDataModelEvent evt) {
  55:        // The DataModel changed -> update display
  56:        panel.revalidate();
  57:        repaint();
  58:    }
  59:
  60:    public static void main(String args[]) {
  61:        // Start the whole shebang 
  62:        new GraphFrame().show();
  63:    }
  64:}
  65:
  66:

This very last example which is contained in the source release in package de.progra.charting.test shows how to use the EditableChartDataModel to plot realtime values. It creates a ChartPanel, which can be used equivalently to a Chart object but is a Swing component. Afterwards the EditableChartDataModel is updated once per second with random data and the ChartPanel is immediately updated through its event mechanism. By the way, the chart component is double-buffered.

9. Some Further Advice

Well, there's not much to say anymore. Maybe I should recommend to download the API docs from the download section and explore the possibilities yourself. This tutorial can only give some early guidance but cannot show all aspects of the library.

SourceForge Logo

Sebastian Müller
sebastian.mueller@berlin.de