Source for org.jfree.chart.JFreeChart

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * ---------------
  28:  * JFreeChart.java
  29:  * ---------------
  30:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Andrzej Porebski;
  34:  *                   David Li;
  35:  *                   Wolfgang Irler;
  36:  *                   Christian W. Zuckschwerdt;
  37:  *                   Klaus Rheinwald;
  38:  *                   Nicolas Brodu;
  39:  *
  40:  * $Id: JFreeChart.java,v 1.34.2.10 2006/08/25 13:52:50 mungady Exp $
  41:  *
  42:  * Changes (from 20-Jun-2001)
  43:  * --------------------------
  44:  * 20-Jun-2001 : Modifications submitted by Andrzej Porebski for legend 
  45:  *               placement;
  46:  * 21-Jun-2001 : Removed JFreeChart parameter from Plot constructors (DG);
  47:  * 22-Jun-2001 : Multiple titles added (original code by David Berry, with 
  48:  *               reworkings by DG);
  49:  * 18-Sep-2001 : Updated header (DG);
  50:  * 15-Oct-2001 : Moved data source classes into new package 
  51:  *               com.jrefinery.data.* (DG);
  52:  * 18-Oct-2001 : New factory method for creating VerticalXYBarChart (DG);
  53:  * 19-Oct-2001 : Moved series paint and stroke methods to the Plot class (DG);
  54:  *               Moved static chart creation methods to new ChartFactory 
  55:  *               class (DG);
  56:  * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  57:  *               Fixed bug where chart isn't registered with the dataset (DG);
  58:  * 07-Nov-2001 : Fixed bug where null title in constructor causes 
  59:  *               exception (DG);
  60:  *               Tidied up event notification code (DG);
  61:  * 17-Nov-2001 : Added getLegendItemCount() method (DG);
  62:  * 21-Nov-2001 : Set clipping in draw method to ensure that nothing gets drawn 
  63:  *               outside the chart area (DG);
  64:  * 11-Dec-2001 : Added the createBufferedImage() method, taken from the 
  65:  *               JFreeChartServletDemo class (DG);
  66:  * 13-Dec-2001 : Added tooltips (DG);
  67:  * 16-Jan-2002 : Added handleClick() method (DG);
  68:  * 22-Jan-2002 : Fixed bug correlating legend labels with pie data (DG);
  69:  * 05-Feb-2002 : Removed redundant tooltips code (DG);
  70:  * 19-Feb-2002 : Added accessor methods for the backgroundImage and 
  71:  *               backgroundImageAlpha attributes (DG);
  72:  * 21-Feb-2002 : Added static fields for INFO, COPYRIGHT, LICENCE, CONTRIBUTORS
  73:  *               and LIBRARIES.  These can be used to display information about
  74:  *               JFreeChart (DG);
  75:  * 06-Mar-2002 : Moved constants to JFreeChartConstants interface (DG);
  76:  * 18-Apr-2002 : PieDataset is no longer sorted (oldman);
  77:  * 23-Apr-2002 : Moved dataset to the Plot class (DG);
  78:  * 13-Jun-2002 : Added an extra draw() method (DG);
  79:  * 25-Jun-2002 : Implemented the Drawable interface and removed redundant 
  80:  *               imports (DG);
  81:  * 26-Jun-2002 : Added another createBufferedImage() method (DG);
  82:  * 18-Sep-2002 : Fixed issues reported by Checkstyle (DG);
  83:  * 23-Sep-2002 : Added new contributor (DG);
  84:  * 28-Oct-2002 : Created main title and subtitle list to replace existing title
  85:  *               list (DG);
  86:  * 08-Jan-2003 : Added contributor (DG);
  87:  * 17-Jan-2003 : Added new constructor (DG);
  88:  * 22-Jan-2003 : Added ChartColor class by Cameron Riley, and background image 
  89:  *               alignment code by Christian W. Zuckschwerdt (DG);
  90:  * 11-Feb-2003 : Added flag to allow suppression of chart change events, based 
  91:  *               on a suggestion by Klaus Rheinwald (DG);
  92:  * 04-Mar-2003 : Added small fix for suppressed chart change events (see bug id
  93:  *               690865) (DG);
  94:  * 10-Mar-2003 : Added Benoit Xhenseval to contributors (DG);
  95:  * 26-Mar-2003 : Implemented Serializable (DG);
  96:  * 15-Jul-2003 : Added an optional border for the chart (DG);
  97:  * 11-Sep-2003 : Took care of listeners while cloning (NB);
  98:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  99:  * 22-Sep-2003 : Added nullpointer checks.
 100:  * 25-Sep-2003 : Added nullpointer checks too (NB).
 101:  * 03-Dec-2003 : Legends are now registered by this class instead of using the 
 102:  *               old constructor way (TM);
 103:  * 03-Dec-2003 : Added anchorPoint to draw() method (DG);
 104:  * 08-Jan-2004 : Reworked title code, introducing line wrapping (DG);
 105:  * 09-Feb-2004 : Created additional createBufferedImage() method (DG);
 106:  * 05-Apr-2004 : Added new createBufferedImage() method (DG);
 107:  * 27-May-2004 : Moved constants from JFreeChartConstants.java back to this 
 108:  *               class (DG);
 109:  * 25-Nov-2004 : Updates for changes to Title class (DG);
 110:  * 06-Jan-2005 : Change lookup for default background color (DG);
 111:  * 31-Jan-2005 : Added Don Elliott to contributors (DG);
 112:  * 02-Feb-2005 : Added clearSubtitles() method (DG);
 113:  * 03-Feb-2005 : Added Mofeed Shahin to contributors (DG);
 114:  * 08-Feb-2005 : Updated for RectangleConstraint changes (DG);
 115:  * 28-Mar-2005 : Renamed Legend --> OldLegend (DG);
 116:  * 12-Apr-2005 : Added methods to access legend(s) in subtitle list (DG);
 117:  * 13-Apr-2005 : Added removeLegend() and removeSubtitle() methods (DG);
 118:  * 20-Apr-2005 : Modified to collect chart entities from titles and 
 119:  *               subtitles (DG);
 120:  * 26-Apr-2005 : Removed LOGGER (DG);
 121:  * 06-Jun-2005 : Added addLegend() method and padding attribute, fixed equals() 
 122:  *               method (DG);
 123:  * 24-Nov-2005 : Removed OldLegend and related code - don't want to support
 124:  *               this in 1.0.0 final (DG);
 125:  * ------------- JFREECHART 1.0.0 ---------------------------------------------
 126:  * 27-Jan-2006 : Updated version number (DG);
 127:  * 
 128:  */
 129: 
 130: package org.jfree.chart;
 131: 
 132: import java.awt.AlphaComposite;
 133: import java.awt.BasicStroke;
 134: import java.awt.Color;
 135: import java.awt.Composite;
 136: import java.awt.Font;
 137: import java.awt.Graphics2D;
 138: import java.awt.Image;
 139: import java.awt.Paint;
 140: import java.awt.RenderingHints;
 141: import java.awt.Shape;
 142: import java.awt.Stroke;
 143: import java.awt.geom.AffineTransform;
 144: import java.awt.geom.Point2D;
 145: import java.awt.geom.Rectangle2D;
 146: import java.awt.image.BufferedImage;
 147: import java.io.IOException;
 148: import java.io.ObjectInputStream;
 149: import java.io.ObjectOutputStream;
 150: import java.io.Serializable;
 151: import java.net.URL;
 152: import java.util.ArrayList;
 153: import java.util.Arrays;
 154: import java.util.Iterator;
 155: import java.util.List;
 156: import java.util.ResourceBundle;
 157: 
 158: import javax.swing.ImageIcon;
 159: import javax.swing.UIManager;
 160: import javax.swing.event.EventListenerList;
 161: 
 162: import org.jfree.JCommon;
 163: import org.jfree.chart.block.BlockBorder;
 164: import org.jfree.chart.block.BlockParams;
 165: import org.jfree.chart.block.EntityBlockResult;
 166: import org.jfree.chart.block.LengthConstraintType;
 167: import org.jfree.chart.block.RectangleConstraint;
 168: import org.jfree.chart.entity.EntityCollection;
 169: import org.jfree.chart.event.ChartChangeEvent;
 170: import org.jfree.chart.event.ChartChangeListener;
 171: import org.jfree.chart.event.ChartProgressEvent;
 172: import org.jfree.chart.event.ChartProgressListener;
 173: import org.jfree.chart.event.PlotChangeEvent;
 174: import org.jfree.chart.event.PlotChangeListener;
 175: import org.jfree.chart.event.TitleChangeEvent;
 176: import org.jfree.chart.event.TitleChangeListener;
 177: import org.jfree.chart.plot.CategoryPlot;
 178: import org.jfree.chart.plot.Plot;
 179: import org.jfree.chart.plot.PlotRenderingInfo;
 180: import org.jfree.chart.plot.XYPlot;
 181: import org.jfree.chart.title.LegendTitle;
 182: import org.jfree.chart.title.TextTitle;
 183: import org.jfree.chart.title.Title;
 184: import org.jfree.data.Range;
 185: import org.jfree.io.SerialUtilities;
 186: import org.jfree.ui.Align;
 187: import org.jfree.ui.Drawable;
 188: import org.jfree.ui.HorizontalAlignment;
 189: import org.jfree.ui.RectangleEdge;
 190: import org.jfree.ui.RectangleInsets;
 191: import org.jfree.ui.Size2D;
 192: import org.jfree.ui.VerticalAlignment;
 193: import org.jfree.ui.about.Contributor;
 194: import org.jfree.ui.about.Licences;
 195: import org.jfree.ui.about.ProjectInfo;
 196: import org.jfree.util.ObjectUtilities;
 197: import org.jfree.util.PaintUtilities;
 198: 
 199: /**
 200:  * A chart class implemented using the Java 2D APIs.  The current version
 201:  * supports bar charts, line charts, pie charts and xy plots (including time
 202:  * series data).
 203:  * <P>
 204:  * JFreeChart coordinates several objects to achieve its aim of being able to
 205:  * draw a chart on a Java 2D graphics device: a list of {@link Title} objects
 206:  * (which often includes the chart's legend), a {@link Plot} and a 
 207:  * {@link org.jfree.data.general.Dataset} (the plot in turn manages a 
 208:  * domain axis and a range axis).
 209:  * <P>
 210:  * You should use a {@link ChartPanel} to display a chart in a GUI.
 211:  * <P>
 212:  * The {@link ChartFactory} class contains static methods for creating 
 213:  * 'ready-made' charts.
 214:  *
 215:  * @see ChartPanel
 216:  * @see ChartFactory
 217:  * @see Title
 218:  * @see Plot
 219:  */
 220: public class JFreeChart implements Drawable,
 221:                                    TitleChangeListener,
 222:                                    PlotChangeListener,
 223:                                    Serializable,
 224:                                    Cloneable {
 225: 
 226:     /** For serialization. */    
 227:     private static final long serialVersionUID = -3470703747817429120L;
 228:     
 229:     /** Information about the project. */
 230:     public static final ProjectInfo INFO = new JFreeChartInfo();
 231: 
 232:     /** The default font for titles. */
 233:     public static final Font DEFAULT_TITLE_FONT 
 234:         = new Font("SansSerif", Font.BOLD, 18);
 235: 
 236:     /** The default background color. */
 237:     public static final Paint DEFAULT_BACKGROUND_PAINT 
 238:         = UIManager.getColor("Panel.background");
 239: 
 240:     /** The default background image. */
 241:     public static final Image DEFAULT_BACKGROUND_IMAGE = null;
 242: 
 243:     /** The default background image alignment. */
 244:     public static final int DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = Align.FIT;
 245: 
 246:     /** The default background image alpha. */
 247:     public static final float DEFAULT_BACKGROUND_IMAGE_ALPHA = 0.5f;
 248: 
 249:     /** 
 250:      * Rendering hints that will be used for chart drawing.  This should never
 251:      * be <code>null</code>. 
 252:      */
 253:     private transient RenderingHints renderingHints;
 254: 
 255:     /** A flag that controls whether or not the chart border is drawn. */
 256:     private boolean borderVisible;
 257: 
 258:     /** The stroke used to draw the chart border (if visible). */
 259:     private transient Stroke borderStroke;
 260: 
 261:     /** The paint used to draw the chart border (if visible). */
 262:     private transient Paint borderPaint;
 263: 
 264:     /** The padding between the chart border and the chart drawing area. */
 265:     private RectangleInsets padding;
 266:     
 267:     /** The chart title (optional). */
 268:     private TextTitle title;
 269: 
 270:     /** The chart subtitles (zero, one or many). */
 271:     private List subtitles;
 272: 
 273:     /** Draws the visual representation of the data. */
 274:     private Plot plot;
 275: 
 276:     /** Paint used to draw the background of the chart. */
 277:     private transient Paint backgroundPaint;
 278: 
 279:     /** An optional background image for the chart. */
 280:     private transient Image backgroundImage;  // todo: not serialized yet
 281: 
 282:     /** The alignment for the background image. */
 283:     private int backgroundImageAlignment = Align.FIT;
 284: 
 285:     /** The alpha transparency for the background image. */
 286:     private float backgroundImageAlpha = 0.5f;
 287: 
 288:     /** Storage for registered change listeners. */
 289:     private transient EventListenerList changeListeners;
 290: 
 291:     /** Storage for registered progress listeners. */
 292:     private transient EventListenerList progressListeners;
 293: 
 294:     /** 
 295:      * A flag that can be used to enable/disable notification of chart change 
 296:      * events. 
 297:      */
 298:     private boolean notify;
 299:     
 300:     /**
 301:      * Creates a new chart based on the supplied plot.  The chart will have
 302:      * a legend added automatically, but no title (although you can easily add
 303:      * one later).  
 304:      * <br><br>
 305:      * Note that the  {@link ChartFactory} class contains a range 
 306:      * of static methods that will return ready-made charts, and often this
 307:      * is a more convenient way to create charts than using this constructor.
 308:      *
 309:      * @param plot  the plot (<code>null</code> not permitted).
 310:      */
 311:     public JFreeChart(Plot plot) {
 312:         this(null, null, plot, true);
 313:     }
 314: 
 315:     /**
 316:      * Creates a new chart with the given title and plot.  A default font 
 317:      * (@link DEFAULT_TITLE_FONT) is used for the title, and the chart will 
 318:      * have a legend added automatically.  
 319:      * <br><br>
 320:      * Note that the  {@link ChartFactory} class contains a range 
 321:      * of static methods that will return ready-made charts, and often this
 322:      * is a more convenient way to create charts than using this constructor.
 323:      *
 324:      * @param title  the chart title (<code>null</code> permitted).
 325:      * @param plot  the plot (<code>null</code> not permitted).
 326:      */
 327:     public JFreeChart(String title, Plot plot) {
 328:         this(title, JFreeChart.DEFAULT_TITLE_FONT, plot, true);
 329:     }
 330: 
 331:     /**
 332:      * Creates a new chart with the given title and plot.  The 
 333:      * <code>createLegend</code> argument specifies whether or not a legend
 334:      * should be added to the chart.  
 335:      * <br><br>
 336:      * Note that the  {@link ChartFactory} class contains a range 
 337:      * of static methods that will return ready-made charts, and often this
 338:      * is a more convenient way to create charts than using this constructor.
 339:      *
 340:      * @param title  the chart title (<code>null</code> permitted).
 341:      * @param titleFont  the font for displaying the chart title 
 342:      *                   (<code>null</code> permitted).
 343:      * @param plot  controller of the visual representation of the data 
 344:      *              (<code>null</code> not permitted).
 345:      * @param createLegend  a flag indicating whether or not a legend should   
 346:      *                      be created for the chart.
 347:      */
 348:     public JFreeChart(String title, Font titleFont, Plot plot, 
 349:                       boolean createLegend) {
 350: 
 351:         if (plot == null) {
 352:             throw new NullPointerException("Null 'plot' argument.");
 353:         }
 354: 
 355:         // create storage for listeners...
 356:         this.progressListeners = new EventListenerList();
 357:         this.changeListeners = new EventListenerList();
 358:         this.notify = true;  // default is to notify listeners when the 
 359:                              // chart changes
 360: 
 361:         this.renderingHints = new RenderingHints(
 362:                 RenderingHints.KEY_ANTIALIASING, 
 363:                 RenderingHints.VALUE_ANTIALIAS_ON);
 364: 
 365:         this.borderVisible = false;
 366:         this.borderStroke = new BasicStroke(1.0f);
 367:         this.borderPaint = Color.black;
 368: 
 369:         this.padding = RectangleInsets.ZERO_INSETS;
 370:         
 371:         this.plot = plot;
 372:         plot.addChangeListener(this);
 373: 
 374:         this.subtitles = new ArrayList();
 375: 
 376:         // create a legend, if requested...
 377:         if (createLegend) {
 378:             LegendTitle legend = new LegendTitle(this.plot);
 379:             legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0));
 380:             legend.setBorder(new BlockBorder());
 381:             legend.setBackgroundPaint(Color.white);
 382:             legend.setPosition(RectangleEdge.BOTTOM);
 383:             this.subtitles.add(legend);
 384:         }
 385: 
 386:         // add the chart title, if one has been specified...
 387:         if (title != null) {
 388:             if (titleFont == null) {
 389:                 titleFont = DEFAULT_TITLE_FONT;
 390:             }
 391:             this.title = new TextTitle(title, titleFont);
 392:             this.title.addChangeListener(this);
 393:         }
 394: 
 395:         this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
 396: 
 397:         this.backgroundImage = DEFAULT_BACKGROUND_IMAGE;
 398:         this.backgroundImageAlignment = DEFAULT_BACKGROUND_IMAGE_ALIGNMENT;
 399:         this.backgroundImageAlpha = DEFAULT_BACKGROUND_IMAGE_ALPHA;
 400: 
 401:     }
 402: 
 403:     /**
 404:      * Returns the collection of rendering hints for the chart.
 405:      *
 406:      * @return The rendering hints for the chart (never <code>null</code>).
 407:      */
 408:     public RenderingHints getRenderingHints() {
 409:         return this.renderingHints;
 410:     }
 411: 
 412:     /**
 413:      * Sets the rendering hints for the chart.  These will be added (using the 
 414:      * Graphics2D.addRenderingHints() method) near the start of the 
 415:      * JFreeChart.draw() method.
 416:      *
 417:      * @param renderingHints  the rendering hints (<code>null</code> not 
 418:      *                        permitted).
 419:      */
 420:     public void setRenderingHints(RenderingHints renderingHints) {
 421:         if (renderingHints == null) {
 422:             throw new NullPointerException("RenderingHints given are null");
 423:         }
 424:         this.renderingHints = renderingHints;
 425:         fireChartChanged();
 426:     }
 427: 
 428:     /**
 429:      * Returns a flag that controls whether or not a border is drawn around the
 430:      * outside of the chart.
 431:      *
 432:      * @return A boolean.
 433:      */
 434:     public boolean isBorderVisible() {
 435:         return this.borderVisible;
 436:     }
 437: 
 438:     /**
 439:      * Sets a flag that controls whether or not a border is drawn around the 
 440:      * outside of the chart.
 441:      *
 442:      * @param visible  the flag.
 443:      */
 444:     public void setBorderVisible(boolean visible) {
 445:         this.borderVisible = visible;
 446:         fireChartChanged();
 447:     }
 448: 
 449:     /**
 450:      * Returns the stroke used to draw the chart border (if visible).
 451:      *
 452:      * @return The border stroke.
 453:      */
 454:     public Stroke getBorderStroke() {
 455:         return this.borderStroke;
 456:     }
 457: 
 458:     /**
 459:      * Sets the stroke used to draw the chart border (if visible).
 460:      *
 461:      * @param stroke  the stroke.
 462:      */
 463:     public void setBorderStroke(Stroke stroke) {
 464:         this.borderStroke = stroke;
 465:         fireChartChanged();
 466:     }
 467: 
 468:     /**
 469:      * Returns the paint used to draw the chart border (if visible).
 470:      *
 471:      * @return The border paint.
 472:      */
 473:     public Paint getBorderPaint() {
 474:         return this.borderPaint;
 475:     }
 476: 
 477:     /**
 478:      * Sets the paint used to draw the chart border (if visible).
 479:      *
 480:      * @param paint  the paint.
 481:      */
 482:     public void setBorderPaint(Paint paint) {
 483:         this.borderPaint = paint;
 484:         fireChartChanged();
 485:     }
 486:     
 487:     /**
 488:      * Returns the padding between the chart border and the chart drawing area.
 489:      * 
 490:      * @return The padding (never <code>null</code>).
 491:      */
 492:     public RectangleInsets getPadding() {
 493:         return this.padding;   
 494:     }
 495: 
 496:     /**
 497:      * Sets the padding between the chart border and the chart drawing area,
 498:      * and sends a {@link ChartChangeEvent} to all registered listeners.
 499:      * 
 500:      * @param padding  the padding (<code>null</code> not permitted).
 501:      */
 502:     public void setPadding(RectangleInsets padding) {
 503:         if (padding == null) {
 504:             throw new IllegalArgumentException("Null 'padding' argument.");   
 505:         }
 506:         this.padding = padding;
 507:         notifyListeners(new ChartChangeEvent(this));
 508:     }
 509:     
 510:     /**
 511:      * Returns the main chart title.  Very often a chart will have just one
 512:      * title, so we make this case simple by providing accessor methods for
 513:      * the main title.  However, multiple titles are supported - see the
 514:      * {@link #addSubtitle(Title)} method.
 515:      *
 516:      * @return The chart title (possibly <code>null</code>).
 517:      */
 518:     public TextTitle getTitle() {
 519:         return this.title;
 520:     }
 521: 
 522:     /**
 523:      * Sets the main title for the chart and sends a {@link ChartChangeEvent} 
 524:      * to all registered listeners.  If you do not want a title for the 
 525:      * chart, set it to <code>null</code>.  If you want more than one title on
 526:      * a chart, use the {@link #addSubtitle(Title)} method.
 527:      *
 528:      * @param title  the title (<code>null</code> permitted).
 529:      */
 530:     public void setTitle(TextTitle title) {
 531:         this.title = title;
 532:         fireChartChanged();
 533:     }
 534: 
 535:     /**
 536:      * Sets the chart title and sends a {@link ChartChangeEvent} to all 
 537:      * registered listeners.  This is a convenience method that ends up calling 
 538:      * the {@link #setTitle(TextTitle)} method.  If there is an existing title,
 539:      * its text is updated, otherwise a new title using the default font is 
 540:      * added to the chart.  If <code>text</code> is <code>null</code> the chart
 541:      * title is set to <code>null</code>.
 542:      *
 543:      * @param text  the title text (<code>null</code> permitted).
 544:      */
 545:     public void setTitle(String text) {
 546:         if (text != null) {
 547:             if (this.title == null) {
 548:                 setTitle(new TextTitle(text, JFreeChart.DEFAULT_TITLE_FONT));
 549:             }
 550:             else {
 551:                 this.title.setText(text);
 552:             }
 553:         }
 554:         else {
 555:             setTitle((TextTitle) null);
 556:         }
 557:     }
 558: 
 559:     /**
 560:      * Adds a legend to the plot and sends a {@link ChartChangeEvent} to all
 561:      * registered listeners.
 562:      * 
 563:      * @param legend  the legend (<code>null</code> not permitted).
 564:      */
 565:     public void addLegend(LegendTitle legend) {
 566:         addSubtitle(legend);    
 567:     }
 568:     
 569:     /**
 570:      * Returns the legend for the chart, if there is one.  Note that a chart
 571:      * can have more than one legend - this method returns the first.
 572:      * 
 573:      * @return The legend (possibly <code>null</code>).
 574:      */
 575:     public LegendTitle getLegend() {
 576:         return getLegend(0);
 577:     }
 578:     
 579:     /**
 580:      * Returns the nth legend for a chart, or <code>null</code>.
 581:      * 
 582:      * @param index  the legend index (zero-based).
 583:      * 
 584:      * @return The legend (possibly <code>null</code>).
 585:      */
 586:     public LegendTitle getLegend(int index) {
 587:         int seen = 0;
 588:         Iterator iterator = this.subtitles.iterator();
 589:         while (iterator.hasNext()) {
 590:             Title subtitle = (Title) iterator.next();
 591:             if (subtitle instanceof LegendTitle) {
 592:                 if (seen == index) {
 593:                     return (LegendTitle) subtitle;
 594:                 }
 595:                 else {
 596:                     seen++;   
 597:                 }
 598:             }
 599:         }
 600:         return null;        
 601:     }
 602:     
 603:     /**
 604:      * Removes the first legend in the chart and sends a 
 605:      * {@link ChartChangeEvent} to all registered listeners.
 606:      */
 607:     public void removeLegend() {
 608:         removeSubtitle(getLegend());
 609:     }
 610:     
 611:     /**
 612:      * Returns the list of subtitles for the chart.
 613:      *
 614:      * @return The subtitle list (possibly empty, but never <code>null</code>).
 615:      */
 616:     public List getSubtitles() {
 617:         return this.subtitles;
 618:     }
 619: 
 620:     /**
 621:      * Sets the title list for the chart (completely replaces any existing 
 622:      * titles).
 623:      *
 624:      * @param subtitles  the new list of subtitles (<code>null</code> not 
 625:      *                   permitted).
 626:      */
 627:     public void setSubtitles(List subtitles) {
 628:         if (subtitles == null) {
 629:             throw new NullPointerException("Null 'subtitles' argument.");
 630:         }
 631:         this.subtitles = subtitles;
 632:         fireChartChanged();
 633:     }
 634: 
 635:     /**
 636:      * Returns the number of titles for the chart.
 637:      *
 638:      * @return The number of titles for the chart.
 639:      */
 640:     public int getSubtitleCount() {
 641:         return this.subtitles.size();
 642:     }
 643: 
 644:     /**
 645:      * Returns a chart subtitle.
 646:      *
 647:      * @param index  the index of the chart subtitle (zero based).
 648:      *
 649:      * @return A chart subtitle.
 650:      */
 651:     public Title getSubtitle(int index) {
 652:         if ((index < 0) || (index == getSubtitleCount())) {
 653:             throw new IllegalArgumentException("Index out of range.");
 654:         }
 655:         return (Title) this.subtitles.get(index);
 656:     }
 657: 
 658:     /**
 659:      * Adds a chart subtitle, and notifies registered listeners that the chart 
 660:      * has been modified.
 661:      *
 662:      * @param subtitle  the subtitle (<code>null</code> not permitted).
 663:      */
 664:     public void addSubtitle(Title subtitle) {
 665:         if (subtitle == null) {
 666:             throw new IllegalArgumentException("Null 'subtitle' argument.");
 667:         }
 668:         this.subtitles.add(subtitle);
 669:         subtitle.addChangeListener(this);
 670:         fireChartChanged();
 671:     }
 672:     
 673:     /**
 674:      * Clears all subtitles from the chart and sends a {@link ChartChangeEvent}
 675:      * to all registered listeners.
 676:      */
 677:     public void clearSubtitles() {
 678:         Iterator iterator = this.subtitles.iterator();
 679:         while (iterator.hasNext()) {
 680:             Title t = (Title) iterator.next();
 681:             t.removeChangeListener(this);
 682:         }
 683:         this.subtitles.clear();
 684:         fireChartChanged();
 685:     }
 686: 
 687:     /**
 688:      * Removes the specified subtitle and sends a {@link ChartChangeEvent} to
 689:      * all registered listeners.
 690:      * 
 691:      * @param title  the title.
 692:      */
 693:     public void removeSubtitle(Title title) {
 694:         this.subtitles.remove(title);
 695:         fireChartChanged();
 696:     }
 697:     
 698:     /**
 699:      * Returns the plot for the chart.  The plot is a class responsible for
 700:      * coordinating the visual representation of the data, including the axes
 701:      * (if any).
 702:      *
 703:      * @return The plot.
 704:      */
 705:     public Plot getPlot() {
 706:         return this.plot;
 707:     }
 708: 
 709:     /**
 710:      * Returns the plot cast as a {@link CategoryPlot}.
 711:      * <p>
 712:      * NOTE: if the plot is not an instance of {@link CategoryPlot}, then a
 713:      * <code>ClassCastException</code> is thrown.
 714:      *
 715:      * @return The plot.
 716:      */
 717:     public CategoryPlot getCategoryPlot() {
 718:         return (CategoryPlot) this.plot;
 719:     }
 720: 
 721:     /**
 722:      * Returns the plot cast as an {@link XYPlot}.
 723:      * <p>
 724:      * NOTE: if the plot is not an instance of {@link XYPlot}, then a
 725:      * <code>ClassCastException</code> is thrown.
 726:      *
 727:      * @return The plot.
 728:      */
 729:     public XYPlot getXYPlot() {
 730:         return (XYPlot) this.plot;
 731:     }
 732: 
 733:     /**
 734:      * Returns a flag that indicates whether or not anti-aliasing is used when
 735:      * the chart is drawn.
 736:      *
 737:      * @return The flag.
 738:      */
 739:     public boolean getAntiAlias() {
 740:         Object o = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
 741:         if (o == null) {
 742:             return false;
 743:         }
 744:         return (o.equals(RenderingHints.VALUE_ANTIALIAS_ON));
 745:     }
 746: 
 747:     /**
 748:      * Sets a flag that indicates whether or not anti-aliasing is used when the
 749:      * chart is drawn.
 750:      * <P>
 751:      * Anti-aliasing usually improves the appearance of charts, but is slower.
 752:      *
 753:      * @param flag  the new value of the flag.
 754:      */
 755:     public void setAntiAlias(boolean flag) {
 756: 
 757:         Object o = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING);
 758:         if (o == null) {
 759:             o = RenderingHints.VALUE_ANTIALIAS_DEFAULT;
 760:         }
 761:         if (!flag && RenderingHints.VALUE_ANTIALIAS_OFF.equals(o) 
 762:             || flag && RenderingHints.VALUE_ANTIALIAS_ON.equals(o)) {
 763:             // no change, do nothing
 764:             return;
 765:         }
 766:         if (flag) {
 767:             this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 
 768:                                     RenderingHints.VALUE_ANTIALIAS_ON);
 769:         }
 770:         else {
 771:             this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 
 772:                                     RenderingHints.VALUE_ANTIALIAS_OFF);
 773:         }
 774:         fireChartChanged();
 775: 
 776:     }
 777: 
 778:     /**
 779:      * Returns the paint used for the chart background.
 780:      *
 781:      * @return The paint (possibly <code>null</code>).
 782:      */
 783:     public Paint getBackgroundPaint() {
 784:         return this.backgroundPaint;
 785:     }
 786: 
 787:     /**
 788:      * Sets the paint used to fill the chart background and sends a 
 789:      * {@link ChartChangeEvent} to all registered listeners.
 790:      *
 791:      * @param paint  the paint (<code>null</code> permitted).
 792:      */
 793:     public void setBackgroundPaint(Paint paint) {
 794: 
 795:         if (this.backgroundPaint != null) {
 796:             if (!this.backgroundPaint.equals(paint)) {
 797:                 this.backgroundPaint = paint;
 798:                 fireChartChanged();
 799:             }
 800:         }
 801:         else {
 802:             if (paint != null) {
 803:                 this.backgroundPaint = paint;
 804:                 fireChartChanged();
 805:             }
 806:         }
 807: 
 808:     }
 809: 
 810:     /**
 811:      * Returns the background image for the chart, or <code>null</code> if 
 812:      * there is no image.
 813:      *
 814:      * @return The image (possibly <code>null</code>).
 815:      */
 816:     public Image getBackgroundImage() {
 817:         return this.backgroundImage;
 818:     }
 819: 
 820:     /**
 821:      * Sets the background image for the chart and sends a 
 822:      * {@link ChartChangeEvent} to all registered listeners.
 823:      *
 824:      * @param image  the image (<code>null</code> permitted).
 825:      */
 826:     public void setBackgroundImage(Image image) {
 827: 
 828:         if (this.backgroundImage != null) {
 829:             if (!this.backgroundImage.equals(image)) {
 830:                 this.backgroundImage = image;
 831:                 fireChartChanged();
 832:             }
 833:         }
 834:         else {
 835:             if (image != null) {
 836:                 this.backgroundImage = image;
 837:                 fireChartChanged();
 838:             }
 839:         }
 840: 
 841:     }
 842: 
 843:     /**
 844:      * Returns the background image alignment. Alignment constants are defined 
 845:      * in the <code>org.jfree.ui.Align</code> class in the JCommon class 
 846:      * library.
 847:      *
 848:      * @return The alignment.
 849:      */
 850:     public int getBackgroundImageAlignment() {
 851:         return this.backgroundImageAlignment;
 852:     }
 853: 
 854:     /**
 855:      * Sets the background alignment.  Alignment options are defined by the 
 856:      * {@link org.jfree.ui.Align} class.
 857:      *
 858:      * @param alignment  the alignment.
 859:      */
 860:     public void setBackgroundImageAlignment(int alignment) {
 861:         if (this.backgroundImageAlignment != alignment) {
 862:             this.backgroundImageAlignment = alignment;
 863:             fireChartChanged();
 864:         }
 865:     }
 866: 
 867:     /**
 868:      * Returns the alpha-transparency for the chart's background image.
 869:      *
 870:      * @return The alpha-transparency.
 871:      */
 872:     public float getBackgroundImageAlpha() {
 873:         return this.backgroundImageAlpha;
 874:     }
 875: 
 876:     /**
 877:      * Sets the alpha-transparency for the chart's background image.
 878:      * Registered listeners are notified that the chart has been changed.
 879:      *
 880:      * @param alpha  the alpha value.
 881:      */
 882:     public void setBackgroundImageAlpha(float alpha) {
 883: 
 884:         if (this.backgroundImageAlpha != alpha) {
 885:             this.backgroundImageAlpha = alpha;
 886:             fireChartChanged();
 887:         }
 888: 
 889:     }
 890: 
 891:     /**
 892:      * Returns a flag that controls whether or not change events are sent to 
 893:      * registered listeners.
 894:      *
 895:      * @return A boolean.
 896:      */
 897:     public boolean isNotify() {
 898:         return this.notify;
 899:     }
 900: 
 901:     /**
 902:      * Sets a flag that controls whether or not listeners receive 
 903:      * {@link ChartChangeEvent} notifications.
 904:      *
 905:      * @param notify  a boolean.
 906:      */
 907:     public void setNotify(boolean notify) {
 908:         this.notify = notify;
 909:         // if the flag is being set to true, there may be queued up changes...
 910:         if (notify) {
 911:             notifyListeners(new ChartChangeEvent(this));
 912:         }
 913:     }
 914: 
 915:     /**
 916:      * Draws the chart on a Java 2D graphics device (such as the screen or a
 917:      * printer).
 918:      * <P>
 919:      * This method is the focus of the entire JFreeChart library.
 920:      *
 921:      * @param g2  the graphics device.
 922:      * @param area  the area within which the chart should be drawn.
 923:      */
 924:     public void draw(Graphics2D g2, Rectangle2D area) {
 925:         draw(g2, area, null, null);
 926:     }
 927: 
 928:     /**
 929:      * Draws the chart on a Java 2D graphics device (such as the screen or a
 930:      * printer).  This method is the focus of the entire JFreeChart library.
 931:      *
 932:      * @param g2  the graphics device.
 933:      * @param area  the area within which the chart should be drawn.
 934:      * @param info  records info about the drawing (null means collect no info).
 935:      */
 936:     public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) {
 937:         draw(g2, area, null, info);
 938:     }
 939:     
 940:     /**
 941:      * Draws the chart on a Java 2D graphics device (such as the screen or a
 942:      * printer).
 943:      * <P>
 944:      * This method is the focus of the entire JFreeChart library.
 945:      *
 946:      * @param g2  the graphics device.
 947:      * @param chartArea  the area within which the chart should be drawn.
 948:      * @param anchor  the anchor point (in Java2D space) for the chart 
 949:      *                (<code>null</code> permitted).
 950:      * @param info  records info about the drawing (null means collect no info).
 951:      */
 952:     public void draw(Graphics2D g2, 
 953:                      Rectangle2D chartArea, Point2D anchor, 
 954:                      ChartRenderingInfo info) {
 955: 
 956:         notifyListeners(new ChartProgressEvent(this, this, 
 957:                 ChartProgressEvent.DRAWING_STARTED, 0));
 958: 
 959:         // record the chart area, if info is requested...
 960:         if (info != null) {
 961:             info.clear();
 962:             info.setChartArea(chartArea);
 963:         }
 964: 
 965:         // ensure no drawing occurs outside chart area...
 966:         Shape savedClip = g2.getClip();
 967:         g2.clip(chartArea);
 968: 
 969:         g2.addRenderingHints(this.renderingHints);
 970: 
 971:         // draw the chart background...
 972:         if (this.backgroundPaint != null) {
 973:             g2.setPaint(this.backgroundPaint);
 974:             g2.fill(chartArea);
 975:         }
 976: 
 977:         if (this.backgroundImage != null) {
 978:             Composite originalComposite = g2.getComposite();
 979:             g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 
 980:                     this.backgroundImageAlpha));
 981:             Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 
 982:                     this.backgroundImage.getWidth(null), 
 983:                     this.backgroundImage.getHeight(null));
 984:             Align.align(dest, chartArea, this.backgroundImageAlignment);
 985:             g2.drawImage(this.backgroundImage, (int) dest.getX(), 
 986:                     (int) dest.getY(), (int) dest.getWidth(), 
 987:                     (int) dest.getHeight(), null);
 988:             g2.setComposite(originalComposite);
 989:         }
 990: 
 991:         if (isBorderVisible()) {
 992:             Paint paint = getBorderPaint();
 993:             Stroke stroke = getBorderStroke();
 994:             if (paint != null && stroke != null) {
 995:                 Rectangle2D borderArea = new Rectangle2D.Double(
 996:                         chartArea.getX(), chartArea.getY(), 
 997:                         chartArea.getWidth() - 1.0, chartArea.getHeight() 
 998:                         - 1.0);
 999:                 g2.setPaint(paint);
1000:                 g2.setStroke(stroke);
1001:                 g2.draw(borderArea);
1002:             }
1003:         }
1004: 
1005:         // draw the title and subtitles...
1006:         Rectangle2D nonTitleArea = new Rectangle2D.Double();
1007:         nonTitleArea.setRect(chartArea);
1008:         this.padding.trim(nonTitleArea);
1009:         
1010:         EntityCollection entities = null;
1011:         if (info != null) {
1012:             entities = info.getEntityCollection();   
1013:         }
1014:         if (this.title != null) {
1015:             EntityCollection e = drawTitle(this.title, g2, nonTitleArea, 
1016:                     (entities != null));
1017:             if (e != null) {
1018:                 entities.addAll(e);   
1019:             }
1020:         }
1021: 
1022:         Iterator iterator = this.subtitles.iterator();
1023:         while (iterator.hasNext()) {
1024:             Title currentTitle = (Title) iterator.next();
1025:             EntityCollection e = drawTitle(currentTitle, g2, nonTitleArea, 
1026:                     (entities != null));
1027:             if (e != null) {
1028:                 entities.addAll(e);   
1029:             }
1030:         }
1031: 
1032:         Rectangle2D plotArea = nonTitleArea;
1033:  
1034:         // draw the plot (axes and data visualisation)
1035:         PlotRenderingInfo plotInfo = null;
1036:         if (info != null) {
1037:             plotInfo = info.getPlotInfo();
1038:         }
1039:         this.plot.draw(g2, plotArea, anchor, null, plotInfo);
1040: 
1041:         g2.setClip(savedClip);
1042: 
1043:         notifyListeners(new ChartProgressEvent(this, this, 
1044:                 ChartProgressEvent.DRAWING_FINISHED, 100));
1045:     }
1046: 
1047:     /**
1048:      * Creates a rectangle that is aligned to the frame.
1049:      * 
1050:      * @param dimensions
1051:      * @param frame
1052:      * @param hAlign
1053:      * @param vAlign
1054:      * 
1055:      * @return A rectangle.
1056:      */
1057:     private Rectangle2D createAlignedRectangle2D(Size2D dimensions, 
1058:             Rectangle2D frame, HorizontalAlignment hAlign, 
1059:             VerticalAlignment vAlign) {
1060:         double x = Double.NaN;
1061:         double y = Double.NaN;
1062:         if (hAlign == HorizontalAlignment.LEFT) {
1063:             x = frame.getX();   
1064:         }
1065:         else if (hAlign == HorizontalAlignment.CENTER) {
1066:             x = frame.getCenterX() - (dimensions.width / 2.0);   
1067:         }
1068:         else if (hAlign == HorizontalAlignment.RIGHT) {
1069:             x = frame.getMaxX() - dimensions.width;   
1070:         }
1071:         if (vAlign == VerticalAlignment.TOP) {
1072:             y = frame.getY();   
1073:         }
1074:         else if (vAlign == VerticalAlignment.CENTER) {
1075:             y = frame.getCenterY() - (dimensions.height / 2.0);   
1076:         }
1077:         else if (vAlign == VerticalAlignment.BOTTOM) {
1078:             y = frame.getMaxY() - dimensions.height;   
1079:         }
1080:         
1081:         return new Rectangle2D.Double(x, y, dimensions.width, 
1082:                 dimensions.height);
1083:     }
1084:     
1085:     /**
1086:      * Draws a title.  The title should be drawn at the top, bottom, left or 
1087:      * right of the specified area, and the area should be updated to reflect 
1088:      * the amount of space used by the title.
1089:      *
1090:      * @param t  the title (<code>null</code> not permitted).
1091:      * @param g2  the graphics device (<code>null</code> not permitted).
1092:      * @param area  the chart area, excluding any existing titles 
1093:      *              (<code>null</code> not permitted).
1094:      * @param entities  a flag that controls whether or not an entity 
1095:      *                  collection is returned for the title.
1096:      * 
1097:      * @return An entity collection for the title (possibly <code>null</code>).
1098:      */
1099:     protected EntityCollection drawTitle(Title t, Graphics2D g2, 
1100:                                          Rectangle2D area, boolean entities) {
1101: 
1102:         if (t == null) {
1103:             throw new IllegalArgumentException("Null 't' argument.");   
1104:         }
1105:         if (area == null) {
1106:             throw new IllegalArgumentException("Null 'area' argument.");   
1107:         }
1108:         Rectangle2D titleArea = new Rectangle2D.Double();
1109:         RectangleEdge position = t.getPosition();
1110:         double ww = area.getWidth();
1111:         if (ww <= 0.0) {
1112:             return null;
1113:         }
1114:         double hh = area.getHeight();
1115:         if (hh <= 0.0) {
1116:             return null;
1117:         }
1118:         RectangleConstraint constraint = new RectangleConstraint(ww, 
1119:                 new Range(0.0, ww), LengthConstraintType.RANGE, hh, 
1120:                 new Range(0.0, hh), LengthConstraintType.RANGE);
1121:         Object retValue = null;
1122:         BlockParams p = new BlockParams();
1123:         p.setGenerateEntities(entities);
1124:         if (position == RectangleEdge.TOP) {
1125:             Size2D size = t.arrange(g2, constraint);
1126:             titleArea = createAlignedRectangle2D(size, area, 
1127:                     t.getHorizontalAlignment(), VerticalAlignment.TOP);
1128:             retValue = t.draw(g2, titleArea, p);
1129:             area.setRect(area.getX(), Math.min(area.getY() + size.height, 
1130:                     area.getMaxY()), area.getWidth(), Math.max(area.getHeight()
1131:                     - size.height, 0));
1132:         }
1133:         else if (position == RectangleEdge.BOTTOM) {
1134:             Size2D size = t.arrange(g2, constraint);
1135:             titleArea = createAlignedRectangle2D(size, area, 
1136:                     t.getHorizontalAlignment(), VerticalAlignment.BOTTOM);
1137:             retValue = t.draw(g2, titleArea, p);
1138:             area.setRect(area.getX(), area.getY(), area.getWidth(), 
1139:                     area.getHeight() - size.height);
1140:         }
1141:         else if (position == RectangleEdge.RIGHT) {
1142:             Size2D size = t.arrange(g2, constraint);
1143:             titleArea = createAlignedRectangle2D(size, area, 
1144:                     HorizontalAlignment.RIGHT, t.getVerticalAlignment());
1145:             retValue = t.draw(g2, titleArea, p);
1146:             area.setRect(area.getX(), area.getY(), area.getWidth() 
1147:                     - size.width, area.getHeight());
1148:         }
1149: 
1150:         else if (position == RectangleEdge.LEFT) {
1151:             Size2D size = t.arrange(g2, constraint);
1152:             titleArea = createAlignedRectangle2D(size, area, 
1153:                     HorizontalAlignment.LEFT, t.getVerticalAlignment());
1154:             retValue = t.draw(g2, titleArea, p);
1155:             area.setRect(area.getX() + size.width, area.getY(), area.getWidth() 
1156:                     - size.width, area.getHeight());
1157:         }
1158:         else {
1159:             throw new RuntimeException("Unrecognised title position.");
1160:         }
1161:         EntityCollection result = null;
1162:         if (retValue instanceof EntityBlockResult) {
1163:             EntityBlockResult ebr = (EntityBlockResult) retValue;
1164:             result = ebr.getEntityCollection();
1165:         }
1166:         return result;   
1167:     }
1168: 
1169:     /**
1170:      * Creates and returns a buffered image into which the chart has been drawn.
1171:      *
1172:      * @param width  the width.
1173:      * @param height  the height.
1174:      *
1175:      * @return A buffered image.
1176:      */
1177:     public BufferedImage createBufferedImage(int width, int height) {
1178:         return createBufferedImage(width, height, null);
1179:     }
1180: 
1181:     /**
1182:      * Creates and returns a buffered image into which the chart has been drawn.
1183:      *
1184:      * @param width  the width.
1185:      * @param height  the height.
1186:      * @param info  carries back chart state information (<code>null</code> 
1187:      *              permitted).
1188:      *
1189:      * @return A buffered image.
1190:      */
1191:     public BufferedImage createBufferedImage(int width, int height, 
1192:                                              ChartRenderingInfo info) {
1193:         return createBufferedImage(width, height, BufferedImage.TYPE_INT_RGB, 
1194:                 info);
1195:     }
1196: 
1197:     /**
1198:      * Creates and returns a buffered image into which the chart has been drawn.
1199:      *
1200:      * @param width  the width.
1201:      * @param height  the height.
1202:      * @param imageType  the image type.
1203:      * @param info  carries back chart state information (<code>null</code> 
1204:      *              permitted).
1205:      *
1206:      * @return A buffered image.
1207:      */
1208:     public BufferedImage createBufferedImage(int width, int height, 
1209:                                              int imageType, 
1210:                                              ChartRenderingInfo info) {
1211:         BufferedImage image = new BufferedImage(width, height, imageType);
1212:         Graphics2D g2 = image.createGraphics();
1213:         draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info);
1214:         g2.dispose();
1215:         return image;
1216:     }
1217: 
1218:     /**
1219:      * Creates and returns a buffered image into which the chart has been drawn.
1220:      *
1221:      * @param imageWidth  the image width.
1222:      * @param imageHeight  the image height.
1223:      * @param drawWidth  the width for drawing the chart (will be scaled to 
1224:      *                   fit image).
1225:      * @param drawHeight  the height for drawing the chart (will be scaled to 
1226:      *                    fit image).
1227:      * @param info  optional object for collection chart dimension and entity 
1228:      *              information.
1229:      *
1230:      * @return A buffered image.
1231:      */
1232:     public BufferedImage createBufferedImage(int imageWidth, 
1233:                                              int imageHeight,
1234:                                              double drawWidth, 
1235:                                              double drawHeight, 
1236:                                              ChartRenderingInfo info) {
1237: 
1238:         BufferedImage image = new BufferedImage(imageWidth, imageHeight, 
1239:                 BufferedImage.TYPE_INT_RGB);
1240:         Graphics2D g2 = image.createGraphics();
1241:         double scaleX = imageWidth / drawWidth;
1242:         double scaleY = imageHeight / drawHeight;
1243:         AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY);
1244:         g2.transform(st);
1245:         draw(g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null, 
1246:                 info);
1247:         g2.dispose();
1248:         return image;
1249: 
1250:     }
1251: 
1252:     /**
1253:      * Handles a 'click' on the chart.
1254:      * <P>
1255:      * JFreeChart is not a UI component, so some other object (e.g. ChartPanel)
1256:      * needs to capture the click event and pass it onto the JFreeChart object.
1257:      * If you are not using JFreeChart in a client application, then this
1258:      * method is not required (and hopefully it doesn't get in the way).
1259:      *
1260:      * @param x  x-coordinate of the click (in Java2D space).
1261:      * @param y  y-coordinate of the click (in Java2D space).
1262:      * @param info  contains chart dimension and entity information.
1263:      */
1264:     public void handleClick(int x, int y, ChartRenderingInfo info) {
1265: 
1266:         // pass the click on to the plot...
1267:         // rely on the plot to post a plot change event and redraw the chart...
1268:         this.plot.handleClick(x, y, info.getPlotInfo());
1269: 
1270:     }
1271: 
1272:     /**
1273:      * Registers an object for notification of changes to the chart.
1274:      *
1275:      * @param listener  the listener (<code>null</code> not permitted).
1276:      */
1277:     public void addChangeListener(ChartChangeListener listener) {
1278:         if (listener == null) {
1279:             throw new IllegalArgumentException("Null 'listener' argument.");
1280:         }
1281:         this.changeListeners.add(ChartChangeListener.class, listener);
1282:     }
1283: 
1284:     /**
1285:      * Deregisters an object for notification of changes to the chart.
1286:      *
1287:      * @param listener  the listener (<code>null</code> not permitted)
1288:      */
1289:     public void removeChangeListener(ChartChangeListener listener) {
1290:         if (listener == null) {
1291:             throw new IllegalArgumentException("Null 'listener' argument.");
1292:         }
1293:         this.changeListeners.remove(ChartChangeListener.class, listener);
1294:     }
1295: 
1296:     /**
1297:      * Sends a default {@link ChartChangeEvent} to all registered listeners.
1298:      * <P>
1299:      * This method is for convenience only.
1300:      */
1301:     public void fireChartChanged() {
1302:         ChartChangeEvent event = new ChartChangeEvent(this);
1303:         notifyListeners(event);
1304:     }
1305: 
1306:     /**
1307:      * Sends a {@link ChartChangeEvent} to all registered listeners.
1308:      *
1309:      * @param event  information about the event that triggered the 
1310:      *               notification.
1311:      */
1312:     protected void notifyListeners(ChartChangeEvent event) {
1313:         if (this.notify) {
1314:             Object[] listeners = this.changeListeners.getListenerList();
1315:             for (int i = listeners.length - 2; i >= 0; i -= 2) {
1316:                 if (listeners[i] == ChartChangeListener.class) {
1317:                     ((ChartChangeListener) listeners[i + 1]).chartChanged(
1318:                             event);
1319:                 }
1320:             }
1321:         }
1322:     }
1323: 
1324:     /**
1325:      * Registers an object for notification of progress events relating to the 
1326:      * chart.
1327:      *
1328:      * @param listener  the object being registered.
1329:      */
1330:     public void addProgressListener(ChartProgressListener listener) {
1331:         this.progressListeners.add(ChartProgressListener.class, listener);
1332:     }
1333: 
1334:     /**
1335:      * Deregisters an object for notification of changes to the chart.
1336:      *
1337:      * @param listener  the object being deregistered.
1338:      */
1339:     public void removeProgressListener(ChartProgressListener listener) {
1340:         this.progressListeners.remove(ChartProgressListener.class, listener);
1341:     }
1342: 
1343:     /**
1344:      * Sends a {@link ChartProgressEvent} to all registered listeners.
1345:      *
1346:      * @param event  information about the event that triggered the 
1347:      *               notification.
1348:      */
1349:     protected void notifyListeners(ChartProgressEvent event) {
1350: 
1351:         Object[] listeners = this.progressListeners.getListenerList();
1352:         for (int i = listeners.length - 2; i >= 0; i -= 2) {
1353:             if (listeners[i] == ChartProgressListener.class) {
1354:                 ((ChartProgressListener) listeners[i + 1]).chartProgress(event);
1355:             }
1356:         }
1357: 
1358:     }
1359: 
1360:     /**
1361:      * Receives notification that a chart title has changed, and passes this
1362:      * on to registered listeners.
1363:      *
1364:      * @param event  information about the chart title change.
1365:      */
1366:     public void titleChanged(TitleChangeEvent event) {
1367:         event.setChart(this);
1368:         notifyListeners(event);
1369:     }
1370: 
1371:     /**
1372:      * Receives notification that the plot has changed, and passes this on to
1373:      * registered listeners.
1374:      *
1375:      * @param event  information about the plot change.
1376:      */
1377:     public void plotChanged(PlotChangeEvent event) {
1378:         event.setChart(this);
1379:         notifyListeners(event);
1380:     }
1381: 
1382:     /**
1383:      * Tests this chart for equality with another object.
1384:      *
1385:      * @param obj  the object (<code>null</code> permitted).
1386:      *
1387:      * @return A boolean.
1388:      */
1389:     public boolean equals(Object obj) {
1390:         if (obj == this) {
1391:             return true;
1392:         }
1393:         if (!(obj instanceof JFreeChart)) {
1394:             return false;
1395:         }
1396:         JFreeChart that = (JFreeChart) obj;
1397:         if (!this.renderingHints.equals(that.renderingHints)) {
1398:             return false;   
1399:         }
1400:         if (this.borderVisible != that.borderVisible) {
1401:             return false;   
1402:         }
1403:         if (!ObjectUtilities.equal(this.borderStroke, that.borderStroke)) {
1404:             return false;   
1405:         }
1406:         if (!PaintUtilities.equal(this.borderPaint, that.borderPaint)) {
1407:             return false;   
1408:         }
1409:         if (!this.padding.equals(that.padding)) {
1410:             return false;   
1411:         }
1412:         if (!ObjectUtilities.equal(this.title, that.title)) {
1413:             return false;
1414:         }
1415:         if (!ObjectUtilities.equal(this.subtitles, that.subtitles)) {
1416:             return false;
1417:         }
1418:         if (!ObjectUtilities.equal(this.plot, that.plot)) {
1419:             return false;
1420:         }
1421:         if (!PaintUtilities.equal(
1422:             this.backgroundPaint, that.backgroundPaint
1423:         )) {
1424:             return false;
1425:         }
1426:         if (!ObjectUtilities.equal(this.backgroundImage, 
1427:                 that.backgroundImage)) {
1428:             return false;
1429:         }
1430:         if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1431:             return false;
1432:         }
1433:         if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1434:             return false;
1435:         }
1436:         if (this.notify != that.notify) {
1437:             return false;
1438:         }
1439:         return true;
1440:     }
1441: 
1442:     /**
1443:      * Provides serialization support.
1444:      *
1445:      * @param stream  the output stream.
1446:      *
1447:      * @throws IOException  if there is an I/O error.
1448:      */
1449:     private void writeObject(ObjectOutputStream stream) throws IOException {
1450:         stream.defaultWriteObject();
1451:         SerialUtilities.writeStroke(this.borderStroke, stream);
1452:         SerialUtilities.writePaint(this.borderPaint, stream);
1453:         SerialUtilities.writePaint(this.backgroundPaint, stream);
1454:     }
1455: 
1456:     /**
1457:      * Provides serialization support.
1458:      *
1459:      * @param stream  the input stream.
1460:      *
1461:      * @throws IOException  if there is an I/O error.
1462:      * @throws ClassNotFoundException  if there is a classpath problem.
1463:      */
1464:     private void readObject(ObjectInputStream stream) 
1465:         throws IOException, ClassNotFoundException {
1466:         stream.defaultReadObject();
1467:         this.borderStroke = SerialUtilities.readStroke(stream);
1468:         this.borderPaint = SerialUtilities.readPaint(stream);
1469:         this.backgroundPaint = SerialUtilities.readPaint(stream);
1470:         this.progressListeners = new EventListenerList();
1471:         this.changeListeners = new EventListenerList();
1472:         this.renderingHints = new RenderingHints(
1473:                 RenderingHints.KEY_ANTIALIASING, 
1474:                 RenderingHints.VALUE_ANTIALIAS_ON);
1475: 
1476:         // register as a listener with sub-components...
1477:         if (this.title != null) {
1478:             this.title.addChangeListener(this);
1479:         }
1480: 
1481:         for (int i = 0; i < getSubtitleCount(); i++) {
1482:             getSubtitle(i).addChangeListener(this);
1483:         }
1484:         this.plot.addChangeListener(this);
1485:     }
1486: 
1487:     /**
1488:      * Prints information about JFreeChart to standard output.
1489:      *
1490:      * @param args  no arguments are honored.
1491:      */
1492:     public static void main(String[] args) {
1493:         System.out.println(JFreeChart.INFO.toString());
1494:     }
1495: 
1496:     /**
1497:      * Clones the object, and takes care of listeners.
1498:      * Note: caller shall register its own listeners on cloned graph.
1499:      * 
1500:      * @return A clone.
1501:      * 
1502:      * @throws CloneNotSupportedException if the chart is not cloneable.
1503:      */
1504:     public Object clone() throws CloneNotSupportedException {
1505:         JFreeChart chart = (JFreeChart) super.clone();
1506: 
1507:         chart.renderingHints = (RenderingHints) this.renderingHints.clone();
1508:         // private boolean borderVisible;
1509:         // private transient Stroke borderStroke;
1510:         // private transient Paint borderPaint;
1511: 
1512:         if (this.title != null) {
1513:             chart.title = (TextTitle) this.title.clone();
1514:             chart.title.addChangeListener(chart);
1515:         }
1516: 
1517:         chart.subtitles = new ArrayList();
1518:         for (int i = 0; i < getSubtitleCount(); i++) {
1519:             Title subtitle = (Title) getSubtitle(i).clone();
1520:             chart.subtitles.add(subtitle);
1521:             subtitle.addChangeListener(chart);
1522:         }
1523: 
1524:         if (this.plot != null) {
1525:             chart.plot = (Plot) this.plot.clone();
1526:             chart.plot.addChangeListener(chart);
1527:         }
1528: 
1529:         chart.progressListeners = new EventListenerList();
1530:         chart.changeListeners = new EventListenerList();
1531:         return chart;
1532:     }
1533: 
1534: }
1535: 
1536: /**
1537:  * Information about the JFreeChart project.  One instance of this class is 
1538:  * assigned to <code>JFreeChart.INFO<code>.
1539:  */
1540: class JFreeChartInfo extends ProjectInfo {
1541: 
1542:     /** 
1543:      * Default constructor. 
1544:      */
1545:     public JFreeChartInfo() {
1546: 
1547:         // get a locale-specific resource bundle...
1548:         String baseResourceClass 
1549:             = "org.jfree.chart.resources.JFreeChartResources";
1550:         ResourceBundle resources = ResourceBundle.getBundle(baseResourceClass);
1551: 
1552:         setName(resources.getString("project.name"));
1553:         setVersion(resources.getString("project.version"));
1554:         setInfo(resources.getString("project.info"));
1555:         setCopyright(resources.getString("project.copyright"));
1556:         setLogo(null);  // load only when required
1557:         setLicenceName("LGPL");
1558:         setLicenceText(Licences.getInstance().getLGPL());
1559: 
1560:         setContributors(Arrays.asList(
1561:             new Contributor[]{
1562:                 new Contributor("Eric Alexander", "-"),
1563:                 new Contributor(
1564:                     "Richard Atkinson", "richard_c_atkinson@ntlworld.com"
1565:                 ),
1566:                 new Contributor("David Basten", "-"),
1567:                 new Contributor("David Berry", "-"),
1568:                 new Contributor("Anthony Boulestreau", "-"),
1569:                 new Contributor("Jeremy Bowman", "-"),
1570:                 new Contributor("Nicolas Brodu", "-"),
1571:                 new Contributor("David Browning", "-"),
1572:                 new Contributor("S???ren Caspersen", "-"),
1573:                 new Contributor("Chuanhao Chiu", "-"),
1574:                 new Contributor("Brian Cole", "-"),
1575:                 new Contributor("Pascal Collet", "-"),
1576:                 new Contributor("Martin Cordova", "-"),
1577:                 new Contributor("Paolo Cova", "-"),
1578:                 new Contributor("Mike Duffy", "-"),
1579:                 new Contributor("Don Elliott", "-"),
1580:                 new Contributor("Jonathan Gabbai", "-"),
1581:                 new Contributor("David Gilbert", 
1582:                         "david.gilbert@object-refinery.com"),
1583:                 new Contributor("Serge V. Grachov", "-"),
1584:                 new Contributor("Daniel Gredler", "-"),
1585:                 new Contributor("Hans-Jurgen Greiner", "-"),
1586:                 new Contributor("Joao Guilherme Del Valle", "-"),
1587:                 new Contributor("Aiman Han", "-"),
1588:                 new Contributor("Cameron Hayne", "-"),
1589:                 new Contributor("Jon Iles", "-"),
1590:                 new Contributor("Wolfgang Irler", "-"),
1591:                 new Contributor("Xun Kang", "-"),
1592:                 new Contributor("Bill Kelemen", "-"),
1593:                 new Contributor("Norbert Kiesel", "-"),
1594:                 new Contributor("Gideon Krause", "-"),
1595:                 new Contributor("Pierre-Marie Le Biot", "-"),
1596:                 new Contributor("Arnaud Lelievre", "-"),
1597:                 new Contributor("Wolfgang Lenhard", "-"),
1598:                 new Contributor("David Li", "-"),
1599:                 new Contributor("Yan Liu", "-"),
1600:                 new Contributor("Tin Luu", "-"),
1601:                 new Contributor("Craig MacFarlane", "-"),
1602:                 new Contributor("Achilleus Mantzios", "-"),
1603:                 new Contributor("Thomas Meier", "-"),
1604:                 new Contributor("Jim Moore", "-"),
1605:                 new Contributor("Jonathan Nash", "-"),
1606:                 new Contributor("Barak Naveh", "-"),
1607:                 new Contributor("David M. O'Donnell", "-"),
1608:                 new Contributor("Krzysztof Paz", "-"),
1609:                 new Contributor("Tomer Peretz", "-"),
1610:                 new Contributor("Andrzej Porebski", "-"),
1611:                 new Contributor("Xavier Poinsard", "-"),
1612:                 new Contributor("Viktor Rajewski", "-"),
1613:                 new Contributor("Eduardo Ramalho", "-"),
1614:                 new Contributor("Michael Rauch", "-"),
1615:                 new Contributor("Cameron Riley", "-"),
1616:                 new Contributor("Dan Rivett", "d.rivett@ukonline.co.uk"),
1617:                 new Contributor("Scott Sams", "-"),
1618:                 new Contributor("Michel Santos", "-"),
1619:                 new Contributor("Thierry Saura", "-"),
1620:                 new Contributor("Andreas Schneider", "-"),
1621:                 new Contributor("Jean-Luc SCHWAB", "-"),
1622:                 new Contributor("Bryan Scott", "-"),
1623:                 new Contributor("Tobias Selb", "-"),
1624:                 new Contributor("Mofeed Shahin", "-"),
1625:                 new Contributor("Pady Srinivasan", "-"),
1626:                 new Contributor("Greg Steckman", "-"),
1627:                 new Contributor("Roger Studner", "-"),
1628:                 new Contributor("Irv Thomae", "-"),
1629:                 new Contributor("Eric Thomas", "-"),
1630:                 new Contributor("Rich Unger", "-"),
1631:                 new Contributor("Daniel van Enckevort", "-"),
1632:                 new Contributor("Laurence Vanhelsuwe", "-"),
1633:                 new Contributor("Sylvain Vieujot", "-"),
1634:                 new Contributor("Mark Watson", "www.markwatson.com"),
1635:                 new Contributor("Alex Weber", "-"),
1636:                 new Contributor("Matthew Wright", "-"),
1637:                 new Contributor("Benoit Xhenseval", "-"),
1638:                 new Contributor("Christian W. Zuckschwerdt", 
1639:                         "Christian.Zuckschwerdt@Informatik.Uni-Oldenburg.de"),
1640:                 new Contributor("Hari", "-"),
1641:                 new Contributor("Sam (oldman)", "-"),
1642:             }
1643:         ));
1644: 
1645:         addLibrary(JCommon.INFO);
1646: 
1647:     }
1648: 
1649:     /**
1650:      * Returns the JFreeChart logo (a picture of a gorilla).
1651:      *
1652:      * @return The JFreeChart logo.
1653:      */
1654:     public Image getLogo() {
1655: 
1656:         Image logo = super.getLogo();
1657:         if (logo == null) {
1658:             URL imageURL = this.getClass().getClassLoader().getResource(
1659:                     "org/jfree/chart/gorilla.jpg");
1660:             if (imageURL != null) {
1661:                 ImageIcon temp = new ImageIcon(imageURL);  
1662:                     // use ImageIcon because it waits for the image to load...
1663:                 logo = temp.getImage();
1664:                 setLogo(logo);
1665:             }
1666:         }
1667:         return logo;
1668: 
1669:     }
1670: 
1671: }