Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures


Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips


Submit News
Search the site here...

Swing Chapter 23. (Special topics) Java2D. Easy for reading, Click here!

Custom Search
Swing Chapter 23. (Special topics) Java2D. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 2/4 

Previous Page Previous Page (1/4) - Next Page (3/4) Next Page
Subpages: 1. Java2D API overview
2. Rendering charts
3. Rendering text strings
4. Rendering images

23.2  Rendering charts

In this section we'll demonstrate the advantages of using the Java2D API for rendering charts. The following example introduces a custom component which is capable of rendering line graphs, bar charts, and pie charts using strokes, color gradients, and background images. This application demonstrates how to build such charts taking into account issues such as axis positioning and scaling based on the given coordinate data. Be prepared for a bit of math.

Figure 23.1 Charts2D displaying the three available JChart2D charts with various visual effects.

<<file figure23-1.gif>>

The Code: Charts2D.java

see \Chapter23\1

import java.awt.*;

import java.awt.event.*;

import java.awt.font.*;

import java.awt.geom.*;

import java.util.*;

import javax.swing.*;

import javax.swing.border.*;

public class Charts2D extends JFrame


  public Charts2D() {

    super("2D Charts");

    setSize(720, 280);

    getContentPane().setLayout(new GridLayout(1, 3, 10, 0));


    int nData = 8;

    int[] xData = new int[nData];

    int[] yData = new int[nData];

    for (int k=0; k<nData; k++) {

      xData[k] = k;

      yData[k] = (int)(Math.random()*100);

      if (k > 0)

        yData[k] = (yData[k-1] + yData[k])/2;


    JChart2D chart = new JChart2D(

      JChart2D.CHART_LINE, nData, xData,

      yData, "Line Chart");

    chart.setStroke(new BasicStroke(5f, BasicStroke.CAP_ROUND,


    chart.setLineColor(new Color(0, 128, 128));


    chart = new JChart2D(JChart2D.CHART_COLUMN,

      nData, xData, yData, "Column Chart");

    GradientPaint gp = new GradientPaint(0, 100,

      Color.white, 0, 300, Color.blue, true);





    chart = new JChart2D(JChart2D.CHART_PIE, nData, xData,

      yData, "Pie Chart");

    ImageIcon icon = new ImageIcon("hubble.gif");





    WindowListener wndCloser = new WindowAdapter() {

      public void windowClosing(WindowEvent e) {







  public static void main(String argv[]) {

    new Charts2D();



class JChart2D extends JPanel


  public static final int CHART_LINE = 0;

  public static final int CHART_COLUMN = 1;

  public static final int CHART_PIE = 2;

  public static final int EFFECT_PLAIN = 0;

  public static final int EFFECT_GRADIENT = 1;

  public static final int EFFECT_IMAGE = 2;

  protected int m_chartType = CHART_LINE;

  protected JLabel m_title;

  protected ChartPanel m_chart;

  protected int m_nData;

  protected int[] m_xData;

  protected int[] m_yData;

  protected int m_xMin;

  protected int m_xMax;

  protected int m_yMin;

  protected int m_yMax;

  protected double[] m_pieData;

  protected int  m_effectIndex = EFFECT_PLAIN;

  protected Stroke m_stroke;

  protected GradientPaint m_gradient;

  protected Image  m_foregroundImage;

  protected Color  m_lineColor = Color.black;

  protected Color  m_columnColor = Color.blue;

  protected int  m_columnWidth = 12;

  protected boolean m_drawShadow = false;

  public JChart2D(int type, int nData,

   int[] yData, String text) {

    this(type, nData, null, yData, text);


  public JChart2D(int type, int nData, int[] xData,

   int[] yData, String text) {

    super(new BorderLayout());


    m_title = new JLabel(text, JLabel.CENTER);

    add(m_title, BorderLayout.NORTH);

    m_chartType = type;

    if (xData==null) {

      xData = new int[nData];

      for (int k=0; k<nData; k++)

        xData[k] = k;


    if (yData == null)

      throw new IllegalArgumentException(

      "yData can't be null");

    if (nData > yData.length)

      throw new IllegalArgumentException(

      "Insufficient yData length");

    if (nData > xData.length)

      throw new IllegalArgumentException(

      "Insufficient xData length");

    m_nData = nData;

    m_xData = xData;

    m_yData = yData;

    m_xMin = m_xMax = 0;  // To include 0 into the interval

    m_yMin = m_yMax = 0;

    for (int k=0; k<m_nData; k++) {

      m_xMin = Math.min(m_xMin, m_xData[k]);

      m_xMax = Math.max(m_xMax, m_xData[k]);

      m_yMin = Math.min(m_yMin, m_yData[k]);

      m_yMax = Math.max(m_yMax, m_yData[k]);


    if (m_xMin == m_xMax)


    if (m_yMin == m_yMax)


    if (m_chartType == CHART_PIE) {

      double sum = 0;

      for (int k=0; k<m_nData; k++) {

        m_yData[k] = Math.max(m_yData[k], 0);

        sum += m_yData[k];


      m_pieData = new double[m_nData];

      for (int k=0; k<m_nData; k++)

        m_pieData[k] = m_yData[k]*360.0/sum;


    m_chart = new ChartPanel();

    add(m_chart, BorderLayout.CENTER);


  public void setEffectIndex(int effectIndex) {

    m_effectIndex = effectIndex;



  public int getEffectIndex() { return m_effectIndex; }

  public void setStroke(Stroke stroke) {

    m_stroke = stroke;



  public void setForegroundImage(Image img) {

    m_foregroundImage = img;



  public Image getForegroundImage() { return m_foregroundImage; }

  public Stroke getStroke() { return m_stroke; }

  public void setGradient(GradientPaint gradient) {

    m_gradient = gradient;



  public GradientPaint getGradient() { return m_gradient; }

  public void setColumnWidth(int columnWidth) {

    m_columnWidth = columnWidth;




  public int getColumnWidth() { return m_columnWidth; }

  public void setColumnColor(Color c) {

    m_columnColor = c;



  public Color getColumnColor() { return m_columnColor; }

  public void setLineColor(Color c) {

    m_lineColor = c;



  public Color getLineColor() { return m_lineColor; }

  public void setDrawShadow(boolean drawShadow) {

    m_drawShadow = drawShadow;



  public boolean getDrawShadow() { return m_drawShadow; }

  class ChartPanel extends JComponent


    int m_xMargin = 5;

    int m_yMargin = 5;

    int m_pieGap = 10;

    int m_x;

    int m_y;

    int m_w;

    int m_h;

    ChartPanel() {



    protected void processComponentEvent(ComponentEvent e) {



    public void calcDimensions() {

      Dimension d = getSize();

      m_x = m_xMargin;

      m_y = m_yMargin;

      m_w = d.width-2*m_xMargin;

      m_h = d.height-2*m_yMargin;

      if (m_chartType == CHART_COLUMN) {

        m_x += m_columnWidth/2;

        m_w -= m_columnWidth;



    public int xChartToScreen(int x) {

      return m_x + (x-m_xMin)*m_w/(m_xMax-m_xMin);


    public int yChartToScreen(int y) {

      return m_y + (m_yMax-y)*m_h/(m_yMax-m_yMin);


    public void paintComponent(Graphics g) {

      int x0 = 0;

      int y0 = 0;

      if (m_chartType != CHART_PIE) {


        x0 = xChartToScreen(0);

        g.drawLine(x0, m_y, x0, m_y+m_h);

        y0 = yChartToScreen(0);

        g.drawLine(m_x, y0, m_x+m_w, y0);


      Graphics2D g2 = (Graphics2D) g;





      if (m_stroke != null)


      GeneralPath path = new GeneralPath();

      switch (m_chartType) {

      case CHART_LINE:




        for (int k=1; k<m_nData; k++)





      case CHART_COLUMN:

        for (int k=0; k<m_nData; k++) {

          m_xMax ++;

          int x = xChartToScreen(m_xData[k]);

          int w = m_columnWidth;

          int y1 = yChartToScreen(m_yData[k]);

          int y = Math.min(y0, y1);

          int h = Math.abs(y1 - y0);

          Shape rc = new Rectangle2D.Double(x, y, w, h);

          path.append(rc, false);

          m_xMax --;


        if (m_drawShadow) {

          AffineTransform s0 = new AffineTransform(

            1.0, 0.0, 0.0, -1.0, x0, y0);


            1.0, 0.5));


            0.5, 0.0));

          s0.concatenate(new AffineTransform(

            1.0, 0.0, 0.0, -1.0, -x0, y0));


          Shape shadow = s0.createTransformedShape(path);



        if (m_effectIndex==EFFECT_GRADIENT &&

          m_gradient != null) {




        else if (m_effectIndex==EFFECT_IMAGE &&

          m_foregroundImage != null)

          fillByImage(g2, path, 0);

        else {







      case CHART_PIE:

        double start = 0.0;

        double finish = 0.0;

        int ww = m_w - 2*m_pieGap;

        int hh = m_h - 2*m_pieGap;

        if (m_drawShadow) {

          ww -= m_pieGap;

          hh -= m_pieGap;


        for (int k=0; k<m_nData; k++) {

          finish = start+m_pieData[k];

          double f1 = Math.min(90-start, 90-finish);

          double f2 = Math.max(90-start, 90-finish);

          Shape shp = new Arc2D.Double(m_x, m_y, ww, hh,

            f1, f2-f1, Arc2D.PIE);

          double f = (f1 + f2)/2*Math.PI/180;

          AffineTransform s1 = AffineTransform.



          s1.translate(m_pieGap, m_pieGap);

          Shape piece = s1.createTransformedShape(shp);

          path.append(piece, false);

          start = finish;


        if (m_drawShadow) {

          AffineTransform s0 = AffineTransform.

            getTranslateInstance(m_pieGap, m_pieGap);


          Shape shadow = s0.createTransformedShape(path);



        if (m_effectIndex==EFFECT_GRADIENT && m_gradient != null) {




        else if (m_effectIndex==EFFECT_IMAGE &&

         m_foregroundImage != null)

          fillByImage(g2, path, 0);

        else {









    protected void fillByImage(Graphics2D g2,

     Shape shape, int xOffset) {

      if (m_foregroundImage == null)


      int wImg = m_foregroundImage.getWidth(this);

      int hImg = m_foregroundImage.getHeight(this);

      if (wImg <=0 || hImg <= 0)



      Rectangle bounds = shape.getBounds();

      for (int xx = bounds.x+xOffset;

       xx < bounds.x+bounds.width; xx += wImg)

        for (int yy = bounds.y; yy < bounds.y+bounds.height;

         yy += hImg)

          g2.drawImage(m_foregroundImage, xx, yy, this);




Understanding the Code

Class Charts2D

This class provides the frame encompassing this example. It creates an array of equidistant x-coordinates and random y-coordinates to be drawn in the charts. Three instances of our custom JChart2D class (see below) are created and placed in the frame using a GridLayout. The methods used to provide setup and initialization for our chart are built into the JChart2D class and will be explained below.

Class JChart2D

Several constants are defined for use as the available chart type and visual effect options:

int CHART_LINE: specifies a line chart.

int CHART_COLUMN: specifies a column chart.

int CHART_PIE: specifies a pie chart.

int EFFECT_PLAIN: use no visual effects (homogeneous chart).

int EFFECT_GRADIENT: use a color gradient to fill the chart.

int EFFECT_IMAGE: use an image to fill the chart.

Several instance variables are defined to hold data used by this class:

JLabel m_title: label used to display a chart's title.

ChartPanel m_chart: custom component used to display a chart's body (see below).

int m_nData: number of points in the chart.

int[] m_xData: array of x-coordinates in the chart.

int[] m_yData: array of y-coordinates in the chart.

int m_xMin: minimum x-coordinate.

int m_xMax: maximum x-coordinate.

int m_yMin: minimum y-coordinate.

int m_yMax: maximum y-coordinate.

double[] m_pieData: angles for each piece of the pie chart.

int m_chartType: maintains the chart's type (one of the constants listed above).

int m_effectIndex: maintains the chart's effect index (one of the constants listed above).

Stroke m_stroke: stroke instance used to outline the chart.

GradientPaint m_gradient: color gradient used to fill the chart (this only takes effect when m_effectIndex is set to EFFECT_GRADIENT).

Image m_foregroundImage: image used to fill the chart (this only takes effect when m_effectIndex is set to EFFECT_IMAGE).

Color m_lineColor: color used to outline the chart.

Color m_columnColor: color used to fill the chart (this only takes effect when m_effectIndex is set to EFFECT_PLAIN -- this is its default setting).

int m_columnWidth: width of columns in the column chart.

boolean m_drawShadow: flag to draw a shadow for column or pie chart.

Two constructors are provided in the JChart2D class. The first one takes four parameters and simply calls the second, passing it the given parameters and using a null value for a fifth. This second constructor is where a JChart2D is actually created and its five parameters are:

int type: the type of this chart (CHART_LINE, CHART_COLUMN, or CHART_PIE).

int nData: number of data points in this chart.

int[] xData: an array of x-coordinates for this chart (may be null -- this is passed as null from the first constructor).

int[] yData: an array of y-coordinates for this chart.

String text: this chart's title.

The constructor validates the input data and initializes all instance variables. In the case of a pie chart, an array, m_pieData, is created, which contains sectors with angles normalized to 360 degrees (the sum value used here was calculated previous to this code as the sum of all m_yData[] values):

     m_pieData = new double[m_nData];

     for (int k=0; k<m_nData; k++)

       m_pieData[k] = m_yData[k]*360.0/sum;

This chart component extends JPanel and contains two child components managed using a BorderLayout: JLabel m_title, which displays the chart's title in the NORTH region, and an instance of our custom ChartPanel component, m_chart, which is placed in the CENTER region.

The rest of the code for this class consists of set/get methods supporting instance variables declared in this class and does not require further explanation.

Class JChart2D.ChartPanel

This inner class extends JComponent and represents the custom component that is actually responsible for  rendering our charts. Several instance variables are declared:

int m_xMargin: the left and right margin size of the rendering area.

int m_yMargin: the top and bottom margin size of the rendering area.

int m_pieGap: radial shift for pieces of pie (i.e. spacing between each).

int m_x: left coordinate of the rendering area.

int m_y: top coordinate of the rendering area.

int m_w: width of the rendering area.

int m_h: height of the rendering area.

The ChartPanel constructor enables the processing of component resize events. When such an event occurs, the processComponentEvent() method triggers a call to calcDimensions() (note that this event will normally be generated when ChartPanel is added to a container for the first time). This method retrieves the current component's size, calculates the coordinates of the rendering area, and stores them in the appropriate instance variables listed above. In the case of a column chart, we offset the rendering area by an additional half of the column width, and then shrink it by a full column width. Otherwise, the first and the last columns will be rendered on top of the chart's border.

Methods xChartToScreen() and yChartToScreen() calculate screen coordinates from chart coordinates as illustrated in figure 23.2. We need to scale the chart data so the chart will occupy the entire component region, taking into account the margins. To get the necessary scaling ratios we divide the dimensions of the chart component (minus the margins) by the difference between max and min values of the chart data. These methods are used in rendering the line and column charts because they are based on coordinate data. The only sizing information the pie chart needs is m_w and m_h, as it does not rely on coordinate data.

Figure 23.2 Screen coordinates vs. chart coordinates.

<<file figure23-2.gif>>

The paintComponent() method performs the actual chart rendering. The coordinate axes are drawn first for line and column charts. Then we cast the Graphics instance to a Graphics2D so we have access to Java2D features. As we discussed earlier, we use two rendering hints and assign them with the setRenderingHint() method: anti-aliasing and the preference to render quality over speed. If the m_stroke instance variable has been initialized, the Graphics2D stroke attribute is set using the setStroke() method. The rest of the paintComponent() method is placed into a switch block with cases for each chart type. Before the switch block is entered we create a GeneralPath which we will use to construct each chart using the methods we described in section 23.1.2.

The line chart is the simplest case. It is drawn as a broken line through the array of points representing the chart data. First we start the GeneralPath out by passing the first coordinate of data using moveTo(). Then  we iterate through the chart data adding lines to the path using its lineTo() method. Once we've done this we are ready to render it and use the Graphics2D  draw() method to do so.

Note: The Java2D API provides ways to draw quadratic and cubic curves passing through 3 and 4 given points respectively. Unfortunately this functionality is not suitable for drawing a smooth line chart with interpolation.

The column chart is drawn as a set of vertical bars with a common baseline corresponding to the 0-value of the chart, y0 (note that this value is always included in the [m_yMin, m_yMax] interval). The GeneralPath instance accumulates these bars as Rectangle2D.Double instances using its append() method, passing false for the  line connection option.

If the m_drawShadow flag is set, the next step forms and draws a shadow from these bars, which should be viewed as standing vertically. AffineTransform s0 is constructed to accomplish this in four steps:

1. Transform from screen coordinates to chart coordinates.

2. Scale y-axis by a factor of 0.5.

3. Shear x-axis by a factor of 1.0.

4. Transform chart coordinates back to screen coordinates.

As soon as this AffineTransform is constructed, we create a corresponding transformed version of our path Shape using AffineTransform's createTransformedShape() method. We then set the current color to gray and render it into the 2D graphics context using the fill() method. Finally the set of bars is drawn on the screen. Depending on the m_effectIndex setting we fill this shape with the gradient color, image (by calling our custom fillByImage() method), or with a solid color.

The pie chart is drawn as pieces of a circle with a common center. The larger the chart's value is for a given point, the larger the corresponding angle of that piece is. For an interesting resemblance with a cut pie, all pieces are shifted apart from the common center in the radial direction. To draw such a pie we first build each piece by iterating through the chart's data. Using class Arc2D.Double with its PIE setting provides a convenient way to build a slice of pie. We then translate this slice away from the pie's center in the radial direction using an AffineTransform and its createTransformShape() method. Each resulting shape is appended to our GeneralPath instance.

If the m_drawShadow flag is set, we form and draw a shadow from these pieces. Since this chart can be viewed as laying on a flat surface, the shadow has the same shape as the chart itself, but is translated in the south-east direction. Finally the set of pie pieces is drawn on the screen using the selected visual effect. Since at this point we operate with the chart as a single Shape (remember a GeneralPath is a Shape), the code is the same as for the column chart.

The custom fillByImage() method uses the given Shape instance's bounds as the Graphics2D clipping area, and, in a doubly nested for loop, fills this region using our previously assigned m_foregroundImage. (Note that the third parameter to this method, int xOffset, is used for horizontal displacement which we do not make use of in this example. However, we will see this method again in the next example where we will need this functionality.)

Running the Code

Figure 23.1 shows our Charts2D application containing three charts: line, column, and pie. Try modifying the settings specified in Charts2D class to try charts with various combinations of available visual effects. Also try resizing the frame container and note how each chart is scaled accordingly.

Our JChart2D component can easily be plugged into any Swing application. Since we have implemented full scalability and correct coordinate mapping, we have the beginnings of a professional chart component. The next step would be to add informative strings to the axis as well as pie pieces, bars, and data points of the line chart.

[ Return to Swing (Book) ]

Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color

[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3

[ More in News Section ]

Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy