Source for org.jfree.chart.ChartPanel

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