Source for org.jfree.chart.JFreeChart

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