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