Source for org.jfree.chart.JFreeChart

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