Source for org.jfree.chart.ChartPanel

   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:  * ChartPanel.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:  *                   Soren Caspersen;
  35:  *                   Jonathan Nash;
  36:  *                   Hans-Jurgen Greiner;
  37:  *                   Andreas Schneider;
  38:  *                   Daniel van Enckevort;
  39:  *                   David M O'Donnell;
  40:  *                   Arnaud Lelievre;
  41:  *                   Matthias Rose;
  42:  *                   Onno vd Akker;
  43:  *                   Sergei Ivanov;
  44:  *
  45:  * $Id: ChartPanel.java,v 1.20.2.12 2007/03/05 15:56:33 mungady Exp $
  46:  *
  47:  * Changes (from 28-Jun-2001)
  48:  * --------------------------
  49:  * 28-Jun-2001 : Integrated buffering code contributed by S???ren 
  50:  *               Caspersen (DG);
  51:  * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
  52:  * 22-Nov-2001 : Added scaling to improve display of charts in small sizes (DG);
  53:  * 26-Nov-2001 : Added property editing, saving and printing (DG);
  54:  * 11-Dec-2001 : Transferred saveChartAsPNG method to new ChartUtilities 
  55:  *               class (DG);
  56:  * 13-Dec-2001 : Added tooltips (DG);
  57:  * 16-Jan-2002 : Added an optional crosshair, based on the implementation by 
  58:  *               Jonathan Nash. Renamed the tooltips class (DG);
  59:  * 23-Jan-2002 : Implemented zooming based on code by Hans-Jurgen Greiner (DG);
  60:  * 05-Feb-2002 : Improved tooltips setup.  Renamed method attemptSaveAs() 
  61:  *               --> doSaveAs() and made it public rather than private (DG);
  62:  * 28-Mar-2002 : Added a new constructor (DG);
  63:  * 09-Apr-2002 : Changed initialisation of tooltip generation, as suggested by 
  64:  *               Hans-Jurgen Greiner (DG);
  65:  * 27-May-2002 : New interactive zooming methods based on code by Hans-Jurgen 
  66:  *               Greiner. Renamed JFreeChartPanel --> ChartPanel, moved 
  67:  *               constants to ChartPanelConstants interface (DG);
  68:  * 31-May-2002 : Fixed a bug with interactive zooming and added a way to 
  69:  *               control if the zoom rectangle is filled in or drawn as an 
  70:  *               outline. A mouse drag gesture towards the top left now causes 
  71:  *               an autoRangeBoth() and is a way to undo zooms (AS);
  72:  * 11-Jun-2002 : Reinstated handleClick method call in mouseClicked() to get 
  73:  *               crosshairs working again (DG);
  74:  * 13-Jun-2002 : Added check for null popup menu in mouseDragged method (DG);
  75:  * 18-Jun-2002 : Added get/set methods for minimum and maximum chart 
  76:  *               dimensions (DG);
  77:  * 25-Jun-2002 : Removed redundant code (DG);
  78:  * 27-Aug-2002 : Added get/set methods for popup menu (DG);
  79:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  80:  * 22-Oct-2002 : Added translation methods for screen <--> Java2D, contributed
  81:  *               by Daniel van Enckevort (DG);
  82:  * 05-Nov-2002 : Added a chart reference to the ChartMouseEvent class (DG);
  83:  * 22-Nov-2002 : Added test in zoom method for inverted axes, supplied by 
  84:  *               David M O'Donnell (DG);
  85:  * 14-Jan-2003 : Implemented ChartProgressListener interface (DG);
  86:  * 14-Feb-2003 : Removed deprecated setGenerateTooltips method (DG);
  87:  * 12-Mar-2003 : Added option to enforce filename extension (see bug id 
  88:  *               643173) (DG);
  89:  * 08-Sep-2003 : Added internationalization via use of properties 
  90:  *               resourceBundle (RFE 690236) (AL);
  91:  * 18-Sep-2003 : Added getScaleX() and getScaleY() methods (protected) as 
  92:  *               requested by Irv Thomae (DG);
  93:  * 12-Nov-2003 : Added zooming support for the FastScatterPlot class (DG);
  94:  * 24-Nov-2003 : Minor Javadoc updates (DG);
  95:  * 04-Dec-2003 : Added anchor point for crosshair calculation (DG);
  96:  * 17-Jan-2004 : Added new methods to set tooltip delays to be used in this 
  97:  *               chart panel. Refer to patch 877565 (MR);
  98:  * 02-Feb-2004 : Fixed bug in zooming trigger and added zoomTriggerDistance 
  99:  *               attribute (DG);
 100:  * 08-Apr-2004 : Changed getScaleX() and getScaleY() from protected to 
 101:  *               public (DG);
 102:  * 15-Apr-2004 : Added zoomOutFactor and zoomInFactor (DG);
 103:  * 21-Apr-2004 : Fixed zooming bug in mouseReleased() method (DG);
 104:  * 13-Jul-2004 : Added check for null chart (DG);
 105:  * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG); 
 106:  * 11-Nov-2004 : Moved constants back in from ChartPanelConstants (DG);
 107:  * 12-Nov-2004 : Modified zooming mechanism to support zooming within 
 108:  *               subplots (DG);
 109:  * 26-Jan-2005 : Fixed mouse zooming for horizontal category plots (DG);
 110:  * 11-Apr-2005 : Added getFillZoomRectangle() method, renamed 
 111:  *               setHorizontalZoom() --> setDomainZoomable(), 
 112:  *               setVerticalZoom() --> setRangeZoomable(), added 
 113:  *               isDomainZoomable() and isRangeZoomable(), added 
 114:  *               getHorizontalAxisTrace() and getVerticalAxisTrace(),
 115:  *               renamed autoRangeBoth() --> restoreAutoBounds(),
 116:  *               autoRangeHorizontal() --> restoreAutoDomainBounds(),
 117:  *               autoRangeVertical() --> restoreAutoRangeBounds() (DG);
 118:  * 12-Apr-2005 : Removed working areas, added getAnchorPoint() method,
 119:  *               added protected accessors for tracelines (DG);
 120:  * 18-Apr-2005 : Made constants final (DG);
 121:  * 26-Apr-2005 : Removed LOGGER (DG);
 122:  * 01-Jun-2005 : Fixed zooming for combined plots - see bug report 
 123:  *               1212039, fix thanks to Onno vd Akker (DG);
 124:  * 25-Nov-2005 : Reworked event listener mechanism (DG);
 125:  * ------------- JFREECHART 1.0.x ---------------------------------------------
 126:  * 01-Aug-2006 : Fixed minor bug in restoreAutoRangeBounds() (DG);
 127:  * 04-Sep-2006 : Renamed attemptEditChartProperties() --> 
 128:  *               doEditChartProperties() and made public (DG);
 129:  * 13-Sep-2006 : Don't generate ChartMouseEvents if the panel's chart is null
 130:  *               (fixes bug 1556951) (DG);
 131:  * 05-Mar-2007 : Applied patch 1672561 by Sergei Ivanov, to fix zoom rectangle
 132:  *               drawing for dynamic charts (DG);
 133:  *               
 134:  */
 135: 
 136: package org.jfree.chart;
 137: 
 138: import java.awt.AWTEvent;
 139: import java.awt.Color;
 140: import java.awt.Dimension;
 141: import java.awt.Graphics;
 142: import java.awt.Graphics2D;
 143: import java.awt.Image;
 144: import java.awt.Insets;
 145: import java.awt.Point;
 146: import java.awt.event.ActionEvent;
 147: import java.awt.event.ActionListener;
 148: import java.awt.event.MouseEvent;
 149: import java.awt.event.MouseListener;
 150: import java.awt.event.MouseMotionListener;
 151: import java.awt.geom.AffineTransform;
 152: import java.awt.geom.Line2D;
 153: import java.awt.geom.Point2D;
 154: import java.awt.geom.Rectangle2D;
 155: import java.awt.print.PageFormat;
 156: import java.awt.print.Printable;
 157: import java.awt.print.PrinterException;
 158: import java.awt.print.PrinterJob;
 159: import java.io.File;
 160: import java.io.IOException;
 161: import java.io.Serializable;
 162: import java.util.EventListener;
 163: import java.util.ResourceBundle;
 164: 
 165: import javax.swing.JFileChooser;
 166: import javax.swing.JMenu;
 167: import javax.swing.JMenuItem;
 168: import javax.swing.JOptionPane;
 169: import javax.swing.JPanel;
 170: import javax.swing.JPopupMenu;
 171: import javax.swing.ToolTipManager;
 172: import javax.swing.event.EventListenerList;
 173: 
 174: import org.jfree.chart.editor.ChartEditor;
 175: import org.jfree.chart.editor.ChartEditorManager;
 176: import org.jfree.chart.entity.ChartEntity;
 177: import org.jfree.chart.entity.EntityCollection;
 178: import org.jfree.chart.event.ChartChangeEvent;
 179: import org.jfree.chart.event.ChartChangeListener;
 180: import org.jfree.chart.event.ChartProgressEvent;
 181: import org.jfree.chart.event.ChartProgressListener;
 182: import org.jfree.chart.plot.Plot;
 183: import org.jfree.chart.plot.PlotOrientation;
 184: import org.jfree.chart.plot.PlotRenderingInfo;
 185: import org.jfree.chart.plot.Zoomable;
 186: import org.jfree.ui.ExtensionFileFilter;
 187: 
 188: /**
 189:  * A Swing GUI component for displaying a {@link JFreeChart} object.
 190:  * <P>
 191:  * The panel registers with the chart to receive notification of changes to any
 192:  * component of the chart.  The chart is redrawn automatically whenever this 
 193:  * notification is received.
 194:  */
 195: public class ChartPanel extends JPanel 
 196:                         implements ChartChangeListener,
 197:                                    ChartProgressListener,
 198:                                    ActionListener,
 199:                                    MouseListener,
 200:                                    MouseMotionListener,
 201:                                    Printable,
 202:                                    Serializable {
 203: 
 204:     /** For serialization. */
 205:     private static final long serialVersionUID = 6046366297214274674L;
 206:     
 207:     /** Default setting for buffer usage. */
 208:     public static final boolean DEFAULT_BUFFER_USED = false;
 209: 
 210:     /** The default panel width. */
 211:     public static final int DEFAULT_WIDTH = 680;
 212: 
 213:     /** The default panel height. */
 214:     public static final int DEFAULT_HEIGHT = 420;
 215: 
 216:     /** The default limit below which chart scaling kicks in. */
 217:     public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300;
 218: 
 219:     /** The default limit below which chart scaling kicks in. */
 220:     public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200;
 221: 
 222:     /** The default limit below which chart scaling kicks in. */
 223:     public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 800;
 224: 
 225:     /** The default limit below which chart scaling kicks in. */
 226:     public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 600;
 227: 
 228:     /** The minimum size required to perform a zoom on a rectangle */
 229:     public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
 230: 
 231:     /** Properties action command. */
 232:     public static final String PROPERTIES_COMMAND = "PROPERTIES";
 233: 
 234:     /** Save action command. */
 235:     public static final String SAVE_COMMAND = "SAVE";
 236: 
 237:     /** Print action command. */
 238:     public static final String PRINT_COMMAND = "PRINT";
 239: 
 240:     /** Zoom in (both axes) action command. */
 241:     public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH";
 242: 
 243:     /** Zoom in (domain axis only) action command. */
 244:     public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN";
 245: 
 246:     /** Zoom in (range axis only) action command. */
 247:     public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE";
 248: 
 249:     /** Zoom out (both axes) action command. */
 250:     public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH";
 251: 
 252:     /** Zoom out (domain axis only) action command. */
 253:     public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH";
 254: 
 255:     /** Zoom out (range axis only) action command. */
 256:     public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH";
 257: 
 258:     /** Zoom reset (both axes) action command. */
 259:     public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH";
 260: 
 261:     /** Zoom reset (domain axis only) action command. */
 262:     public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN";
 263: 
 264:     /** Zoom reset (range axis only) action command. */
 265:     public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE";
 266: 
 267:     /** The chart that is displayed in the panel. */
 268:     private JFreeChart chart;
 269: 
 270:     /** Storage for registered (chart) mouse listeners. */
 271:     private EventListenerList chartMouseListeners;
 272: 
 273:     /** A flag that controls whether or not the off-screen buffer is used. */
 274:     private boolean useBuffer;
 275: 
 276:     /** A flag that indicates that the buffer should be refreshed. */
 277:     private boolean refreshBuffer;
 278: 
 279:     /** A buffer for the rendered chart. */
 280:     private Image chartBuffer;
 281: 
 282:     /** The height of the chart buffer. */
 283:     private int chartBufferHeight;
 284: 
 285:     /** The width of the chart buffer. */
 286:     private int chartBufferWidth;
 287: 
 288:     /** 
 289:      * The minimum width for drawing a chart (uses scaling for smaller widths). 
 290:      */
 291:     private int minimumDrawWidth;
 292: 
 293:     /** 
 294:      * The minimum height for drawing a chart (uses scaling for smaller 
 295:      * heights). 
 296:      */
 297:     private int minimumDrawHeight;
 298: 
 299:     /** 
 300:      * The maximum width for drawing a chart (uses scaling for bigger 
 301:      * widths). 
 302:      */
 303:     private int maximumDrawWidth;
 304: 
 305:     /** 
 306:      * The maximum height for drawing a chart (uses scaling for bigger 
 307:      * heights). 
 308:      */
 309:     private int maximumDrawHeight;
 310: 
 311:     /** The popup menu for the frame. */
 312:     private JPopupMenu popup;
 313: 
 314:     /** The drawing info collected the last time the chart was drawn. */
 315:     private ChartRenderingInfo info;
 316:     
 317:     /** The chart anchor point. */
 318:     private Point2D anchor;
 319: 
 320:     /** The scale factor used to draw the chart. */
 321:     private double scaleX;
 322: 
 323:     /** The scale factor used to draw the chart. */
 324:     private double scaleY;
 325: 
 326:     /** The plot orientation. */
 327:     private PlotOrientation orientation = PlotOrientation.VERTICAL;
 328:     
 329:     /** A flag that controls whether or not domain zooming is enabled. */
 330:     private boolean domainZoomable = false;
 331: 
 332:     /** A flag that controls whether or not range zooming is enabled. */
 333:     private boolean rangeZoomable = false;
 334: 
 335:     /** 
 336:      * The zoom rectangle starting point (selected by the user with a mouse 
 337:      * click).  This is a point on the screen, not the chart (which may have
 338:      * been scaled up or down to fit the panel).  
 339:      */
 340:     private Point zoomPoint = null;
 341: 
 342:     /** The zoom rectangle (selected by the user with the mouse). */
 343:     private transient Rectangle2D zoomRectangle = null;
 344: 
 345:     /** Controls if the zoom rectangle is drawn as an outline or filled. */
 346:     private boolean fillZoomRectangle = false;
 347: 
 348:     /** The minimum distance required to drag the mouse to trigger a zoom. */
 349:     private int zoomTriggerDistance;
 350:     
 351:     /** A flag that controls whether or not horizontal tracing is enabled. */
 352:     private boolean horizontalAxisTrace = false;
 353: 
 354:     /** A flag that controls whether or not vertical tracing is enabled. */
 355:     private boolean verticalAxisTrace = false;
 356: 
 357:     /** A vertical trace line. */
 358:     private transient Line2D verticalTraceLine;
 359: 
 360:     /** A horizontal trace line. */
 361:     private transient Line2D horizontalTraceLine;
 362: 
 363:     /** Menu item for zooming in on a chart (both axes). */
 364:     private JMenuItem zoomInBothMenuItem;
 365: 
 366:     /** Menu item for zooming in on a chart (domain axis). */
 367:     private JMenuItem zoomInDomainMenuItem;
 368: 
 369:     /** Menu item for zooming in on a chart (range axis). */
 370:     private JMenuItem zoomInRangeMenuItem;
 371: 
 372:     /** Menu item for zooming out on a chart. */
 373:     private JMenuItem zoomOutBothMenuItem;
 374: 
 375:     /** Menu item for zooming out on a chart (domain axis). */
 376:     private JMenuItem zoomOutDomainMenuItem;
 377: 
 378:     /** Menu item for zooming out on a chart (range axis). */
 379:     private JMenuItem zoomOutRangeMenuItem;
 380: 
 381:     /** Menu item for resetting the zoom (both axes). */
 382:     private JMenuItem zoomResetBothMenuItem;
 383: 
 384:     /** Menu item for resetting the zoom (domain axis only). */
 385:     private JMenuItem zoomResetDomainMenuItem;
 386: 
 387:     /** Menu item for resetting the zoom (range axis only). */
 388:     private JMenuItem zoomResetRangeMenuItem;
 389: 
 390:     /** A flag that controls whether or not file extensions are enforced. */
 391:     private boolean enforceFileExtensions;
 392: 
 393:     /** A flag that indicates if original tooltip delays are changed. */
 394:     private boolean ownToolTipDelaysActive;  
 395:     
 396:     /** Original initial tooltip delay of ToolTipManager.sharedInstance(). */
 397:     private int originalToolTipInitialDelay;
 398: 
 399:     /** Original reshow tooltip delay of ToolTipManager.sharedInstance(). */
 400:     private int originalToolTipReshowDelay;  
 401: 
 402:     /** Original dismiss tooltip delay of ToolTipManager.sharedInstance(). */
 403:     private int originalToolTipDismissDelay;
 404: 
 405:     /** Own initial tooltip delay to be used in this chart panel. */
 406:     private int ownToolTipInitialDelay;
 407:     
 408:     /** Own reshow tooltip delay to be used in this chart panel. */
 409:     private int ownToolTipReshowDelay;  
 410: 
 411:     /** Own dismiss tooltip delay to be used in this chart panel. */
 412:     private int ownToolTipDismissDelay;    
 413: 
 414:     /** The factor used to zoom in on an axis range. */
 415:     private double zoomInFactor = 0.5;
 416:     
 417:     /** The factor used to zoom out on an axis range. */
 418:     private double zoomOutFactor = 2.0;
 419:     
 420:     /** The resourceBundle for the localization. */
 421:     protected static ResourceBundle localizationResources 
 422:         = ResourceBundle.getBundle("org.jfree.chart.LocalizationBundle");
 423: 
 424:     /**
 425:      * Constructs a panel that displays the specified chart.
 426:      *
 427:      * @param chart  the chart.
 428:      */
 429:     public ChartPanel(JFreeChart chart) {
 430: 
 431:         this(
 432:             chart,
 433:             DEFAULT_WIDTH,
 434:             DEFAULT_HEIGHT,
 435:             DEFAULT_MINIMUM_DRAW_WIDTH,
 436:             DEFAULT_MINIMUM_DRAW_HEIGHT,
 437:             DEFAULT_MAXIMUM_DRAW_WIDTH,
 438:             DEFAULT_MAXIMUM_DRAW_HEIGHT,
 439:             DEFAULT_BUFFER_USED,
 440:             true,  // properties
 441:             true,  // save
 442:             true,  // print
 443:             true,  // zoom
 444:             true   // tooltips
 445:         );
 446: 
 447:     }
 448: 
 449:     /**
 450:      * Constructs a panel containing a chart.
 451:      *
 452:      * @param chart  the chart.
 453:      * @param useBuffer  a flag controlling whether or not an off-screen buffer
 454:      *                   is used.
 455:      */
 456:     public ChartPanel(JFreeChart chart, boolean useBuffer) {
 457: 
 458:         this(chart,
 459:              DEFAULT_WIDTH,
 460:              DEFAULT_HEIGHT,
 461:              DEFAULT_MINIMUM_DRAW_WIDTH,
 462:              DEFAULT_MINIMUM_DRAW_HEIGHT,
 463:              DEFAULT_MAXIMUM_DRAW_WIDTH,
 464:              DEFAULT_MAXIMUM_DRAW_HEIGHT,
 465:              useBuffer,
 466:              true,  // properties
 467:              true,  // save
 468:              true,  // print
 469:              true,  // zoom
 470:              true   // tooltips
 471:              );
 472: 
 473:     }
 474: 
 475:     /**
 476:      * Constructs a JFreeChart panel.
 477:      *
 478:      * @param chart  the chart.
 479:      * @param properties  a flag indicating whether or not the chart property
 480:      *                    editor should be available via the popup menu.
 481:      * @param save  a flag indicating whether or not save options should be
 482:      *              available via the popup menu.
 483:      * @param print  a flag indicating whether or not the print option
 484:      *               should be available via the popup menu.
 485:      * @param zoom  a flag indicating whether or not zoom options should
 486:      *              be added to the popup menu.
 487:      * @param tooltips  a flag indicating whether or not tooltips should be
 488:      *                  enabled for the chart.
 489:      */
 490:     public ChartPanel(JFreeChart chart,
 491:                       boolean properties,
 492:                       boolean save,
 493:                       boolean print,
 494:                       boolean zoom,
 495:                       boolean tooltips) {
 496: 
 497:         this(chart,
 498:              DEFAULT_WIDTH,
 499:              DEFAULT_HEIGHT,
 500:              DEFAULT_MINIMUM_DRAW_WIDTH,
 501:              DEFAULT_MINIMUM_DRAW_HEIGHT,
 502:              DEFAULT_MAXIMUM_DRAW_WIDTH,
 503:              DEFAULT_MAXIMUM_DRAW_HEIGHT,
 504:              DEFAULT_BUFFER_USED,
 505:              properties,
 506:              save,
 507:              print,
 508:              zoom,
 509:              tooltips
 510:              );
 511: 
 512:     }
 513: 
 514:     /**
 515:      * Constructs a JFreeChart panel.
 516:      *
 517:      * @param chart  the chart.
 518:      * @param width  the preferred width of the panel.
 519:      * @param height  the preferred height of the panel.
 520:      * @param minimumDrawWidth  the minimum drawing width.
 521:      * @param minimumDrawHeight  the minimum drawing height.
 522:      * @param maximumDrawWidth  the maximum drawing width.
 523:      * @param maximumDrawHeight  the maximum drawing height.
 524:      * @param useBuffer  a flag that indicates whether to use the off-screen
 525:      *                   buffer to improve performance (at the expense of 
 526:      *                   memory).
 527:      * @param properties  a flag indicating whether or not the chart property
 528:      *                    editor should be available via the popup menu.
 529:      * @param save  a flag indicating whether or not save options should be
 530:      *              available via the popup menu.
 531:      * @param print  a flag indicating whether or not the print option
 532:      *               should be available via the popup menu.
 533:      * @param zoom  a flag indicating whether or not zoom options should be 
 534:      *              added to the popup menu.
 535:      * @param tooltips  a flag indicating whether or not tooltips should be 
 536:      *                  enabled for the chart.
 537:      */
 538:     public ChartPanel(JFreeChart chart,
 539:                       int width,
 540:                       int height,
 541:                       int minimumDrawWidth,
 542:                       int minimumDrawHeight,
 543:                       int maximumDrawWidth,
 544:                       int maximumDrawHeight,
 545:                       boolean useBuffer,
 546:                       boolean properties,
 547:                       boolean save,
 548:                       boolean print,
 549:                       boolean zoom,
 550:                       boolean tooltips) {
 551: 
 552:         this.setChart(chart);
 553:         this.chartMouseListeners = new EventListenerList();
 554:         this.info = new ChartRenderingInfo();
 555:         setPreferredSize(new Dimension(width, height));
 556:         this.useBuffer = useBuffer;
 557:         this.refreshBuffer = false;
 558:         this.minimumDrawWidth = minimumDrawWidth;
 559:         this.minimumDrawHeight = minimumDrawHeight;
 560:         this.maximumDrawWidth = maximumDrawWidth;
 561:         this.maximumDrawHeight = maximumDrawHeight;
 562:         this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
 563: 
 564:         // set up popup menu...
 565:         this.popup = null;
 566:         if (properties || save || print || zoom) {
 567:             this.popup = createPopupMenu(properties, save, print, zoom);
 568:         }
 569: 
 570:         enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 571:         enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
 572:         setDisplayToolTips(tooltips);
 573:         addMouseListener(this);
 574:         addMouseMotionListener(this);
 575: 
 576:         this.enforceFileExtensions = true;
 577: 
 578:         // initialize ChartPanel-specific tool tip delays with
 579:         // values the from ToolTipManager.sharedInstance()
 580:         ToolTipManager ttm = ToolTipManager.sharedInstance();       
 581:         this.ownToolTipInitialDelay = ttm.getInitialDelay();
 582:         this.ownToolTipDismissDelay = ttm.getDismissDelay();
 583:         this.ownToolTipReshowDelay = ttm.getReshowDelay();
 584: 
 585:     }
 586: 
 587:     /**
 588:      * Returns the chart contained in the panel.
 589:      *
 590:      * @return The chart (possibly <code>null</code>).
 591:      */
 592:     public JFreeChart getChart() {
 593:         return this.chart;
 594:     }
 595: 
 596:     /**
 597:      * Sets the chart that is displayed in the panel.
 598:      *
 599:      * @param chart  the chart (<code>null</code> permitted).
 600:      */
 601:     public void setChart(JFreeChart chart) {
 602: 
 603:         // stop listening for changes to the existing chart
 604:         if (this.chart != null) {
 605:             this.chart.removeChangeListener(this);
 606:             this.chart.removeProgressListener(this);
 607:         }
 608: 
 609:         // add the new chart
 610:         this.chart = chart;
 611:         if (chart != null) {
 612:             this.chart.addChangeListener(this);
 613:             this.chart.addProgressListener(this);
 614:             Plot plot = chart.getPlot();
 615:             this.domainZoomable = false;
 616:             this.rangeZoomable = false;
 617:             if (plot instanceof Zoomable) {
 618:                 Zoomable z = (Zoomable) plot;
 619:                 this.domainZoomable = z.isDomainZoomable();
 620:                 this.rangeZoomable = z.isRangeZoomable();
 621:                 this.orientation = z.getOrientation();
 622:             }
 623:         }
 624:         else {
 625:             this.domainZoomable = false;
 626:             this.rangeZoomable = false;
 627:         }
 628:         if (this.useBuffer) {
 629:             this.refreshBuffer = true;
 630:         }
 631:         repaint();
 632: 
 633:     }
 634: 
 635:     /**
 636:      * Returns the minimum drawing width for charts.
 637:      * <P>
 638:      * If the width available on the panel is less than this, then the chart is
 639:      * drawn at the minimum width then scaled down to fit.
 640:      *
 641:      * @return The minimum drawing width.
 642:      */
 643:     public int getMinimumDrawWidth() {
 644:         return this.minimumDrawWidth;
 645:     }
 646: 
 647:     /**
 648:      * Sets the minimum drawing width for the chart on this panel.
 649:      * <P>
 650:      * At the time the chart is drawn on the panel, if the available width is
 651:      * less than this amount, the chart will be drawn using the minimum width
 652:      * then scaled down to fit the available space.
 653:      *
 654:      * @param width  The width.
 655:      */
 656:     public void setMinimumDrawWidth(int width) {
 657:         this.minimumDrawWidth = width;
 658:     }
 659: 
 660:     /**
 661:      * Returns the maximum drawing width for charts.
 662:      * <P>
 663:      * If the width available on the panel is greater than this, then the chart
 664:      * is drawn at the maximum width then scaled up to fit.
 665:      *
 666:      * @return The maximum drawing width.
 667:      */
 668:     public int getMaximumDrawWidth() {
 669:         return this.maximumDrawWidth;
 670:     }
 671: 
 672:     /**
 673:      * Sets the maximum drawing width for the chart on this panel.
 674:      * <P>
 675:      * At the time the chart is drawn on the panel, if the available width is
 676:      * greater than this amount, the chart will be drawn using the maximum
 677:      * width then scaled up to fit the available space.
 678:      *
 679:      * @param width  The width.
 680:      */
 681:     public void setMaximumDrawWidth(int width) {
 682:         this.maximumDrawWidth = width;
 683:     }
 684: 
 685:     /**
 686:      * Returns the minimum drawing height for charts.
 687:      * <P>
 688:      * If the height available on the panel is less than this, then the chart
 689:      * is drawn at the minimum height then scaled down to fit.
 690:      *
 691:      * @return The minimum drawing height.
 692:      */
 693:     public int getMinimumDrawHeight() {
 694:         return this.minimumDrawHeight;
 695:     }
 696: 
 697:     /**
 698:      * Sets the minimum drawing height for the chart on this panel.
 699:      * <P>
 700:      * At the time the chart is drawn on the panel, if the available height is
 701:      * less than this amount, the chart will be drawn using the minimum height
 702:      * then scaled down to fit the available space.
 703:      *
 704:      * @param height  The height.
 705:      */
 706:     public void setMinimumDrawHeight(int height) {
 707:         this.minimumDrawHeight = height;
 708:     }
 709: 
 710:     /**
 711:      * Returns the maximum drawing height for charts.
 712:      * <P>
 713:      * If the height available on the panel is greater than this, then the
 714:      * chart is drawn at the maximum height then scaled up to fit.
 715:      *
 716:      * @return The maximum drawing height.
 717:      */
 718:     public int getMaximumDrawHeight() {
 719:         return this.maximumDrawHeight;
 720:     }
 721: 
 722:     /**
 723:      * Sets the maximum drawing height for the chart on this panel.
 724:      * <P>
 725:      * At the time the chart is drawn on the panel, if the available height is
 726:      * greater than this amount, the chart will be drawn using the maximum
 727:      * height then scaled up to fit the available space.
 728:      *
 729:      * @param height  The height.
 730:      */
 731:     public void setMaximumDrawHeight(int height) {
 732:         this.maximumDrawHeight = height;
 733:     }
 734: 
 735:     /**
 736:      * Returns the X scale factor for the chart.  This will be 1.0 if no 
 737:      * scaling has been used.
 738:      * 
 739:      * @return The scale factor.
 740:      */
 741:     public double getScaleX() {
 742:         return this.scaleX;
 743:     }
 744:     
 745:     /**
 746:      * Returns the Y scale factory for the chart.  This will be 1.0 if no 
 747:      * scaling has been used.
 748:      * 
 749:      * @return The scale factor.
 750:      */
 751:     public double getScaleY() {
 752:         return this.scaleY;
 753:     }
 754:     
 755:     /**
 756:      * Returns the anchor point.
 757:      * 
 758:      * @return The anchor point (possibly <code>null</code>).
 759:      */
 760:     public Point2D getAnchor() {
 761:         return this.anchor;   
 762:     }
 763:     
 764:     /**
 765:      * Sets the anchor point.  This method is provided for the use of 
 766:      * subclasses, not end users.
 767:      * 
 768:      * @param anchor  the anchor point (<code>null</code> permitted).
 769:      */
 770:     protected void setAnchor(Point2D anchor) {
 771:         this.anchor = anchor;   
 772:     }
 773:     
 774:     /**
 775:      * Returns the popup menu.
 776:      *
 777:      * @return The popup menu.
 778:      */
 779:     public JPopupMenu getPopupMenu() {
 780:         return this.popup;
 781:     }
 782: 
 783:     /**
 784:      * Sets the popup menu for the panel.
 785:      *
 786:      * @param popup  the popup menu (<code>null</code> permitted).
 787:      */
 788:     public void setPopupMenu(JPopupMenu popup) {
 789:         this.popup = popup;
 790:     }
 791: 
 792:     /**
 793:      * Returns the chart rendering info from the most recent chart redraw.
 794:      *
 795:      * @return The chart rendering info.
 796:      */
 797:     public ChartRenderingInfo getChartRenderingInfo() {
 798:         return this.info;
 799:     }
 800: 
 801:     /**
 802:      * A convenience method that switches on mouse-based zooming.
 803:      *
 804:      * @param flag  <code>true</code> enables zooming and rectangle fill on 
 805:      *              zoom.
 806:      */
 807:     public void setMouseZoomable(boolean flag) {
 808:         setMouseZoomable(flag, true);
 809:     }
 810: 
 811:     /**
 812:      * A convenience method that switches on mouse-based zooming.
 813:      *
 814:      * @param flag  <code>true</code> if zooming enabled
 815:      * @param fillRectangle  <code>true</code> if zoom rectangle is filled,
 816:      *                       false if rectangle is shown as outline only.
 817:      */
 818:     public void setMouseZoomable(boolean flag, boolean fillRectangle) {
 819:         setDomainZoomable(flag);
 820:         setRangeZoomable(flag);
 821:         setFillZoomRectangle(fillRectangle);
 822:     }
 823: 
 824:     /**
 825:      * Returns the flag that determines whether or not zooming is enabled for 
 826:      * the domain axis.
 827:      * 
 828:      * @return A boolean.
 829:      */
 830:     public boolean isDomainZoomable() {
 831:         return this.domainZoomable;
 832:     }
 833:     
 834:     /**
 835:      * Sets the flag that controls whether or not zooming is enable for the 
 836:      * domain axis.  A check is made to ensure that the current plot supports
 837:      * zooming for the domain values.
 838:      *
 839:      * @param flag  <code>true</code> enables zooming if possible.
 840:      */
 841:     public void setDomainZoomable(boolean flag) {
 842:         if (flag) {
 843:             Plot plot = this.chart.getPlot();
 844:             if (plot instanceof Zoomable) {
 845:                 Zoomable z = (Zoomable) plot;
 846:                 this.domainZoomable = flag && (z.isDomainZoomable());  
 847:             }
 848:         }
 849:         else {
 850:             this.domainZoomable = false;
 851:         }
 852:     }
 853: 
 854:     /**
 855:      * Returns the flag that determines whether or not zooming is enabled for 
 856:      * the range axis.
 857:      * 
 858:      * @return A boolean.
 859:      */
 860:     public boolean isRangeZoomable() {
 861:         return this.rangeZoomable;
 862:     }
 863:     
 864:     /**
 865:      * A flag that controls mouse-based zooming on the vertical axis.
 866:      *
 867:      * @param flag  <code>true</code> enables zooming.
 868:      */
 869:     public void setRangeZoomable(boolean flag) {
 870:         if (flag) {
 871:             Plot plot = this.chart.getPlot();
 872:             if (plot instanceof Zoomable) {
 873:                 Zoomable z = (Zoomable) plot;
 874:                 this.rangeZoomable = flag && (z.isRangeZoomable());  
 875:             }
 876:         }
 877:         else {
 878:             this.rangeZoomable = false;
 879:         }
 880:     }
 881: 
 882:     /**
 883:      * Returns the flag that controls whether or not the zoom rectangle is
 884:      * filled when drawn.
 885:      * 
 886:      * @return A boolean.
 887:      */
 888:     public boolean getFillZoomRectangle() {
 889:         return this.fillZoomRectangle;
 890:     }
 891:     
 892:     /**
 893:      * A flag that controls how the zoom rectangle is drawn.
 894:      *
 895:      * @param flag  <code>true</code> instructs to fill the rectangle on
 896:      *              zoom, otherwise it will be outlined.
 897:      */
 898:     public void setFillZoomRectangle(boolean flag) {
 899:         this.fillZoomRectangle = flag;
 900:     }
 901: 
 902:     /**
 903:      * Returns the zoom trigger distance.  This controls how far the mouse must
 904:      * move before a zoom action is triggered.
 905:      * 
 906:      * @return The distance (in Java2D units).
 907:      */
 908:     public int getZoomTriggerDistance() {
 909:         return this.zoomTriggerDistance;
 910:     }
 911:     
 912:     /**
 913:      * Sets the zoom trigger distance.  This controls how far the mouse must 
 914:      * move before a zoom action is triggered.
 915:      * 
 916:      * @param distance  the distance (in Java2D units).
 917:      */
 918:     public void setZoomTriggerDistance(int distance) {
 919:         this.zoomTriggerDistance = distance;
 920:     }
 921:     
 922:     /**
 923:      * Returns the flag that controls whether or not a horizontal axis trace
 924:      * line is drawn over the plot area at the current mouse location.
 925:      * 
 926:      * @return A boolean.
 927:      */
 928:     public boolean getHorizontalAxisTrace() {
 929:         return this.horizontalAxisTrace;    
 930:     }
 931:     
 932:     /**
 933:      * A flag that controls trace lines on the horizontal axis.
 934:      *
 935:      * @param flag  <code>true</code> enables trace lines for the mouse
 936:      *      pointer on the horizontal axis.
 937:      */
 938:     public void setHorizontalAxisTrace(boolean flag) {
 939:         this.horizontalAxisTrace = flag;
 940:     }
 941:     
 942:     /**
 943:      * Returns the horizontal trace line.
 944:      * 
 945:      * @return The horizontal trace line (possibly <code>null</code>).
 946:      */
 947:     protected Line2D getHorizontalTraceLine() {
 948:         return this.horizontalTraceLine;   
 949:     }
 950:     
 951:     /**
 952:      * Sets the horizontal trace line.
 953:      * 
 954:      * @param line  the line (<code>null</code> permitted).
 955:      */
 956:     protected void setHorizontalTraceLine(Line2D line) {
 957:         this.horizontalTraceLine = line;   
 958:     }
 959: 
 960:     /**
 961:      * Returns the flag that controls whether or not a vertical axis trace
 962:      * line is drawn over the plot area at the current mouse location.
 963:      * 
 964:      * @return A boolean.
 965:      */
 966:     public boolean getVerticalAxisTrace() {
 967:         return this.verticalAxisTrace;    
 968:     }
 969:     
 970:     /**
 971:      * A flag that controls trace lines on the vertical axis.
 972:      *
 973:      * @param flag  <code>true</code> enables trace lines for the mouse
 974:      *              pointer on the vertical axis.
 975:      */
 976:     public void setVerticalAxisTrace(boolean flag) {
 977:         this.verticalAxisTrace = flag;
 978:     }
 979: 
 980:     /**
 981:      * Returns the vertical trace line.
 982:      * 
 983:      * @return The vertical trace line (possibly <code>null</code>).
 984:      */
 985:     protected Line2D getVerticalTraceLine() {
 986:         return this.verticalTraceLine;   
 987:     }
 988:     
 989:     /**
 990:      * Sets the vertical trace line.
 991:      * 
 992:      * @param line  the line (<code>null</code> permitted).
 993:      */
 994:     protected void setVerticalTraceLine(Line2D line) {
 995:         this.verticalTraceLine = line;   
 996:     }
 997: 
 998:     /**
 999:      * Returns <code>true</code> if file extensions should be enforced, and 
1000:      * <code>false</code> otherwise.
1001:      *
1002:      * @return The flag.
1003:      */
1004:     public boolean isEnforceFileExtensions() {
1005:         return this.enforceFileExtensions;
1006:     }
1007: 
1008:     /**
1009:      * Sets a flag that controls whether or not file extensions are enforced.
1010:      *
1011:      * @param enforce  the new flag value.
1012:      */
1013:     public void setEnforceFileExtensions(boolean enforce) {
1014:         this.enforceFileExtensions = enforce;
1015:     }
1016: 
1017:     /**
1018:      * Switches the display of tooltips for the panel on or off.  Note that 
1019:      * tooltips can only be displayed if the chart has been configured to
1020:      * generate tooltip items.
1021:      *
1022:      * @param flag  <code>true</code> to enable tooltips, <code>false</code> to
1023:      *              disable tooltips.
1024:      */
1025:     public void setDisplayToolTips(boolean flag) {
1026:         if (flag) {
1027:             ToolTipManager.sharedInstance().registerComponent(this);
1028:         }
1029:         else {
1030:             ToolTipManager.sharedInstance().unregisterComponent(this);
1031:         }
1032:     }
1033: 
1034:     /**
1035:      * Returns a string for the tooltip.
1036:      *
1037:      * @param e  the mouse event.
1038:      *
1039:      * @return A tool tip or <code>null</code> if no tooltip is available.
1040:      */
1041:     public String getToolTipText(MouseEvent e) {
1042: 
1043:         String result = null;
1044:         if (this.info != null) {
1045:             EntityCollection entities = this.info.getEntityCollection();
1046:             if (entities != null) {
1047:                 Insets insets = getInsets();
1048:                 ChartEntity entity = entities.getEntity(
1049:                         (int) ((e.getX() - insets.left) / this.scaleX),
1050:                         (int) ((e.getY() - insets.top) / this.scaleY));
1051:                 if (entity != null) {
1052:                     result = entity.getToolTipText();
1053:                 }
1054:             }
1055:         }
1056:         return result;
1057: 
1058:     }
1059: 
1060:     /**
1061:      * Translates a Java2D point on the chart to a screen location.
1062:      *
1063:      * @param java2DPoint  the Java2D point.
1064:      *
1065:      * @return The screen location.
1066:      */
1067:     public Point translateJava2DToScreen(Point2D java2DPoint) {
1068:         Insets insets = getInsets();
1069:         int x = (int) (java2DPoint.getX() * this.scaleX + insets.left);
1070:         int y = (int) (java2DPoint.getY() * this.scaleY + insets.top);
1071:         return new Point(x, y);
1072:     }
1073: 
1074:     /**
1075:      * Translates a screen location to a Java2D point.
1076:      *
1077:      * @param screenPoint  the screen location.
1078:      *
1079:      * @return The Java2D coordinates.
1080:      */
1081:     public Point2D translateScreenToJava2D(Point screenPoint) {
1082:         Insets insets = getInsets();
1083:         double x = (screenPoint.getX() - insets.left) / this.scaleX;
1084:         double y = (screenPoint.getY() - insets.top) / this.scaleY;
1085:         return new Point2D.Double(x, y);
1086:     }
1087: 
1088:     /**
1089:      * Applies any scaling that is in effect for the chart drawing to the
1090:      * given rectangle.
1091:      *  
1092:      * @param rect  the rectangle.
1093:      * 
1094:      * @return A new scaled rectangle.
1095:      */
1096:     public Rectangle2D scale(Rectangle2D rect) {
1097:         Insets insets = getInsets();
1098:         double x = rect.getX() * getScaleX() + insets.left;
1099:         double y = rect.getY() * this.getScaleY() + insets.top;
1100:         double w = rect.getWidth() * this.getScaleX();
1101:         double h = rect.getHeight() * this.getScaleY();
1102:         return new Rectangle2D.Double(x, y, w, h);
1103:     }
1104: 
1105:     /**
1106:      * Returns the chart entity at a given point.
1107:      * <P>
1108:      * This method will return null if there is (a) no entity at the given 
1109:      * point, or (b) no entity collection has been generated.
1110:      *
1111:      * @param viewX  the x-coordinate.
1112:      * @param viewY  the y-coordinate.
1113:      *
1114:      * @return The chart entity (possibly <code>null</code>).
1115:      */
1116:     public ChartEntity getEntityForPoint(int viewX, int viewY) {
1117: 
1118:         ChartEntity result = null;
1119:         if (this.info != null) {
1120:             Insets insets = getInsets();
1121:             double x = (viewX - insets.left) / this.scaleX;
1122:             double y = (viewY - insets.top) / this.scaleY;
1123:             EntityCollection entities = this.info.getEntityCollection();
1124:             result = entities != null ? entities.getEntity(x, y) : null; 
1125:         }
1126:         return result;
1127: 
1128:     }
1129: 
1130:     /**
1131:      * Returns the flag that controls whether or not the offscreen buffer
1132:      * needs to be refreshed.
1133:      * 
1134:      * @return A boolean.
1135:      */
1136:     public boolean getRefreshBuffer() {
1137:         return this.refreshBuffer;
1138:     }
1139:     
1140:     /**
1141:      * Sets the refresh buffer flag.  This flag is used to avoid unnecessary
1142:      * redrawing of the chart when the offscreen image buffer is used.
1143:      *
1144:      * @param flag  <code>true</code> indicate, that the buffer should be 
1145:      *              refreshed.
1146:      */
1147:     public void setRefreshBuffer(boolean flag) {
1148:         this.refreshBuffer = flag;
1149:     }
1150: 
1151:     /**
1152:      * Paints the component by drawing the chart to fill the entire component,
1153:      * but allowing for the insets (which will be non-zero if a border has been
1154:      * set for this component).  To increase performance (at the expense of
1155:      * memory), an off-screen buffer image can be used.
1156:      *
1157:      * @param g  the graphics device for drawing on.
1158:      */
1159:     public void paintComponent(Graphics g) {
1160:         super.paintComponent(g);
1161:         if (this.chart == null) {
1162:             return;
1163:         }
1164:         Graphics2D g2 = (Graphics2D) g.create();
1165: 
1166:         // first determine the size of the chart rendering area...
1167:         Dimension size = getSize();
1168:         Insets insets = getInsets();
1169:         Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
1170:                 size.getWidth() - insets.left - insets.right,
1171:                 size.getHeight() - insets.top - insets.bottom);
1172: 
1173:         // work out if scaling is required...
1174:         boolean scale = false;
1175:         double drawWidth = available.getWidth();
1176:         double drawHeight = available.getHeight();
1177:         this.scaleX = 1.0;
1178:         this.scaleY = 1.0;
1179: 
1180:         if (drawWidth < this.minimumDrawWidth) {
1181:             this.scaleX = drawWidth / this.minimumDrawWidth;
1182:             drawWidth = this.minimumDrawWidth;
1183:             scale = true;
1184:         }
1185:         else if (drawWidth > this.maximumDrawWidth) {
1186:             this.scaleX = drawWidth / this.maximumDrawWidth;
1187:             drawWidth = this.maximumDrawWidth;
1188:             scale = true;
1189:         }
1190: 
1191:         if (drawHeight < this.minimumDrawHeight) {
1192:             this.scaleY = drawHeight / this.minimumDrawHeight;
1193:             drawHeight = this.minimumDrawHeight;
1194:             scale = true;
1195:         }
1196:         else if (drawHeight > this.maximumDrawHeight) {
1197:             this.scaleY = drawHeight / this.maximumDrawHeight;
1198:             drawHeight = this.maximumDrawHeight;
1199:             scale = true;
1200:         }
1201: 
1202:         Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, 
1203:                 drawHeight);
1204: 
1205:         // are we using the chart buffer?
1206:         if (this.useBuffer) {
1207: 
1208:             // do we need to resize the buffer?
1209:             if ((this.chartBuffer == null) 
1210:                     || (this.chartBufferWidth != available.getWidth())
1211:                     || (this.chartBufferHeight != available.getHeight())
1212:             ) {
1213:                 this.chartBufferWidth = (int) available.getWidth();
1214:                 this.chartBufferHeight = (int) available.getHeight();
1215:                 this.chartBuffer = createImage(
1216:                         this.chartBufferWidth, this.chartBufferHeight);
1217: //                GraphicsConfiguration gc = g2.getDeviceConfiguration();
1218: //                this.chartBuffer = gc.createCompatibleImage(
1219: //                        this.chartBufferWidth, this.chartBufferHeight, 
1220: //                        Transparency.TRANSLUCENT);
1221:                 this.refreshBuffer = true;
1222:             }
1223: 
1224:             // do we need to redraw the buffer?
1225:             if (this.refreshBuffer) {
1226: 
1227:                 Rectangle2D bufferArea = new Rectangle2D.Double(
1228:                         0, 0, this.chartBufferWidth, this.chartBufferHeight);
1229: 
1230:                 Graphics2D bufferG2 
1231:                     = (Graphics2D) this.chartBuffer.getGraphics();
1232:                 if (scale) {
1233:                     AffineTransform saved = bufferG2.getTransform();
1234:                     AffineTransform st = AffineTransform.getScaleInstance(
1235:                             this.scaleX, this.scaleY);
1236:                     bufferG2.transform(st);
1237:                     this.chart.draw(bufferG2, chartArea, this.anchor, 
1238:                             this.info);
1239:                     bufferG2.setTransform(saved);
1240:                 }
1241:                 else {
1242:                     this.chart.draw(bufferG2, bufferArea, this.anchor, 
1243:                             this.info);
1244:                 }
1245: 
1246:                 this.refreshBuffer = false;
1247: 
1248:             }
1249: 
1250:             // zap the buffer onto the panel...
1251:             g2.drawImage(this.chartBuffer, insets.left, insets.right, this);
1252: 
1253:         }
1254: 
1255:         // or redrawing the chart every time...
1256:         else {
1257: 
1258:             AffineTransform saved = g2.getTransform();
1259:             g2.translate(insets.left, insets.top);
1260:             if (scale) {
1261:                 AffineTransform st = AffineTransform.getScaleInstance(
1262:                         this.scaleX, this.scaleY);
1263:                 g2.transform(st);
1264:             }
1265:             this.chart.draw(g2, chartArea, this.anchor, this.info);
1266:             g2.setTransform(saved);
1267: 
1268:         }
1269:         
1270:         // Redraw the zoom rectangle (if present)
1271:         drawZoomRectangle(g2);
1272:         
1273:         g2.dispose();
1274: 
1275:         this.anchor = null;
1276:         this.verticalTraceLine = null;
1277:         this.horizontalTraceLine = null;
1278: 
1279:     }
1280: 
1281:     /**
1282:      * Receives notification of changes to the chart, and redraws the chart.
1283:      *
1284:      * @param event  details of the chart change event.
1285:      */
1286:     public void chartChanged(ChartChangeEvent event) {
1287:         this.refreshBuffer = true;
1288:         Plot plot = this.chart.getPlot();
1289:         if (plot instanceof Zoomable) {
1290:             Zoomable z = (Zoomable) plot;
1291:             this.orientation = z.getOrientation();
1292:         }
1293:         repaint();
1294:     }
1295: 
1296:     /**
1297:      * Receives notification of a chart progress event.
1298:      *
1299:      * @param event  the event.
1300:      */
1301:     public void chartProgress(ChartProgressEvent event) {
1302:         // does nothing - override if necessary
1303:     }
1304: 
1305:     /**
1306:      * Handles action events generated by the popup menu.
1307:      *
1308:      * @param event  the event.
1309:      */
1310:     public void actionPerformed(ActionEvent event) {
1311: 
1312:         String command = event.getActionCommand();
1313: 
1314:         if (command.equals(PROPERTIES_COMMAND)) {
1315:             doEditChartProperties();
1316:         }
1317:         else if (command.equals(SAVE_COMMAND)) {
1318:             try {
1319:                 doSaveAs();
1320:             }
1321:             catch (IOException e) {
1322:                 e.printStackTrace();
1323:             }
1324:         }
1325:         else if (command.equals(PRINT_COMMAND)) {
1326:             createChartPrintJob();
1327:         }
1328:         else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
1329:             zoomInBoth(this.zoomPoint.getX(), this.zoomPoint.getY());
1330:         }
1331:         else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
1332:             zoomInDomain(this.zoomPoint.getX(), this.zoomPoint.getY());
1333:         }
1334:         else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
1335:             zoomInRange(this.zoomPoint.getX(), this.zoomPoint.getY());
1336:         }
1337:         else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
1338:             zoomOutBoth(this.zoomPoint.getX(), this.zoomPoint.getY());
1339:         }
1340:         else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
1341:             zoomOutDomain(this.zoomPoint.getX(), this.zoomPoint.getY());
1342:         }
1343:         else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
1344:             zoomOutRange(this.zoomPoint.getX(), this.zoomPoint.getY());
1345:         }
1346:         else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
1347:             restoreAutoBounds();
1348:         }
1349:         else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
1350:             restoreAutoDomainBounds();
1351:         }
1352:         else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
1353:             restoreAutoRangeBounds();
1354:         }
1355: 
1356:     }
1357: 
1358:     /**
1359:      * Handles a 'mouse entered' event. This method changes the tooltip delays
1360:      * of ToolTipManager.sharedInstance() to the possibly different values set 
1361:      * for this chart panel. 
1362:      *
1363:      * @param e  the mouse event.
1364:      */
1365:     public void mouseEntered(MouseEvent e) {
1366:         if (!this.ownToolTipDelaysActive) {
1367:             ToolTipManager ttm = ToolTipManager.sharedInstance();
1368:             
1369:             this.originalToolTipInitialDelay = ttm.getInitialDelay();
1370:             ttm.setInitialDelay(this.ownToolTipInitialDelay);
1371:     
1372:             this.originalToolTipReshowDelay = ttm.getReshowDelay();
1373:             ttm.setReshowDelay(this.ownToolTipReshowDelay);
1374:             
1375:             this.originalToolTipDismissDelay = ttm.getDismissDelay();
1376:             ttm.setDismissDelay(this.ownToolTipDismissDelay);
1377:     
1378:             this.ownToolTipDelaysActive = true;
1379:         }
1380:     }
1381: 
1382:     /**
1383:      * Handles a 'mouse exited' event. This method resets the tooltip delays of
1384:      * ToolTipManager.sharedInstance() to their
1385:      * original values in effect before mouseEntered()
1386:      *
1387:      * @param e  the mouse event.
1388:      */
1389:     public void mouseExited(MouseEvent e) {
1390:         if (this.ownToolTipDelaysActive) {
1391:             // restore original tooltip dealys 
1392:             ToolTipManager ttm = ToolTipManager.sharedInstance();       
1393:             ttm.setInitialDelay(this.originalToolTipInitialDelay);
1394:             ttm.setReshowDelay(this.originalToolTipReshowDelay);
1395:             ttm.setDismissDelay(this.originalToolTipDismissDelay);
1396:             this.ownToolTipDelaysActive = false;
1397:         }
1398:     }
1399: 
1400:     /**
1401:      * Handles a 'mouse pressed' event.
1402:      * <P>
1403:      * This event is the popup trigger on Unix/Linux.  For Windows, the popup
1404:      * trigger is the 'mouse released' event.
1405:      *
1406:      * @param e  The mouse event.
1407:      */
1408:     public void mousePressed(MouseEvent e) {
1409:         if (this.zoomRectangle == null) {
1410:             Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
1411:             if (screenDataArea != null) {
1412:                 this.zoomPoint = getPointInRectangle(e.getX(), e.getY(), 
1413:                         screenDataArea);
1414:             }
1415:             else {
1416:                 this.zoomPoint = null;
1417:             }
1418:             if (e.isPopupTrigger()) {
1419:                 if (this.popup != null) {
1420:                     displayPopupMenu(e.getX(), e.getY());
1421:                 }
1422:             }
1423:         }
1424:     }
1425:     
1426:     /**
1427:      * Returns a point based on (x, y) but constrained to be within the bounds
1428:      * of the given rectangle.  This method could be moved to JCommon.
1429:      * 
1430:      * @param x  the x-coordinate.
1431:      * @param y  the y-coordinate.
1432:      * @param area  the rectangle (<code>null</code> not permitted).
1433:      * 
1434:      * @return A point within the rectangle.
1435:      */
1436:     private Point getPointInRectangle(int x, int y, Rectangle2D area) {
1437:         x = (int) Math.max(Math.ceil(area.getMinX()), Math.min(x, 
1438:                 Math.floor(area.getMaxX())));   
1439:         y = (int) Math.max(Math.ceil(area.getMinY()), Math.min(y, 
1440:                 Math.floor(area.getMaxY())));
1441:         return new Point(x, y);
1442:     }
1443: 
1444:     /**
1445:      * Handles a 'mouse dragged' event.
1446:      *
1447:      * @param e  the mouse event.
1448:      */
1449:     public void mouseDragged(MouseEvent e) {
1450: 
1451:         // if the popup menu has already been triggered, then ignore dragging...
1452:         if (this.popup != null && this.popup.isShowing()) {
1453:             return;
1454:         }
1455:         // if no initial zoom point was set, ignore dragging...
1456:         if (this.zoomPoint == null) {
1457:             return;
1458:         }
1459:         Graphics2D g2 = (Graphics2D) getGraphics();
1460: 
1461:         // Erase the previous zoom rectangle (if any)...
1462:         drawZoomRectangle(g2);
1463: 
1464:         boolean hZoom = false;
1465:         boolean vZoom = false;
1466:         if (this.orientation == PlotOrientation.HORIZONTAL) {
1467:             hZoom = this.rangeZoomable;
1468:             vZoom = this.domainZoomable;
1469:         }
1470:         else {
1471:             hZoom = this.domainZoomable;              
1472:             vZoom = this.rangeZoomable;
1473:         }
1474:         Rectangle2D scaledDataArea = getScreenDataArea(
1475:                 (int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
1476:         if (hZoom && vZoom) {
1477:             // selected rectangle shouldn't extend outside the data area...
1478:             double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1479:             double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1480:             this.zoomRectangle = new Rectangle2D.Double(
1481:                     this.zoomPoint.getX(), this.zoomPoint.getY(),
1482:                     xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY());
1483:         }
1484:         else if (hZoom) {
1485:             double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1486:             this.zoomRectangle = new Rectangle2D.Double(
1487:                     this.zoomPoint.getX(), scaledDataArea.getMinY(),
1488:                     xmax - this.zoomPoint.getX(), scaledDataArea.getHeight());
1489:         }
1490:         else if (vZoom) {
1491:             double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1492:             this.zoomRectangle = new Rectangle2D.Double(
1493:                     scaledDataArea.getMinX(), this.zoomPoint.getY(),
1494:                     scaledDataArea.getWidth(), ymax - this.zoomPoint.getY());
1495:         }
1496: 
1497:         // Draw the new zoom rectangle...
1498:         drawZoomRectangle(g2);
1499:         
1500:         g2.dispose();
1501: 
1502:     }
1503: 
1504:     /**
1505:      * Handles a 'mouse released' event.  On Windows, we need to check if this 
1506:      * is a popup trigger, but only if we haven't already been tracking a zoom
1507:      * rectangle.
1508:      *
1509:      * @param e  information about the event.
1510:      */
1511:     public void mouseReleased(MouseEvent e) {
1512: 
1513:         if (this.zoomRectangle != null) {
1514:             boolean hZoom = false;
1515:             boolean vZoom = false;
1516:             if (this.orientation == PlotOrientation.HORIZONTAL) {
1517:                 hZoom = this.rangeZoomable;
1518:                 vZoom = this.domainZoomable;
1519:             }
1520:             else {
1521:                 hZoom = this.domainZoomable;              
1522:                 vZoom = this.rangeZoomable;
1523:             }
1524:             
1525:             boolean zoomTrigger1 = hZoom && Math.abs(e.getX() 
1526:                 - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
1527:             boolean zoomTrigger2 = vZoom && Math.abs(e.getY() 
1528:                 - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
1529:             if (zoomTrigger1 || zoomTrigger2) {
1530:                 if ((hZoom && (e.getX() < this.zoomPoint.getX())) 
1531:                     || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
1532:                     restoreAutoBounds();
1533:                 }
1534:                 else {
1535:                     double x, y, w, h;
1536:                     Rectangle2D screenDataArea = getScreenDataArea(
1537:                             (int) this.zoomPoint.getX(), 
1538:                             (int) this.zoomPoint.getY());
1539:                     // for mouseReleased event, (horizontalZoom || verticalZoom)
1540:                     // will be true, so we can just test for either being false;
1541:                     // otherwise both are true
1542:                     if (!vZoom) {
1543:                         x = this.zoomPoint.getX();
1544:                         y = screenDataArea.getMinY();
1545:                         w = Math.min(this.zoomRectangle.getWidth(),
1546:                                 screenDataArea.getMaxX() 
1547:                                 - this.zoomPoint.getX());
1548:                         h = screenDataArea.getHeight();
1549:                     }
1550:                     else if (!hZoom) {
1551:                         x = screenDataArea.getMinX();
1552:                         y = this.zoomPoint.getY();
1553:                         w = screenDataArea.getWidth();
1554:                         h = Math.min(this.zoomRectangle.getHeight(),
1555:                                 screenDataArea.getMaxY() 
1556:                                 - this.zoomPoint.getY());
1557:                     }
1558:                     else {
1559:                         x = this.zoomPoint.getX();
1560:                         y = this.zoomPoint.getY();
1561:                         w = Math.min(this.zoomRectangle.getWidth(),
1562:                                 screenDataArea.getMaxX() 
1563:                                 - this.zoomPoint.getX());
1564:                         h = Math.min(this.zoomRectangle.getHeight(),
1565:                                 screenDataArea.getMaxY() 
1566:                                 - this.zoomPoint.getY());
1567:                     }
1568:                     Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
1569:                     zoom(zoomArea);
1570:                 }
1571:                 this.zoomPoint = null;
1572:                 this.zoomRectangle = null;
1573:             }
1574:             else {
1575:                 // Erase the zoom rectangle
1576:                 Graphics2D g2 = (Graphics2D) getGraphics();
1577:                 drawZoomRectangle(g2);
1578:                 g2.dispose();
1579:                 this.zoomPoint = null;
1580:                 this.zoomRectangle = null;
1581:             }
1582: 
1583:         }
1584: 
1585:         else if (e.isPopupTrigger()) {
1586:             if (this.popup != null) {
1587:                 displayPopupMenu(e.getX(), e.getY());
1588:             }
1589:         }
1590: 
1591:     }
1592: 
1593:     /**
1594:      * Receives notification of mouse clicks on the panel. These are
1595:      * translated and passed on to any registered chart mouse click listeners.
1596:      *
1597:      * @param event  Information about the mouse event.
1598:      */
1599:     public void mouseClicked(MouseEvent event) {
1600: 
1601:         Insets insets = getInsets();
1602:         int x = (int) ((event.getX() - insets.left) / this.scaleX);
1603:         int y = (int) ((event.getY() - insets.top) / this.scaleY);
1604: 
1605:         this.anchor = new Point2D.Double(x, y);
1606:         if (this.chart == null) {
1607:             return;
1608:         }
1609:         this.chart.setNotify(true);  // force a redraw 
1610:         // new entity code...
1611:         Object[] listeners = this.chartMouseListeners.getListeners(
1612:                 ChartMouseListener.class);
1613:         if (listeners.length == 0) {
1614:             return;
1615:         }
1616: 
1617:         ChartEntity entity = null;
1618:         if (this.info != null) {
1619:             EntityCollection entities = this.info.getEntityCollection();
1620:             if (entities != null) {
1621:                 entity = entities.getEntity(x, y);
1622:             }
1623:         }
1624:         ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event, 
1625:                 entity);
1626:         for (int i = listeners.length - 1; i >= 0; i -= 1) {
1627:             ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
1628:         }
1629: 
1630:     }
1631: 
1632:     /**
1633:      * Implementation of the MouseMotionListener's method.
1634:      *
1635:      * @param e  the event.
1636:      */
1637:     public void mouseMoved(MouseEvent e) {
1638:         Graphics2D g2 = (Graphics2D) getGraphics();
1639:         if (this.horizontalAxisTrace) {
1640:             drawHorizontalAxisTrace(g2, e.getX());
1641:         }
1642:         if (this.verticalAxisTrace) {
1643:             drawVerticalAxisTrace(g2, e.getY());
1644:         }
1645:         g2.dispose();
1646:         
1647:         Object[] listeners = this.chartMouseListeners.getListeners(
1648:                 ChartMouseListener.class);
1649:         if (listeners.length == 0) {
1650:             return;
1651:         }
1652:         Insets insets = getInsets();
1653:         int x = (int) ((e.getX() - insets.left) / this.scaleX);
1654:         int y = (int) ((e.getY() - insets.top) / this.scaleY);
1655: 
1656:         ChartEntity entity = null;
1657:         if (this.info != null) {
1658:             EntityCollection entities = this.info.getEntityCollection();
1659:             if (entities != null) {
1660:                 entity = entities.getEntity(x, y);
1661:             }
1662:         }
1663:         
1664:         // we can only generate events if the panel's chart is not null
1665:         // (see bug report 1556951)
1666:         if (this.chart != null) {
1667:             ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity);
1668:             for (int i = listeners.length - 1; i >= 0; i -= 1) {
1669:                 ((ChartMouseListener) listeners[i]).chartMouseMoved(event);
1670:             }
1671:         }
1672: 
1673:     }
1674: 
1675:     /**
1676:      * Zooms in on an anchor point (specified in screen coordinate space).
1677:      *
1678:      * @param x  the x value (in screen coordinates).
1679:      * @param y  the y value (in screen coordinates).
1680:      */
1681:     public void zoomInBoth(double x, double y) {
1682:         zoomInDomain(x, y);
1683:         zoomInRange(x, y);
1684:     }
1685: 
1686:     /**
1687:      * Decreases the length of the domain axis, centered about the given
1688:      * coordinate on the screen.  The length of the domain axis is reduced
1689:      * by the value of {@link #getZoomInFactor()}.
1690:      *
1691:      * @param x  the x coordinate (in screen coordinates).
1692:      * @param y  the y-coordinate (in screen coordinates).
1693:      */
1694:     public void zoomInDomain(double x, double y) {
1695:         Plot p = this.chart.getPlot();
1696:         if (p instanceof Zoomable) {
1697:             Zoomable plot = (Zoomable) p;
1698:             plot.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(), 
1699:                     translateScreenToJava2D(new Point((int) x, (int) y)));
1700:         }
1701:     }
1702: 
1703:     /**
1704:      * Decreases the length of the range axis, centered about the given
1705:      * coordinate on the screen.  The length of the range axis is reduced by
1706:      * the value of {@link #getZoomInFactor()}.
1707:      *
1708:      * @param x  the x-coordinate (in screen coordinates).
1709:      * @param y  the y coordinate (in screen coordinates).
1710:      */
1711:     public void zoomInRange(double x, double y) {
1712:         Plot p = this.chart.getPlot();
1713:         if (p instanceof Zoomable) {
1714:             Zoomable z = (Zoomable) p;
1715:             z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(), 
1716:                     translateScreenToJava2D(new Point((int) x, (int) y)));
1717:         }
1718:     }
1719: 
1720:     /**
1721:      * Zooms out on an anchor point (specified in screen coordinate space).
1722:      *
1723:      * @param x  the x value (in screen coordinates).
1724:      * @param y  the y value (in screen coordinates).
1725:      */
1726:     public void zoomOutBoth(double x, double y) {
1727:         zoomOutDomain(x, y);
1728:         zoomOutRange(x, y);
1729:     }
1730: 
1731:     /**
1732:      * Increases the length of the domain axis, centered about the given
1733:      * coordinate on the screen.  The length of the domain axis is increased
1734:      * by the value of {@link #getZoomOutFactor()}.
1735:      *
1736:      * @param x  the x coordinate (in screen coordinates).
1737:      * @param y  the y-coordinate (in screen coordinates).
1738:      */
1739:     public void zoomOutDomain(double x, double y) {
1740:         Plot p = this.chart.getPlot();
1741:         if (p instanceof Zoomable) {
1742:             Zoomable z = (Zoomable) p;
1743:             z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(), 
1744:                     translateScreenToJava2D(new Point((int) x, (int) y)));
1745:         }
1746:     }
1747: 
1748:     /**
1749:      * Increases the length the range axis, centered about the given
1750:      * coordinate on the screen.  The length of the range axis is increased
1751:      * by the value of {@link #getZoomOutFactor()}.
1752:      *
1753:      * @param x  the x coordinate (in screen coordinates).
1754:      * @param y  the y-coordinate (in screen coordinates).
1755:      */
1756:     public void zoomOutRange(double x, double y) {
1757:         Plot p = this.chart.getPlot();
1758:         if (p instanceof Zoomable) {
1759:             Zoomable z = (Zoomable) p;
1760:             z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(), 
1761:                     translateScreenToJava2D(new Point((int) x, (int) y)));
1762:         }
1763:     }
1764: 
1765:     /**
1766:      * Zooms in on a selected region.
1767:      *
1768:      * @param selection  the selected region.
1769:      */
1770:     public void zoom(Rectangle2D selection) {
1771: 
1772:         // get the origin of the zoom selection in the Java2D space used for
1773:         // drawing the chart (that is, before any scaling to fit the panel)
1774:         Point2D selectOrigin = translateScreenToJava2D(new Point(
1775:                 (int) Math.ceil(selection.getX()), 
1776:                 (int) Math.ceil(selection.getY())));
1777:         PlotRenderingInfo plotInfo = this.info.getPlotInfo();
1778:         Rectangle2D scaledDataArea = getScreenDataArea(
1779:                 (int) selection.getCenterX(), (int) selection.getCenterY());
1780:         if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
1781: 
1782:             double hLower = (selection.getMinX() - scaledDataArea.getMinX()) 
1783:                 / scaledDataArea.getWidth();
1784:             double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) 
1785:                 / scaledDataArea.getWidth();
1786:             double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) 
1787:                 / scaledDataArea.getHeight();
1788:             double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) 
1789:                 / scaledDataArea.getHeight();
1790: 
1791:             Plot p = this.chart.getPlot();
1792:             if (p instanceof Zoomable) {
1793:                 Zoomable z = (Zoomable) p;
1794:                 if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
1795:                     z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
1796:                     z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
1797:                 }
1798:                 else {
1799:                     z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
1800:                     z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
1801:                 }
1802:             }
1803: 
1804:         }
1805: 
1806:     }
1807: 
1808:     /**
1809:      * Restores the auto-range calculation on both axes.
1810:      */
1811:     public void restoreAutoBounds() {
1812:         restoreAutoDomainBounds();
1813:         restoreAutoRangeBounds();
1814:     }
1815: 
1816:     /**
1817:      * Restores the auto-range calculation on the domain axis.
1818:      */
1819:     public void restoreAutoDomainBounds() {
1820:         Plot p = this.chart.getPlot();
1821:         if (p instanceof Zoomable) {
1822:             Zoomable z = (Zoomable) p;
1823:             z.zoomDomainAxes(0.0, this.info.getPlotInfo(), this.zoomPoint);
1824:         }
1825:     }
1826: 
1827:     /**
1828:      * Restores the auto-range calculation on the range axis.
1829:      */
1830:     public void restoreAutoRangeBounds() {
1831:         Plot p = this.chart.getPlot();
1832:         if (p instanceof Zoomable) {
1833:             Zoomable z = (Zoomable) p;
1834:             z.zoomRangeAxes(0.0, this.info.getPlotInfo(), this.zoomPoint);
1835:         }
1836:     }
1837: 
1838:     /**
1839:      * Returns the data area for the chart (the area inside the axes) with the
1840:      * current scaling applied (that is, the area as it appears on screen).
1841:      *
1842:      * @return The scaled data area.
1843:      */
1844:     public Rectangle2D getScreenDataArea() {
1845:         Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
1846:         Insets insets = getInsets();
1847:         double x = dataArea.getX() * this.scaleX + insets.left;
1848:         double y = dataArea.getY() * this.scaleY + insets.top;
1849:         double w = dataArea.getWidth() * this.scaleX;
1850:         double h = dataArea.getHeight() * this.scaleY;
1851:         return new Rectangle2D.Double(x, y, w, h);
1852:     }
1853:     
1854:     /**
1855:      * Returns the data area (the area inside the axes) for the plot or subplot,
1856:      * with the current scaling applied.
1857:      *
1858:      * @param x  the x-coordinate (for subplot selection).
1859:      * @param y  the y-coordinate (for subplot selection).
1860:      * 
1861:      * @return The scaled data area.
1862:      */
1863:     public Rectangle2D getScreenDataArea(int x, int y) {
1864:         PlotRenderingInfo plotInfo = this.info.getPlotInfo();
1865:         Rectangle2D result;
1866:         if (plotInfo.getSubplotCount() == 0) {
1867:             result = getScreenDataArea();
1868:         } 
1869:         else {
1870:             // get the origin of the zoom selection in the Java2D space used for
1871:             // drawing the chart (that is, before any scaling to fit the panel)
1872:             Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
1873:             int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
1874:             if (subplotIndex == -1) {
1875:                 return null;
1876:             }
1877:             result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
1878:         }
1879:         return result;
1880:     }
1881:     
1882:     /**
1883:      * Returns the initial tooltip delay value used inside this chart panel.
1884:      *
1885:      * @return An integer representing the initial delay value, in milliseconds.
1886:      * 
1887:      * @see javax.swing.ToolTipManager#getInitialDelay()
1888:      */
1889:     public int getInitialDelay() {
1890:         return this.ownToolTipInitialDelay;
1891:     }
1892:     
1893:     /**
1894:      * Returns the reshow tooltip delay value used inside this chart panel.
1895:      *
1896:      * @return An integer representing the reshow  delay value, in milliseconds.
1897:      * 
1898:      * @see javax.swing.ToolTipManager#getReshowDelay()
1899:      */
1900:     public int getReshowDelay() {
1901:         return this.ownToolTipReshowDelay;  
1902:     }
1903: 
1904:     /**
1905:      * Returns the dismissal tooltip delay value used inside this chart panel.
1906:      *
1907:      * @return An integer representing the dismissal delay value, in 
1908:      *         milliseconds.
1909:      * 
1910:      * @see javax.swing.ToolTipManager#getDismissDelay()
1911:      */
1912:     public int getDismissDelay() {
1913:         return this.ownToolTipDismissDelay; 
1914:     }
1915:     
1916:     /**
1917:      * Specifies the initial delay value for this chart panel.
1918:      *
1919:      * @param delay  the number of milliseconds to delay (after the cursor has 
1920:      *               paused) before displaying. 
1921:      * 
1922:      * @see javax.swing.ToolTipManager#setInitialDelay(int)
1923:      */
1924:     public void setInitialDelay(int delay) {
1925:         this.ownToolTipInitialDelay = delay;
1926:     }
1927:     
1928:     /**
1929:      * Specifies the amount of time before the user has to wait initialDelay 
1930:      * milliseconds before a tooltip will be shown.
1931:      *
1932:      * @param delay  time in milliseconds
1933:      * 
1934:      * @see javax.swing.ToolTipManager#setReshowDelay(int)
1935:      */
1936:     public void setReshowDelay(int delay) {
1937:         this.ownToolTipReshowDelay = delay;  
1938:     }
1939: 
1940:     /**
1941:      * Specifies the dismissal delay value for this chart panel.
1942:      *
1943:      * @param delay the number of milliseconds to delay before taking away the 
1944:      *              tooltip
1945:      * 
1946:      * @see javax.swing.ToolTipManager#setDismissDelay(int)
1947:      */
1948:     public void setDismissDelay(int delay) {
1949:         this.ownToolTipDismissDelay = delay; 
1950:     }
1951:     
1952:     /**
1953:      * Returns the zoom in factor.
1954:      * 
1955:      * @return The zoom in factor.
1956:      * 
1957:      * @see #setZoomInFactor(double)
1958:      */
1959:     public double getZoomInFactor() {
1960:         return this.zoomInFactor;   
1961:     }
1962:     
1963:     /**
1964:      * Sets the zoom in factor.
1965:      * 
1966:      * @param factor  the factor.
1967:      * 
1968:      * @see #getZoomInFactor()
1969:      */
1970:     public void setZoomInFactor(double factor) {
1971:         this.zoomInFactor = factor;
1972:     }
1973:     
1974:     /**
1975:      * Returns the zoom out factor.
1976:      * 
1977:      * @return The zoom out factor.
1978:      * 
1979:      * @see #setZoomOutFactor(double)
1980:      */
1981:     public double getZoomOutFactor() {
1982:         return this.zoomOutFactor;   
1983:     }
1984:     
1985:     /**
1986:      * Sets the zoom out factor.
1987:      * 
1988:      * @param factor  the factor.
1989:      * 
1990:      * @see #getZoomOutFactor()
1991:      */
1992:     public void setZoomOutFactor(double factor) {
1993:         this.zoomOutFactor = factor;
1994:     }
1995:     
1996:     /**
1997:      * Draws zoom rectangle (if present).
1998:      * The drawing is performed in XOR mode, therefore
1999:      * when this method is called twice in a row,
2000:      * the second call will completely restore the state
2001:      * of the canvas.
2002:      * 
2003:      * @param g2 the graphics device. 
2004:      */
2005:     private void drawZoomRectangle(Graphics2D g2) {
2006:         // Set XOR mode to draw the zoom rectangle
2007:         g2.setXORMode(Color.gray);
2008:         if (this.zoomRectangle != null) {
2009:             if (this.fillZoomRectangle) {
2010:                 g2.fill(this.zoomRectangle);
2011:             }
2012:             else {
2013:                 g2.draw(this.zoomRectangle);
2014:             }
2015:         }
2016:         // Reset to the default 'overwrite' mode
2017:         g2.setPaintMode();
2018:     }
2019:     
2020:     /**
2021:      * Draws a vertical line used to trace the mouse position to the horizontal 
2022:      * axis.
2023:      *
2024:      * @param g2 the graphics device.
2025:      * @param x  the x-coordinate of the trace line.
2026:      */
2027:     private void drawHorizontalAxisTrace(Graphics2D g2, int x) {
2028: 
2029:         Rectangle2D dataArea = getScreenDataArea();
2030: 
2031:         g2.setXORMode(Color.orange);
2032:         if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) {
2033: 
2034:             if (this.verticalTraceLine != null) {
2035:                 g2.draw(this.verticalTraceLine);
2036:                 this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x, 
2037:                         (int) dataArea.getMaxY());
2038:             }
2039:             else {
2040:                 this.verticalTraceLine = new Line2D.Float(x, 
2041:                         (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
2042:             }
2043:             g2.draw(this.verticalTraceLine);
2044:         }
2045: 
2046:         // Reset to the default 'overwrite' mode
2047:         g2.setPaintMode();
2048:     }
2049: 
2050:     /**
2051:      * Draws a horizontal line used to trace the mouse position to the vertical
2052:      * axis.
2053:      *
2054:      * @param g2 the graphics device.
2055:      * @param y  the y-coordinate of the trace line.
2056:      */
2057:     private void drawVerticalAxisTrace(Graphics2D g2, int y) {
2058: 
2059:         Rectangle2D dataArea = getScreenDataArea();
2060: 
2061:         g2.setXORMode(Color.orange);
2062:         if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) {
2063: 
2064:             if (this.horizontalTraceLine != null) {
2065:                 g2.draw(this.horizontalTraceLine);
2066:                 this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y, 
2067:                         (int) dataArea.getMaxX(), y);
2068:             }
2069:             else {
2070:                 this.horizontalTraceLine = new Line2D.Float(
2071:                         (int) dataArea.getMinX(), y, (int) dataArea.getMaxX(), 
2072:                         y);
2073:             }
2074:             g2.draw(this.horizontalTraceLine);
2075:         }
2076: 
2077:         // Reset to the default 'overwrite' mode
2078:         g2.setPaintMode();
2079:     }
2080: 
2081:     /**
2082:      * Displays a dialog that allows the user to edit the properties for the
2083:      * current chart.
2084:      * 
2085:      * @since 1.0.3
2086:      */
2087:     public void doEditChartProperties() {
2088: 
2089:         ChartEditor editor = ChartEditorManager.getChartEditor(this.chart);
2090:         int result = JOptionPane.showConfirmDialog(this, editor, 
2091:                 localizationResources.getString("Chart_Properties"),
2092:                 JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
2093:         if (result == JOptionPane.OK_OPTION) {
2094:             editor.updateChart(this.chart);
2095:         }
2096: 
2097:     }
2098: 
2099:     /**
2100:      * Opens a file chooser and gives the user an opportunity to save the chart
2101:      * in PNG format.
2102:      *
2103:      * @throws IOException if there is an I/O error.
2104:      */
2105:     public void doSaveAs() throws IOException {
2106: 
2107:         JFileChooser fileChooser = new JFileChooser();
2108:         ExtensionFileFilter filter = new ExtensionFileFilter(
2109:                 localizationResources.getString("PNG_Image_Files"), ".png");
2110:         fileChooser.addChoosableFileFilter(filter);
2111: 
2112:         int option = fileChooser.showSaveDialog(this);
2113:         if (option == JFileChooser.APPROVE_OPTION) {
2114:             String filename = fileChooser.getSelectedFile().getPath();
2115:             if (isEnforceFileExtensions()) {
2116:                 if (!filename.endsWith(".png")) {
2117:                     filename = filename + ".png";
2118:                 }
2119:             }
2120:             ChartUtilities.saveChartAsPNG(new File(filename), this.chart, 
2121:                     getWidth(), getHeight());
2122:         }
2123: 
2124:     }
2125: 
2126:     /**
2127:      * Creates a print job for the chart.
2128:      */
2129:     public void createChartPrintJob() {
2130: 
2131:         PrinterJob job = PrinterJob.getPrinterJob();
2132:         PageFormat pf = job.defaultPage();
2133:         PageFormat pf2 = job.pageDialog(pf);
2134:         if (pf2 != pf) {
2135:             job.setPrintable(this, pf2);
2136:             if (job.printDialog()) {
2137:                 try {
2138:                     job.print();
2139:                 }
2140:                 catch (PrinterException e) {
2141:                     JOptionPane.showMessageDialog(this, e);
2142:                 }
2143:             }
2144:         }
2145: 
2146:     }
2147: 
2148:     /**
2149:      * Prints the chart on a single page.
2150:      *
2151:      * @param g  the graphics context.
2152:      * @param pf  the page format to use.
2153:      * @param pageIndex  the index of the page. If not <code>0</code>, nothing 
2154:      *                   gets print.
2155:      *
2156:      * @return The result of printing.
2157:      */
2158:     public int print(Graphics g, PageFormat pf, int pageIndex) {
2159: 
2160:         if (pageIndex != 0) {
2161:             return NO_SUCH_PAGE;
2162:         }
2163:         Graphics2D g2 = (Graphics2D) g;
2164:         double x = pf.getImageableX();
2165:         double y = pf.getImageableY();
2166:         double w = pf.getImageableWidth();
2167:         double h = pf.getImageableHeight();
2168:         this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor, 
2169:                 null);
2170:         return PAGE_EXISTS;
2171: 
2172:     }
2173: 
2174:     /**
2175:      * Adds a listener to the list of objects listening for chart mouse events.
2176:      *
2177:      * @param listener  the listener (<code>null</code> not permitted).
2178:      */
2179:     public void addChartMouseListener(ChartMouseListener listener) {
2180:         if (listener == null) {
2181:             throw new IllegalArgumentException("Null 'listener' argument.");
2182:         }
2183:         this.chartMouseListeners.add(ChartMouseListener.class, listener);
2184:     }
2185: 
2186:     /**
2187:      * Removes a listener from the list of objects listening for chart mouse 
2188:      * events.
2189:      *
2190:      * @param listener  the listener.
2191:      */
2192:     public void removeChartMouseListener(ChartMouseListener listener) {
2193:         this.chartMouseListeners.remove(ChartMouseListener.class, listener);
2194:     }
2195: 
2196:     /**
2197:      * Returns an array of the listeners of the given type registered with the
2198:      * panel.
2199:      * 
2200:      * @param listenerType  the listener type.
2201:      * 
2202:      * @return An array of listeners.
2203:      */
2204:     public EventListener[] getListeners(Class listenerType) {
2205:         if (listenerType == ChartMouseListener.class) {
2206:             // fetch listeners from local storage
2207:             return this.chartMouseListeners.getListeners(listenerType);
2208:         }
2209:         else {
2210:             return super.getListeners(listenerType);
2211:         }
2212:     }
2213: 
2214:     /**
2215:      * Creates a popup menu for the panel.
2216:      *
2217:      * @param properties  include a menu item for the chart property editor.
2218:      * @param save  include a menu item for saving the chart.
2219:      * @param print  include a menu item for printing the chart.
2220:      * @param zoom  include menu items for zooming.
2221:      *
2222:      * @return The popup menu.
2223:      */
2224:     protected JPopupMenu createPopupMenu(boolean properties, 
2225:                                          boolean save, 
2226:                                          boolean print,
2227:                                          boolean zoom) {
2228: 
2229:         JPopupMenu result = new JPopupMenu("Chart:");
2230:         boolean separator = false;
2231: 
2232:         if (properties) {
2233:             JMenuItem propertiesItem = new JMenuItem(
2234:                     localizationResources.getString("Properties..."));
2235:             propertiesItem.setActionCommand(PROPERTIES_COMMAND);
2236:             propertiesItem.addActionListener(this);
2237:             result.add(propertiesItem);
2238:             separator = true;
2239:         }
2240: 
2241:         if (save) {
2242:             if (separator) {
2243:                 result.addSeparator();
2244:                 separator = false;
2245:             }
2246:             JMenuItem saveItem = new JMenuItem(
2247:                     localizationResources.getString("Save_as..."));
2248:             saveItem.setActionCommand(SAVE_COMMAND);
2249:             saveItem.addActionListener(this);
2250:             result.add(saveItem);
2251:             separator = true;
2252:         }
2253: 
2254:         if (print) {
2255:             if (separator) {
2256:                 result.addSeparator();
2257:                 separator = false;
2258:             }
2259:             JMenuItem printItem = new JMenuItem(
2260:                     localizationResources.getString("Print..."));
2261:             printItem.setActionCommand(PRINT_COMMAND);
2262:             printItem.addActionListener(this);
2263:             result.add(printItem);
2264:             separator = true;
2265:         }
2266: 
2267:         if (zoom) {
2268:             if (separator) {
2269:                 result.addSeparator();
2270:                 separator = false;
2271:             }
2272: 
2273:             JMenu zoomInMenu = new JMenu(
2274:                     localizationResources.getString("Zoom_In"));
2275: 
2276:             this.zoomInBothMenuItem = new JMenuItem(
2277:                     localizationResources.getString("All_Axes"));
2278:             this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND);
2279:             this.zoomInBothMenuItem.addActionListener(this);
2280:             zoomInMenu.add(this.zoomInBothMenuItem);
2281: 
2282:             zoomInMenu.addSeparator();
2283: 
2284:             this.zoomInDomainMenuItem = new JMenuItem(
2285:                     localizationResources.getString("Domain_Axis"));
2286:             this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND);
2287:             this.zoomInDomainMenuItem.addActionListener(this);
2288:             zoomInMenu.add(this.zoomInDomainMenuItem);
2289: 
2290:             this.zoomInRangeMenuItem = new JMenuItem(
2291:                     localizationResources.getString("Range_Axis"));
2292:             this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND);
2293:             this.zoomInRangeMenuItem.addActionListener(this);
2294:             zoomInMenu.add(this.zoomInRangeMenuItem);
2295: 
2296:             result.add(zoomInMenu);
2297: 
2298:             JMenu zoomOutMenu = new JMenu(
2299:                     localizationResources.getString("Zoom_Out"));
2300: 
2301:             this.zoomOutBothMenuItem = new JMenuItem(
2302:                     localizationResources.getString("All_Axes"));
2303:             this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND);
2304:             this.zoomOutBothMenuItem.addActionListener(this);
2305:             zoomOutMenu.add(this.zoomOutBothMenuItem);
2306: 
2307:             zoomOutMenu.addSeparator();
2308: 
2309:             this.zoomOutDomainMenuItem = new JMenuItem(
2310:                     localizationResources.getString("Domain_Axis"));
2311:             this.zoomOutDomainMenuItem.setActionCommand(
2312:                     ZOOM_OUT_DOMAIN_COMMAND);
2313:             this.zoomOutDomainMenuItem.addActionListener(this);
2314:             zoomOutMenu.add(this.zoomOutDomainMenuItem);
2315: 
2316:             this.zoomOutRangeMenuItem = new JMenuItem(
2317:                     localizationResources.getString("Range_Axis"));
2318:             this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND);
2319:             this.zoomOutRangeMenuItem.addActionListener(this);
2320:             zoomOutMenu.add(this.zoomOutRangeMenuItem);
2321: 
2322:             result.add(zoomOutMenu);
2323: 
2324:             JMenu autoRangeMenu = new JMenu(
2325:                     localizationResources.getString("Auto_Range"));
2326: 
2327:             this.zoomResetBothMenuItem = new JMenuItem(
2328:                     localizationResources.getString("All_Axes"));
2329:             this.zoomResetBothMenuItem.setActionCommand(
2330:                     ZOOM_RESET_BOTH_COMMAND);
2331:             this.zoomResetBothMenuItem.addActionListener(this);
2332:             autoRangeMenu.add(this.zoomResetBothMenuItem);
2333: 
2334:             autoRangeMenu.addSeparator();
2335:             this.zoomResetDomainMenuItem = new JMenuItem(
2336:                     localizationResources.getString("Domain_Axis"));
2337:             this.zoomResetDomainMenuItem.setActionCommand(
2338:                     ZOOM_RESET_DOMAIN_COMMAND);
2339:             this.zoomResetDomainMenuItem.addActionListener(this);
2340:             autoRangeMenu.add(this.zoomResetDomainMenuItem);
2341: 
2342:             this.zoomResetRangeMenuItem = new JMenuItem(
2343:                     localizationResources.getString("Range_Axis"));
2344:             this.zoomResetRangeMenuItem.setActionCommand(
2345:                     ZOOM_RESET_RANGE_COMMAND);
2346:             this.zoomResetRangeMenuItem.addActionListener(this);
2347:             autoRangeMenu.add(this.zoomResetRangeMenuItem);
2348: 
2349:             result.addSeparator();
2350:             result.add(autoRangeMenu);
2351: 
2352:         }
2353: 
2354:         return result;
2355: 
2356:     }
2357: 
2358:     /**
2359:      * The idea is to modify the zooming options depending on the type of chart 
2360:      * being displayed by the panel.
2361:      *
2362:      * @param x  horizontal position of the popup.
2363:      * @param y  vertical position of the popup.
2364:      */
2365:     protected void displayPopupMenu(int x, int y) {
2366: 
2367:         if (this.popup != null) {
2368: 
2369:             // go through each zoom menu item and decide whether or not to 
2370:             // enable it...
2371:             Plot plot = this.chart.getPlot();
2372:             boolean isDomainZoomable = false;
2373:             boolean isRangeZoomable = false;
2374:             if (plot instanceof Zoomable) {
2375:                 Zoomable z = (Zoomable) plot;
2376:                 isDomainZoomable = z.isDomainZoomable();
2377:                 isRangeZoomable = z.isRangeZoomable();
2378:             }
2379:             
2380:             if (this.zoomInDomainMenuItem != null) {
2381:                 this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
2382:             }
2383:             if (this.zoomOutDomainMenuItem != null) {
2384:                 this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
2385:             } 
2386:             if (this.zoomResetDomainMenuItem != null) {
2387:                 this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
2388:             }
2389: 
2390:             if (this.zoomInRangeMenuItem != null) {
2391:                 this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
2392:             }
2393:             if (this.zoomOutRangeMenuItem != null) {
2394:                 this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
2395:             }
2396: 
2397:             if (this.zoomResetRangeMenuItem != null) {
2398:                 this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
2399:             }
2400: 
2401:             if (this.zoomInBothMenuItem != null) {
2402:                 this.zoomInBothMenuItem.setEnabled(isDomainZoomable 
2403:                         && isRangeZoomable);
2404:             }
2405:             if (this.zoomOutBothMenuItem != null) {
2406:                 this.zoomOutBothMenuItem.setEnabled(isDomainZoomable 
2407:                         && isRangeZoomable);
2408:             }
2409:             if (this.zoomResetBothMenuItem != null) {
2410:                 this.zoomResetBothMenuItem.setEnabled(isDomainZoomable 
2411:                         && isRangeZoomable);
2412:             }
2413: 
2414:             this.popup.show(this, x, y);
2415:         }
2416: 
2417:     }
2418: 
2419: }