Source for org.jfree.chart.plot.ContourPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, 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:  * ContourPlot.java
  29:  * ----------------
  30:  * (C) Copyright 2002-2005, by David M. O'Donnell and Contributors.
  31:  *
  32:  * Original Author:  David M. O'Donnell;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Arnaud Lelievre;
  35:  *                   Nicolas Brodu;
  36:  *
  37:  * $Id: ContourPlot.java,v 1.16.2.3 2005/10/25 20:52:08 mungady Exp $
  38:  *
  39:  * Changes
  40:  * -------
  41:  * 26-Nov-2002 : Version 1 contributed by David M. O'Donnell (DG);
  42:  * 14-Jan-2003 : Added crosshair attributes (DG);
  43:  * 23-Jan-2003 : Removed two constructors (DG);
  44:  * 21-Mar-2003 : Bug fix 701744 (DG);
  45:  * 26-Mar-2003 : Implemented Serializable (DG);
  46:  * 09-Jul-2003 : Changed ColorBar from extending axis classes to enclosing 
  47:  *               them (DG);
  48:  * 05-Aug-2003 : Applied changes in bug report 780298 (DG);
  49:  * 08-Sep-2003 : Added internationalization via use of properties 
  50:  *               resourceBundle (RFE 690236) (AL);
  51:  * 11-Sep-2003 : Cloning support (NB); 
  52:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  53:  * 17-Jan-2004 : Removed references to DefaultContourDataset class, replaced 
  54:  *               with ContourDataset interface (with changes to the interface). 
  55:  *               See bug 741048 (DG);
  56:  * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
  57:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  58:  * 06-Oct-2004 : Updated for changes in DatasetUtilities class (DG);
  59:  * 11-Nov-2004 : Renamed zoom methods to match ValueAxisPlot interface (DG);
  60:  * 25-Nov-2004 : Small update to clone() implementation (DG);
  61:  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
  62:  * 05-May-2005 : Updated draw() method parameters (DG);
  63:  * 16-Jun-2005 : Added default constructor (DG);
  64:  * 01-Sep-2005 : Moved dataAreaRatio from Plot to here (DG);
  65:  * 
  66:  */
  67: 
  68: package org.jfree.chart.plot;
  69: 
  70: import java.awt.AlphaComposite;
  71: import java.awt.Composite;
  72: import java.awt.Graphics2D;
  73: import java.awt.Paint;
  74: import java.awt.RenderingHints;
  75: import java.awt.Shape;
  76: import java.awt.Stroke;
  77: import java.awt.geom.Ellipse2D;
  78: import java.awt.geom.GeneralPath;
  79: import java.awt.geom.Line2D;
  80: import java.awt.geom.Point2D;
  81: import java.awt.geom.Rectangle2D;
  82: import java.awt.geom.RectangularShape;
  83: import java.beans.PropertyChangeEvent;
  84: import java.beans.PropertyChangeListener;
  85: import java.io.Serializable;
  86: import java.util.Iterator;
  87: import java.util.List;
  88: import java.util.ResourceBundle;
  89: 
  90: import org.jfree.chart.ClipPath;
  91: import org.jfree.chart.annotations.XYAnnotation;
  92: import org.jfree.chart.axis.AxisSpace;
  93: import org.jfree.chart.axis.ColorBar;
  94: import org.jfree.chart.axis.NumberAxis;
  95: import org.jfree.chart.axis.ValueAxis;
  96: import org.jfree.chart.entity.ContourEntity;
  97: import org.jfree.chart.entity.EntityCollection;
  98: import org.jfree.chart.event.AxisChangeEvent;
  99: import org.jfree.chart.event.PlotChangeEvent;
 100: import org.jfree.chart.labels.ContourToolTipGenerator;
 101: import org.jfree.chart.labels.StandardContourToolTipGenerator;
 102: import org.jfree.chart.urls.XYURLGenerator;
 103: import org.jfree.data.Range;
 104: import org.jfree.data.contour.ContourDataset;
 105: import org.jfree.data.general.DatasetChangeEvent;
 106: import org.jfree.data.general.DatasetUtilities;
 107: import org.jfree.ui.RectangleEdge;
 108: import org.jfree.ui.RectangleInsets;
 109: import org.jfree.util.ObjectUtilities;
 110: 
 111: /**
 112:  * A class for creating shaded contours.
 113:  *
 114:  * @author David M. O'Donnell
 115:  */
 116: public class ContourPlot extends Plot implements ContourValuePlot,
 117:                                                  ValueAxisPlot,
 118:                                                  PropertyChangeListener,
 119:                                                  Serializable,
 120:                                                  Cloneable {
 121: 
 122:     /** For serialization. */
 123:     private static final long serialVersionUID = 7861072556590502247L;
 124:     
 125:     /** The default insets. */
 126:     protected static final RectangleInsets DEFAULT_INSETS 
 127:         = new RectangleInsets(2.0, 2.0, 100.0, 10.0);
 128: 
 129:     /** The domain axis (used for the x-values). */
 130:     private ValueAxis domainAxis;
 131: 
 132:     /** The range axis (used for the y-values). */
 133:     private ValueAxis rangeAxis;
 134: 
 135:     /** The dataset. */
 136:     private ContourDataset dataset;
 137:     
 138:     /** The colorbar axis (used for the z-values). */
 139:     private ColorBar colorBar = null;
 140: 
 141:     /** The color bar location. */
 142:     private RectangleEdge colorBarLocation;
 143:     
 144:     /** A flag that controls whether or not a domain crosshair is drawn..*/
 145:     private boolean domainCrosshairVisible;
 146: 
 147:     /** The domain crosshair value. */
 148:     private double domainCrosshairValue;
 149: 
 150:     /** The pen/brush used to draw the crosshair (if any). */
 151:     private transient Stroke domainCrosshairStroke;
 152: 
 153:     /** The color used to draw the crosshair (if any). */
 154:     private transient Paint domainCrosshairPaint;
 155: 
 156:     /** 
 157:      * A flag that controls whether or not the crosshair locks onto actual data
 158:      * points. 
 159:      */
 160:     private boolean domainCrosshairLockedOnData = true;
 161: 
 162:     /** A flag that controls whether or not a range crosshair is drawn..*/
 163:     private boolean rangeCrosshairVisible;
 164: 
 165:     /** The range crosshair value. */
 166:     private double rangeCrosshairValue;
 167: 
 168:     /** The pen/brush used to draw the crosshair (if any). */
 169:     private transient Stroke rangeCrosshairStroke;
 170: 
 171:     /** The color used to draw the crosshair (if any). */
 172:     private transient Paint rangeCrosshairPaint;
 173: 
 174:     /** 
 175:      * A flag that controls whether or not the crosshair locks onto actual data
 176:      * points. 
 177:      */
 178:     private boolean rangeCrosshairLockedOnData = true;
 179: 
 180:     /** 
 181:      * Defines dataArea rectangle as the ratio formed from dividing height by 
 182:      * width (of the dataArea).  Modifies plot area calculations.
 183:      * ratio>0 will attempt to layout the plot so that the
 184:      * dataArea.height/dataArea.width = ratio.
 185:      * ratio<0 will attempt to layout the plot so that the
 186:      * dataArea.height/dataArea.width in plot units (not java2D units as when 
 187:      * ratio>0) = -1.*ratio.
 188:      */         //dmo
 189:     private double dataAreaRatio = 0.0;  //zero when the parameter is not set
 190: 
 191:     /** A list of markers (optional) for the domain axis. */
 192:     private List domainMarkers;
 193: 
 194:     /** A list of markers (optional) for the range axis. */
 195:     private List rangeMarkers;
 196: 
 197:     /** A list of annotations (optional) for the plot. */
 198:     private List annotations;
 199: 
 200:     /** The tool tip generator. */
 201:     private ContourToolTipGenerator toolTipGenerator;
 202: 
 203:     /** The URL text generator. */
 204:     private XYURLGenerator urlGenerator;
 205: 
 206:     /** 
 207:      * Controls whether data are render as filled rectangles or rendered as 
 208:      * points 
 209:      */
 210:     private boolean renderAsPoints = false;
 211: 
 212:     /** 
 213:      * Size of points rendered when renderAsPoints = true.  Size is relative to
 214:      * dataArea 
 215:      */
 216:     private double ptSizePct = 0.05;
 217: 
 218:     /** Contains the a ClipPath to "trim" the contours. */
 219:     private transient ClipPath clipPath = null;
 220: 
 221:     /** Set to Paint to represent missing values. */
 222:     private transient Paint missingPaint = null;
 223: 
 224:     /** The resourceBundle for the localization. */
 225:     protected static ResourceBundle localizationResources = 
 226:         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
 227: 
 228:     /**
 229:      * Creates a new plot with no dataset or axes.
 230:      */
 231:     public ContourPlot() {
 232:         this(null, null, null, null);
 233:     }
 234:     
 235:     /**
 236:      * Constructs a contour plot with the specified axes (other attributes take
 237:      * default values).
 238:      *
 239:      * @param dataset  The dataset.
 240:      * @param domainAxis  The domain axis.
 241:      * @param rangeAxis  The range axis.
 242:      * @param colorBar  The z-axis axis.
 243:     */
 244:     public ContourPlot(ContourDataset dataset,
 245:                        ValueAxis domainAxis, ValueAxis rangeAxis, 
 246:                        ColorBar colorBar) {
 247: 
 248:         super();
 249: 
 250:         this.dataset = dataset;
 251:         if (dataset != null) {
 252:             dataset.addChangeListener(this);
 253:         }
 254:         
 255:         this.domainAxis = domainAxis;
 256:         if (domainAxis != null) {
 257:             domainAxis.setPlot(this);
 258:             domainAxis.addChangeListener(this);
 259:         }
 260: 
 261:         this.rangeAxis = rangeAxis;
 262:         if (rangeAxis != null) {
 263:             rangeAxis.setPlot(this);
 264:             rangeAxis.addChangeListener(this);
 265:         }
 266: 
 267:         this.colorBar = colorBar;
 268:         if (colorBar != null) {
 269:             colorBar.getAxis().setPlot(this);
 270:             colorBar.getAxis().addChangeListener(this);
 271:             colorBar.configure(this);
 272:         }
 273:         this.colorBarLocation = RectangleEdge.LEFT;
 274: 
 275:         this.toolTipGenerator = new StandardContourToolTipGenerator();
 276: 
 277:     }
 278: 
 279:     /**
 280:      * Returns the color bar location.
 281:      * 
 282:      * @return The color bar location.
 283:      */
 284:     public RectangleEdge getColorBarLocation() {
 285:         return this.colorBarLocation;
 286:     }
 287:     
 288:     /**
 289:      * Sets the color bar location and sends a {@link PlotChangeEvent} to all 
 290:      * registered listeners.
 291:      * 
 292:      * @param edge  the location.
 293:      */
 294:     public void setColorBarLocation(RectangleEdge edge) {
 295:         this.colorBarLocation = edge;
 296:         notifyListeners(new PlotChangeEvent(this));    
 297:     }
 298:     
 299:     /**
 300:      * Returns the primary dataset for the plot.
 301:      * 
 302:      * @return The primary dataset (possibly <code>null</code>).
 303:      */
 304:     public ContourDataset getDataset() {
 305:         return this.dataset;
 306:     }
 307:     
 308:     /**
 309:      * Sets the dataset for the plot, replacing the existing dataset if there
 310:      * is one.
 311:      * 
 312:      * @param dataset  the dataset (<code>null</code> permitted).
 313:      */
 314:     public void setDataset(ContourDataset dataset) {
 315:         
 316:         // if there is an existing dataset, remove the plot from the list of 
 317:         // change listeners...
 318:         ContourDataset existing = this.dataset;
 319:         if (existing != null) {
 320:             existing.removeChangeListener(this);
 321:         }
 322: 
 323:         // set the new dataset, and register the chart as a change listener...
 324:         this.dataset = dataset;
 325:         if (dataset != null) {
 326:             setDatasetGroup(dataset.getGroup());
 327:             dataset.addChangeListener(this);
 328:         }
 329: 
 330:         // send a dataset change event to self...
 331:         DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
 332:         datasetChanged(event);
 333:         
 334:     }
 335: 
 336:     /**
 337:      * Returns the domain axis for the plot.
 338:      *
 339:      * @return The domain axis.
 340:      */
 341:     public ValueAxis getDomainAxis() {
 342: 
 343:         ValueAxis result = this.domainAxis;
 344: 
 345:         return result;
 346: 
 347:     }
 348: 
 349:     /**
 350:      * Sets the domain axis for the plot (this must be compatible with the plot
 351:      * type or an exception is thrown).
 352:      *
 353:      * @param axis The new axis.
 354:      */
 355:     public void setDomainAxis(ValueAxis axis) {
 356: 
 357:         if (isCompatibleDomainAxis(axis)) {
 358: 
 359:             if (axis != null) {
 360:                 axis.setPlot(this);
 361:                 axis.addChangeListener(this);
 362:             }
 363: 
 364:             // plot is likely registered as a listener with the existing axis...
 365:             if (this.domainAxis != null) {
 366:                 this.domainAxis.removeChangeListener(this);
 367:             }
 368: 
 369:             this.domainAxis = axis;
 370:             notifyListeners(new PlotChangeEvent(this));
 371: 
 372:         }
 373: 
 374:     }
 375: 
 376:     /**
 377:      * Returns the range axis for the plot.
 378:      *
 379:      * @return The range axis.
 380:      */
 381:     public ValueAxis getRangeAxis() {
 382: 
 383:         ValueAxis result = this.rangeAxis;
 384: 
 385:         return result;
 386: 
 387:     }
 388: 
 389:     /**
 390:      * Sets the range axis for the plot.
 391:      * <P>
 392:      * An exception is thrown if the new axis and the plot are not mutually
 393:      * compatible.
 394:      *
 395:      * @param axis The new axis (null permitted).
 396:      */
 397:     public void setRangeAxis(ValueAxis axis) {
 398: 
 399:         if (axis != null) {
 400:             axis.setPlot(this);
 401:             axis.addChangeListener(this);
 402:         }
 403: 
 404:         // plot is likely registered as a listener with the existing axis...
 405:         if (this.rangeAxis != null) {
 406:             this.rangeAxis.removeChangeListener(this);
 407:         }
 408: 
 409:         this.rangeAxis = axis;
 410:         notifyListeners(new PlotChangeEvent(this));
 411: 
 412:     }
 413: 
 414:     /**
 415:      * Sets the colorbar for the plot.
 416:      *
 417:      * @param axis The new axis (null permitted).
 418:      */
 419:     public void setColorBarAxis(ColorBar axis) {
 420: 
 421:         this.colorBar = axis;
 422:         notifyListeners(new PlotChangeEvent(this));
 423: 
 424:     }
 425: 
 426:     /**
 427:      * Returns the data area ratio.
 428:      *
 429:      * @return The ratio.
 430:      */
 431:     public double getDataAreaRatio() {
 432:         return this.dataAreaRatio;
 433:     }
 434: 
 435:     /**
 436:      * Sets the data area ratio.
 437:      *
 438:      * @param ratio  the ratio.
 439:      */
 440:     public void setDataAreaRatio(double ratio) {
 441:         this.dataAreaRatio = ratio;
 442:     }
 443: 
 444:     /**
 445:      * Adds a marker for the domain axis.
 446:      * <P>
 447:      * Typically a marker will be drawn by the renderer as a line perpendicular
 448:      * to the range axis, however this is entirely up to the renderer.
 449:      *
 450:      * @param marker the marker.
 451:      */
 452:     public void addDomainMarker(Marker marker) {
 453: 
 454:         if (this.domainMarkers == null) {
 455:             this.domainMarkers = new java.util.ArrayList();
 456:         }
 457:         this.domainMarkers.add(marker);
 458:         notifyListeners(new PlotChangeEvent(this));
 459: 
 460:     }
 461: 
 462:     /**
 463:      * Clears all the domain markers.
 464:      */
 465:     public void clearDomainMarkers() {
 466:         if (this.domainMarkers != null) {
 467:             this.domainMarkers.clear();
 468:             notifyListeners(new PlotChangeEvent(this));
 469:         }
 470:     }
 471: 
 472:     /**
 473:      * Adds a marker for the range axis.
 474:      * <P>
 475:      * Typically a marker will be drawn by the renderer as a line perpendicular
 476:      * to the range axis, however this is entirely up to the renderer.
 477:      *
 478:      * @param marker The marker.
 479:      */
 480:     public void addRangeMarker(Marker marker) {
 481: 
 482:         if (this.rangeMarkers == null) {
 483:             this.rangeMarkers = new java.util.ArrayList();
 484:         }
 485:         this.rangeMarkers.add(marker);
 486:         notifyListeners(new PlotChangeEvent(this));
 487: 
 488:     }
 489: 
 490:     /**
 491:      * Clears all the range markers.
 492:      */
 493:     public void clearRangeMarkers() {
 494:         if (this.rangeMarkers != null) {
 495:             this.rangeMarkers.clear();
 496:             notifyListeners(new PlotChangeEvent(this));
 497:         }
 498:     }
 499: 
 500:     /**
 501:      * Adds an annotation to the plot.
 502:      *
 503:      * @param annotation  the annotation.
 504:      */
 505:     public void addAnnotation(XYAnnotation annotation) {
 506: 
 507:         if (this.annotations == null) {
 508:             this.annotations = new java.util.ArrayList();
 509:         }
 510:         this.annotations.add(annotation);
 511:         notifyListeners(new PlotChangeEvent(this));
 512: 
 513:     }
 514: 
 515:     /**
 516:      * Clears all the annotations.
 517:      */
 518:     public void clearAnnotations() {
 519:         if (this.annotations != null) {
 520:             this.annotations.clear();
 521:             notifyListeners(new PlotChangeEvent(this));
 522:         }
 523:     }
 524: 
 525:     /**
 526:      * Checks the compatibility of a domain axis, returning true if the axis is
 527:      * compatible with the plot, and false otherwise.
 528:      *
 529:      * @param axis The proposed axis.
 530:      *
 531:      * @return <code>true</code> if the axis is compatible with the plot.
 532:      */
 533:     public boolean isCompatibleDomainAxis(ValueAxis axis) {
 534: 
 535:         return true;
 536: 
 537:     }
 538: 
 539:     /**
 540:      * Draws the plot on a Java 2D graphics device (such as the screen or a 
 541:      * printer).
 542:      * <P>
 543:      * The optional <code>info</code> argument collects information about the 
 544:      * rendering of the plot (dimensions, tooltip information etc).  Just pass
 545:      * in <code>null</code> if you do not need this information.
 546:      *
 547:      * @param g2  the graphics device.
 548:      * @param area  the area within which the plot (including axis labels)
 549:      *              should be drawn.
 550:      * @param anchor  the anchor point (<code>null</code> permitted).
 551:      * @param parentState  the state from the parent plot, if there is one.
 552:      * @param info  collects chart drawing information (<code>null</code> 
 553:      *              permitted).
 554:      */
 555:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
 556:                      PlotState parentState,
 557:                      PlotRenderingInfo info) {
 558: 
 559:         // if the plot area is too small, just return...
 560:         boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
 561:         boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
 562:         if (b1 || b2) {
 563:             return;
 564:         }
 565: 
 566:         // record the plot area...
 567:         if (info != null) {
 568:             info.setPlotArea(area);
 569:         }
 570: 
 571:         // adjust the drawing area for plot insets (if any)...
 572:         RectangleInsets insets = getInsets();
 573:         insets.trim(area);
 574: 
 575:         AxisSpace space = new AxisSpace();
 576:         
 577:         space = this.domainAxis.reserveSpace(
 578:             g2, this, area, RectangleEdge.BOTTOM, space
 579:         );
 580:         space = this.rangeAxis.reserveSpace(
 581:             g2, this, area, RectangleEdge.LEFT, space
 582:         );
 583: 
 584:         Rectangle2D estimatedDataArea = space.shrink(area, null);
 585:         
 586:         AxisSpace space2 = new AxisSpace();
 587:         space2 = this.colorBar.reserveSpace(
 588:             g2, this, area, estimatedDataArea, this.colorBarLocation, 
 589:             space2
 590:         );
 591:         Rectangle2D adjustedPlotArea = space2.shrink(area, null);
 592:         
 593:         Rectangle2D dataArea = space.shrink(adjustedPlotArea, null);
 594: 
 595:         Rectangle2D colorBarArea = space2.reserved(
 596:             area, this.colorBarLocation
 597:         );
 598: 
 599:         // additional dataArea modifications
 600:         if (getDataAreaRatio() != 0.0) { //check whether modification is
 601:             double ratio = getDataAreaRatio();
 602:             Rectangle2D tmpDataArea = (Rectangle2D) dataArea.clone();
 603:             double h = tmpDataArea.getHeight();
 604:             double w = tmpDataArea.getWidth();
 605: 
 606:             if (ratio > 0) { // ratio represents pixels
 607:                 if (w * ratio <= h) {
 608:                     h = ratio * w;
 609:                 }
 610:                 else {
 611:                     w = h / ratio;
 612:                 }
 613:             }
 614:             else {  // ratio represents axis units
 615:                 ratio *= -1.0;
 616:                 double xLength = getDomainAxis().getRange().getLength();
 617:                 double yLength = getRangeAxis().getRange().getLength();
 618:                 double unitRatio = yLength / xLength;
 619: 
 620:                 ratio = unitRatio * ratio;
 621: 
 622:                 if (w * ratio <= h) {
 623:                     h = ratio * w;
 624:                 }
 625:                 else {
 626:                     w = h / ratio;
 627:                 }
 628:             }
 629: 
 630:             dataArea.setRect(
 631:                 tmpDataArea.getX() + tmpDataArea.getWidth() / 2 - w / 2,
 632:                 tmpDataArea.getY(), w, h
 633:             );
 634:         }
 635: 
 636:         if (info != null) {
 637:             info.setDataArea(dataArea);
 638:         }
 639: 
 640:         CrosshairState crosshairState = new CrosshairState();
 641:         crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
 642: 
 643:         // draw the plot background...
 644:         drawBackground(g2, dataArea);
 645: 
 646:         double cursor = dataArea.getMaxY();
 647:         if (this.domainAxis != null) {
 648:             this.domainAxis.draw(
 649:                 g2, cursor, adjustedPlotArea, dataArea, RectangleEdge.BOTTOM, 
 650:                 info
 651:             );
 652:         }
 653: 
 654:         if (this.rangeAxis != null) {
 655:             cursor = dataArea.getMinX();
 656:             this.rangeAxis.draw(
 657:                 g2, cursor, adjustedPlotArea, dataArea, RectangleEdge.LEFT, info
 658:             );
 659:         }
 660: 
 661:         if (this.colorBar != null) {
 662:             cursor = 0.0;
 663:             cursor = this.colorBar.draw(
 664:                 g2, cursor, adjustedPlotArea, dataArea, colorBarArea, 
 665:                 this.colorBarLocation
 666:             );
 667:         }
 668:         Shape originalClip = g2.getClip();
 669:         Composite originalComposite = g2.getComposite();
 670: 
 671:         g2.clip(dataArea);
 672:         g2.setComposite(AlphaComposite.getInstance(
 673:             AlphaComposite.SRC_OVER, getForegroundAlpha())
 674:         );
 675:         render(g2, dataArea, info, crosshairState);
 676: 
 677:         if (this.domainMarkers != null) {
 678:             Iterator iterator = this.domainMarkers.iterator();
 679:             while (iterator.hasNext()) {
 680:                 Marker marker = (Marker) iterator.next();
 681:                 drawDomainMarker(g2, this, getDomainAxis(), marker, dataArea);
 682:             }
 683:         }
 684: 
 685:         if (this.rangeMarkers != null) {
 686:             Iterator iterator = this.rangeMarkers.iterator();
 687:             while (iterator.hasNext()) {
 688:                 Marker marker = (Marker) iterator.next();
 689:                 drawRangeMarker(g2, this, getRangeAxis(), marker, dataArea);
 690:             }
 691:         }
 692: 
 693: // TO DO:  these annotations only work with XYPlot, see if it is possible to 
 694: // make ContourPlot a subclass of XYPlot (DG);
 695: 
 696: //        // draw the annotations...
 697: //        if (this.annotations != null) {
 698: //            Iterator iterator = this.annotations.iterator();
 699: //            while (iterator.hasNext()) {
 700: //                Annotation annotation = (Annotation) iterator.next();
 701: //                if (annotation instanceof XYAnnotation) {
 702: //                    XYAnnotation xya = (XYAnnotation) annotation;
 703: //                    // get the annotation to draw itself...
 704: //                    xya.draw(g2, this, dataArea, getDomainAxis(), 
 705: //                             getRangeAxis());
 706: //                }
 707: //            }
 708: //        }
 709: 
 710:         g2.setClip(originalClip);
 711:         g2.setComposite(originalComposite);
 712:         drawOutline(g2, dataArea);
 713: 
 714:     }
 715: 
 716:     /**
 717:      * Draws a representation of the data within the dataArea region, using the
 718:      * current renderer.
 719:      * <P>
 720:      * The <code>info</code> and <code>crosshairState</code> arguments may be 
 721:      * <code>null</code>.
 722:      *
 723:      * @param g2  the graphics device.
 724:      * @param dataArea  the region in which the data is to be drawn.
 725:      * @param info  an optional object for collection dimension information.
 726:      * @param crosshairState  an optional object for collecting crosshair info.
 727:      */
 728:     public void render(Graphics2D g2, Rectangle2D dataArea,
 729:                        PlotRenderingInfo info, CrosshairState crosshairState) {
 730: 
 731:         // now get the data and plot it (the visual representation will depend
 732:         // on the renderer that has been set)...
 733:         ContourDataset data = getDataset();
 734:         if (data != null) {
 735: 
 736:             ColorBar zAxis = getColorBar();
 737: 
 738:             if (this.clipPath != null) {
 739:                 GeneralPath clipper = getClipPath().draw(
 740:                     g2, dataArea, this.domainAxis, this.rangeAxis
 741:                 );
 742:                 if (this.clipPath.isClip()) {
 743:                     g2.clip(clipper);
 744:                 }
 745:             }
 746: 
 747:             if (this.renderAsPoints) {
 748:                 pointRenderer(g2, dataArea, info, this,
 749:                         this.domainAxis, this.rangeAxis, zAxis,
 750:                               data, crosshairState);
 751:             }
 752:             else {
 753:                 contourRenderer(g2, dataArea, info, this,
 754:                         this.domainAxis, this.rangeAxis, zAxis,
 755:                                 data, crosshairState);
 756:             }
 757: 
 758:             // draw vertical crosshair if required...
 759:             setDomainCrosshairValue(crosshairState.getCrosshairX(), false);
 760:             if (isDomainCrosshairVisible()) {
 761:                 drawVerticalLine(g2, dataArea,
 762:                                  getDomainCrosshairValue(),
 763:                                  getDomainCrosshairStroke(),
 764:                                  getDomainCrosshairPaint());
 765:             }
 766: 
 767:             // draw horizontal crosshair if required...
 768:             setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
 769:             if (isRangeCrosshairVisible()) {
 770:                 drawHorizontalLine(g2, dataArea,
 771:                                    getRangeCrosshairValue(),
 772:                                    getRangeCrosshairStroke(),
 773:                                    getRangeCrosshairPaint());
 774:             }
 775: 
 776:         }
 777:         else if (this.clipPath != null) {
 778:             getClipPath().draw(g2, dataArea, this.domainAxis, this.rangeAxis);
 779:         }
 780: 
 781:     }
 782: 
 783:     /**
 784:      * Fills the plot.
 785:      *
 786:      * @param g2  the graphics device.
 787:      * @param dataArea  the area within which the data is being drawn.
 788:      * @param info  collects information about the drawing.
 789:      * @param plot  the plot (can be used to obtain standard color 
 790:      *              information etc).
 791:      * @param horizontalAxis  the domain (horizontal) axis.
 792:      * @param verticalAxis  the range (vertical) axis.
 793:      * @param colorBar  the color bar axis.
 794:      * @param data  the dataset.
 795:      * @param crosshairState  information about crosshairs on a plot.
 796:      */
 797:     public void contourRenderer(Graphics2D g2,
 798:                                 Rectangle2D dataArea,
 799:                                 PlotRenderingInfo info,
 800:                                 ContourPlot plot,
 801:                                 ValueAxis horizontalAxis,
 802:                                 ValueAxis verticalAxis,
 803:                                 ColorBar colorBar,
 804:                                 ContourDataset data,
 805:                                 CrosshairState crosshairState) {
 806: 
 807:         // setup for collecting optional entity info...
 808:         Rectangle2D.Double entityArea = null;
 809:         EntityCollection entities = null;
 810:         if (info != null) {
 811:             entities = info.getOwner().getEntityCollection();
 812:         }
 813: 
 814:         Rectangle2D.Double rect = null;
 815:         rect = new Rectangle2D.Double();
 816: 
 817:         //turn off anti-aliasing when filling rectangles
 818:         Object antiAlias = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
 819:         g2.setRenderingHint(
 820:             RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF
 821:         );
 822: 
 823:         // get the data points
 824:         Number[] xNumber = data.getXValues();
 825:         Number[] yNumber = data.getYValues();
 826:         Number[] zNumber = data.getZValues();
 827: 
 828:         double[] x = new double[xNumber.length];
 829:         double[] y = new double[yNumber.length];
 830: 
 831:         for (int i = 0; i < x.length; i++) {
 832:             x[i] = xNumber[i].doubleValue();
 833:             y[i] = yNumber[i].doubleValue();
 834:         }
 835: 
 836:         int[] xIndex = data.indexX();
 837:         int[] indexX = data.getXIndices();
 838:         boolean vertInverted = ((NumberAxis) verticalAxis).isInverted();
 839:         boolean horizInverted = false;
 840:         if (horizontalAxis instanceof NumberAxis) {
 841:             horizInverted = ((NumberAxis) horizontalAxis).isInverted();
 842:         }
 843:         double transX = 0.0;
 844:         double transXm1 = 0.0;
 845:         double transXp1 = 0.0;
 846:         double transDXm1 = 0.0;
 847:         double transDXp1 = 0.0;
 848:         double transDX = 0.0;
 849:         double transY = 0.0;
 850:         double transYm1 = 0.0;
 851:         double transYp1 = 0.0;
 852:         double transDYm1 = 0.0;
 853:         double transDYp1 = 0.0;
 854:         double transDY = 0.0;
 855:         int iMax = xIndex[xIndex.length - 1];
 856:         for (int k = 0; k < x.length; k++) {
 857:             int i = xIndex[k];
 858:             if (indexX[i] == k) { // this is a new column
 859:                 if (i == 0) {
 860:                     transX = horizontalAxis.valueToJava2D(
 861:                         x[k], dataArea, RectangleEdge.BOTTOM
 862:                     );
 863:                     transXm1 = transX;
 864:                     transXp1 = horizontalAxis.valueToJava2D(
 865:                         x[indexX[i + 1]], dataArea, RectangleEdge.BOTTOM
 866:                     );
 867:                     transDXm1 = Math.abs(0.5 * (transX - transXm1));
 868:                     transDXp1 = Math.abs(0.5 * (transX - transXp1));
 869:                 }
 870:                 else if (i == iMax) {
 871:                     transX = horizontalAxis.valueToJava2D(
 872:                         x[k], dataArea, RectangleEdge.BOTTOM
 873:                     );
 874:                     transXm1 = horizontalAxis.valueToJava2D(
 875:                         x[indexX[i - 1]], dataArea, RectangleEdge.BOTTOM
 876:                     );
 877:                     transXp1 = transX;
 878:                     transDXm1 = Math.abs(0.5 * (transX - transXm1));
 879:                     transDXp1 = Math.abs(0.5 * (transX - transXp1));
 880:                 }
 881:                 else {
 882:                     transX = horizontalAxis.valueToJava2D(
 883:                         x[k], dataArea, RectangleEdge.BOTTOM
 884:                     );
 885:                     transXp1 = horizontalAxis.valueToJava2D(
 886:                         x[indexX[i + 1]], dataArea, RectangleEdge.BOTTOM
 887:                     );
 888:                     transDXm1 = transDXp1;
 889:                     transDXp1 = Math.abs(0.5 * (transX - transXp1));
 890:                 }
 891: 
 892:                 if (horizInverted) {
 893:                     transX -= transDXp1;
 894:                 }
 895:                 else {
 896:                     transX -= transDXm1;
 897:                 }
 898: 
 899:                 transDX = transDXm1 + transDXp1;
 900: 
 901:                 transY = verticalAxis.valueToJava2D(
 902:                     y[k], dataArea, RectangleEdge.LEFT
 903:                 );
 904:                 transYm1 = transY;
 905:                 if (k + 1 == y.length) {
 906:                     continue;
 907:                 }
 908:                 transYp1 = verticalAxis.valueToJava2D(
 909:                     y[k + 1], dataArea, RectangleEdge.LEFT
 910:                 );
 911:                 transDYm1 = Math.abs(0.5 * (transY - transYm1));
 912:                 transDYp1 = Math.abs(0.5 * (transY - transYp1));
 913:             }
 914:             else if ((i < indexX.length - 1 
 915:                      && indexX[i + 1] - 1 == k) || k == x.length - 1) {
 916:                 // end of column
 917:                 transY = verticalAxis.valueToJava2D(
 918:                     y[k], dataArea, RectangleEdge.LEFT
 919:                 );
 920:                 transYm1 = verticalAxis.valueToJava2D(
 921:                     y[k - 1], dataArea, RectangleEdge.LEFT
 922:                 );
 923:                 transYp1 = transY;
 924:                 transDYm1 = Math.abs(0.5 * (transY - transYm1));
 925:                 transDYp1 = Math.abs(0.5 * (transY - transYp1));
 926:             }
 927:             else {
 928:                 transY = verticalAxis.valueToJava2D(
 929:                     y[k], dataArea, RectangleEdge.LEFT
 930:                 );
 931:                 transYp1 = verticalAxis.valueToJava2D(
 932:                     y[k + 1], dataArea, RectangleEdge.LEFT
 933:                 );
 934:                 transDYm1 = transDYp1;
 935:                 transDYp1 = Math.abs(0.5 * (transY - transYp1));
 936:             }
 937:             if (vertInverted) {
 938:                 transY -= transDYm1;
 939:             }
 940:             else {
 941:                 transY -= transDYp1;
 942:             }
 943: 
 944:             transDY = transDYm1 + transDYp1;
 945: 
 946:             rect.setRect(transX, transY, transDX, transDY);
 947:             if (zNumber[k] != null) {
 948:                 g2.setPaint(colorBar.getPaint(zNumber[k].doubleValue()));
 949:                 g2.fill(rect);
 950:             }
 951:             else if (this.missingPaint != null) {
 952:                 g2.setPaint(this.missingPaint);
 953:                 g2.fill(rect);
 954:             }
 955: 
 956:             entityArea = rect;
 957: 
 958:             // add an entity for the item...
 959:             if (entities != null) {
 960:                 String tip = "";
 961:                 if (getToolTipGenerator() != null) {
 962:                     tip = this.toolTipGenerator.generateToolTip(data, k);
 963:                 }
 964: //              Shape s = g2.getClip();
 965: //              if (s.contains(rect) || s.intersects(rect)) {
 966:                 String url = null;
 967:                 // if (getURLGenerator() != null) {    //dmo: look at this later
 968:                 //      url = getURLGenerator().generateURL(data, series, item);
 969:                 // }
 970:                 // Unlike XYItemRenderer, we need to clone entityArea since it 
 971:                 // reused.
 972:                 ContourEntity entity = new ContourEntity(
 973:                     (Rectangle2D.Double) entityArea.clone(), tip, url
 974:                 );
 975:                 entity.setIndex(k);
 976:                 entities.add(entity);
 977: //              }
 978:             }
 979: 
 980:             // do we need to update the crosshair values?
 981:             if (plot.isDomainCrosshairLockedOnData()) {
 982:                 if (plot.isRangeCrosshairLockedOnData()) {
 983:                     // both axes
 984:                     crosshairState.updateCrosshairPoint(
 985:                         x[k], y[k], transX, transY, PlotOrientation.VERTICAL
 986:                     );
 987:                 }
 988:                 else {
 989:                     // just the horizontal axis...
 990:                     crosshairState.updateCrosshairX(transX);
 991:                 }
 992:             }
 993:             else {
 994:                 if (plot.isRangeCrosshairLockedOnData()) {
 995:                     // just the vertical axis...
 996:                     crosshairState.updateCrosshairY(transY);
 997:                 }
 998:             }
 999:         }
1000: 
1001:         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias);
1002: 
1003:         return;
1004: 
1005:     }
1006: 
1007:     /**
1008:      * Draws the visual representation of a single data item.
1009:      *
1010:      * @param g2  the graphics device.
1011:      * @param dataArea  the area within which the data is being drawn.
1012:      * @param info  collects information about the drawing.
1013:      * @param plot  the plot (can be used to obtain standard color 
1014:      *              information etc).
1015:      * @param domainAxis  the domain (horizontal) axis.
1016:      * @param rangeAxis  the range (vertical) axis.
1017:      * @param colorBar  the color bar axis.
1018:      * @param data  the dataset.
1019:      * @param crosshairState  information about crosshairs on a plot.
1020:      */
1021:     public void pointRenderer(Graphics2D g2,
1022:                               Rectangle2D dataArea,
1023:                               PlotRenderingInfo info,
1024:                               ContourPlot plot,
1025:                               ValueAxis domainAxis,
1026:                               ValueAxis rangeAxis,
1027:                               ColorBar colorBar,
1028:                               ContourDataset data,
1029:                               CrosshairState crosshairState) {
1030: 
1031:         // setup for collecting optional entity info...
1032:         RectangularShape entityArea = null;
1033:         EntityCollection entities = null;
1034:         if (info != null) {
1035:             entities = info.getOwner().getEntityCollection();
1036:         }
1037: 
1038: //      Rectangle2D.Double rect = null;
1039: //      rect = new Rectangle2D.Double();
1040:         RectangularShape rect = new Ellipse2D.Double();
1041: 
1042: 
1043:         //turn off anti-aliasing when filling rectangles
1044:         Object antiAlias = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
1045:         g2.setRenderingHint(
1046:             RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF
1047:         );
1048: 
1049:         // if (tooltips!=null) tooltips.clearToolTips(); // reset collection
1050:         // get the data points
1051:         Number[] xNumber = data.getXValues();
1052:         Number[] yNumber = data.getYValues();
1053:         Number[] zNumber = data.getZValues();
1054: 
1055:         double[] x = new double[xNumber.length];
1056:         double[] y = new double[yNumber.length];
1057: 
1058:         for (int i = 0; i < x.length; i++) {
1059:             x[i] = xNumber[i].doubleValue();
1060:             y[i] = yNumber[i].doubleValue();
1061:         }
1062: 
1063:         double transX = 0.0;
1064:         double transDX = 0.0;
1065:         double transY = 0.0;
1066:         double transDY = 0.0;
1067:         double size = dataArea.getWidth() * this.ptSizePct;
1068:         for (int k = 0; k < x.length; k++) {
1069: 
1070:             transX = domainAxis.valueToJava2D(
1071:                 x[k], dataArea, RectangleEdge.BOTTOM
1072:             ) - 0.5 * size;
1073:             transY = rangeAxis.valueToJava2D(y[k], dataArea, RectangleEdge.LEFT)
1074:                      - 0.5 * size;
1075:             transDX = size;
1076:             transDY = size;
1077: 
1078:             rect.setFrame(transX, transY, transDX, transDY);
1079: 
1080:             if (zNumber[k] != null) {
1081:                 g2.setPaint(colorBar.getPaint(zNumber[k].doubleValue()));
1082:                 g2.fill(rect);
1083:             }
1084:             else if (this.missingPaint != null) {
1085:                 g2.setPaint(this.missingPaint);
1086:                 g2.fill(rect);
1087:             }
1088: 
1089: 
1090:             entityArea = rect;
1091: 
1092:             // add an entity for the item...
1093:             if (entities != null) {
1094:                 String tip = null;
1095:                 if (getToolTipGenerator() != null) {
1096:                     tip = this.toolTipGenerator.generateToolTip(data, k);
1097:                 }
1098:                 String url = null;
1099:                 // if (getURLGenerator() != null) {   //dmo: look at this later
1100:                 //   url = getURLGenerator().generateURL(data, series, item);
1101:                 // }
1102:                 // Unlike XYItemRenderer, we need to clone entityArea since it 
1103:                 // reused.
1104:                 ContourEntity entity = new ContourEntity(
1105:                     (RectangularShape) entityArea.clone(), tip, url
1106:                 );
1107:                 entity.setIndex(k);
1108:                 entities.add(entity);
1109:             }
1110: 
1111:             // do we need to update the crosshair values?
1112:             if (plot.isDomainCrosshairLockedOnData()) {
1113:                 if (plot.isRangeCrosshairLockedOnData()) {
1114:                     // both axes
1115:                     crosshairState.updateCrosshairPoint(
1116:                         x[k], y[k], transX, transY, PlotOrientation.VERTICAL
1117:                     );
1118:                 }
1119:                 else {
1120:                     // just the horizontal axis...
1121:                     crosshairState.updateCrosshairX(transX);
1122:                 }
1123:             }
1124:             else {
1125:                 if (plot.isRangeCrosshairLockedOnData()) {
1126:                     // just the vertical axis...
1127:                     crosshairState.updateCrosshairY(transY);
1128:                 }
1129:             }
1130:         }
1131: 
1132: 
1133:         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias);
1134: 
1135:         return;
1136: 
1137:     }
1138: 
1139:     /**
1140:      * Utility method for drawing a crosshair on the chart (if required).
1141:      *
1142:      * @param g2  The graphics device.
1143:      * @param dataArea  The data area.
1144:      * @param value  The coordinate, where to draw the line.
1145:      * @param stroke  The stroke to use.
1146:      * @param paint  The paint to use.
1147:      */
1148:     protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea,
1149:                                     double value, Stroke stroke, Paint paint) {
1150: 
1151:         double xx = getDomainAxis().valueToJava2D(
1152:             value, dataArea, RectangleEdge.BOTTOM
1153:         );
1154:         Line2D line = new Line2D.Double(
1155:             xx, dataArea.getMinY(), xx, dataArea.getMaxY()
1156:         );
1157:         g2.setStroke(stroke);
1158:         g2.setPaint(paint);
1159:         g2.draw(line);
1160: 
1161:     }
1162: 
1163:     /**
1164:      * Utility method for drawing a crosshair on the chart (if required).
1165:      *
1166:      * @param g2  The graphics device.
1167:      * @param dataArea  The data area.
1168:      * @param value  The coordinate, where to draw the line.
1169:      * @param stroke  The stroke to use.
1170:      * @param paint  The paint to use.
1171:      */
1172:     protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea,
1173:                                       double value, Stroke stroke, 
1174:                                       Paint paint) {
1175: 
1176:         double yy = getRangeAxis().valueToJava2D(
1177:             value, dataArea, RectangleEdge.LEFT
1178:         );
1179:         Line2D line = new Line2D.Double(
1180:             dataArea.getMinX(), yy, dataArea.getMaxX(), yy
1181:         );
1182:         g2.setStroke(stroke);
1183:         g2.setPaint(paint);
1184:         g2.draw(line);
1185: 
1186:     }
1187: 
1188:     /**
1189:      * Handles a 'click' on the plot by updating the anchor values...
1190:      *
1191:      * @param x  x-coordinate, where the click occured.
1192:      * @param y  y-coordinate, where the click occured.
1193:      * @param info  An object for collection dimension information.
1194:      */
1195:     public void handleClick(int x, int y, PlotRenderingInfo info) {
1196: 
1197: /*        // set the anchor value for the horizontal axis...
1198:         ValueAxis hva = getDomainAxis();
1199:         if (hva != null) {
1200:             double hvalue = hva.translateJava2DtoValue(
1201:                 (float) x, info.getDataArea()
1202:             );
1203: 
1204:             hva.setAnchorValue(hvalue);
1205:             setDomainCrosshairValue(hvalue);
1206:         }
1207: 
1208:         // set the anchor value for the vertical axis...
1209:         ValueAxis vva = getRangeAxis();
1210:         if (vva != null) {
1211:             double vvalue = vva.translateJava2DtoValue(
1212:                 (float) y, info.getDataArea()
1213:             );
1214:             vva.setAnchorValue(vvalue);
1215:             setRangeCrosshairValue(vvalue);
1216:         }
1217: */
1218:     }
1219: 
1220:     /**
1221:      * Zooms the axis ranges by the specified percentage about the anchor point.
1222:      *
1223:      * @param percent  The amount of the zoom.
1224:      */
1225:     public void zoom(double percent) {
1226: 
1227:         if (percent > 0) {
1228:           //  double range = this.domainAxis.getRange().getLength();
1229:           //  double scaledRange = range * percent;
1230:           //  domainAxis.setAnchoredRange(scaledRange);
1231: 
1232:           //  range = this.rangeAxis.getRange().getLength();
1233:          //  scaledRange = range * percent;
1234:          //   rangeAxis.setAnchoredRange(scaledRange);
1235:         }
1236:         else {
1237:             getRangeAxis().setAutoRange(true);
1238:             getDomainAxis().setAutoRange(true);
1239:         }
1240: 
1241:     }
1242: 
1243:     /**
1244:      * Returns the plot type as a string.
1245:      *
1246:      * @return A short string describing the type of plot.
1247:      */
1248:     public String getPlotType() {
1249:         return localizationResources.getString("Contour_Plot");
1250:     }
1251: 
1252:     /**
1253:      * Returns the range for an axis.
1254:      *
1255:      * @param axis  the axis.
1256:      *
1257:      * @return The range for an axis.
1258:      */
1259:     public Range getDataRange(ValueAxis axis) {
1260: 
1261:         if (this.dataset == null) {
1262:             return null;
1263:         }
1264: 
1265:         Range result = null;
1266: 
1267:         if (axis == getDomainAxis()) {
1268:             result = DatasetUtilities.findDomainBounds(this.dataset);
1269:         }
1270:         else if (axis == getRangeAxis()) {
1271:             result = DatasetUtilities.findRangeBounds(this.dataset);
1272:         }
1273: 
1274:         return result;
1275: 
1276:     }
1277: 
1278:     /**
1279:      * Returns the range for the Contours.
1280:      *
1281:      * @return The range for the Contours (z-axis).
1282:      */
1283:     public Range getContourDataRange() {
1284: 
1285:         Range result = null;
1286: 
1287:         ContourDataset data = getDataset();
1288: 
1289:         if (data != null) {
1290:             Range h = getDomainAxis().getRange();
1291:             Range v = getRangeAxis().getRange();
1292:             result = this.visibleRange(data, h, v);
1293:         }
1294: 
1295:         return result;
1296:     }
1297: 
1298:     /**
1299:      * Notifies all registered listeners of a property change.
1300:      * <P>
1301:      * One source of property change events is the plot's renderer.
1302:      *
1303:      * @param event  Information about the property change.
1304:      */
1305:     public void propertyChange(PropertyChangeEvent event) {
1306:         notifyListeners(new PlotChangeEvent(this));
1307:     }
1308: 
1309:     /**
1310:      * Receives notification of a change to the plot's dataset.
1311:      * <P>
1312:      * The chart reacts by passing on a chart change event to all registered
1313:      * listeners.
1314:      *
1315:      * @param event  Information about the event (not used here).
1316:      */
1317:     public void datasetChanged(DatasetChangeEvent event) {
1318:         if (this.domainAxis != null) {
1319:             this.domainAxis.configure();
1320:         }
1321:         if (this.rangeAxis != null) {
1322:             this.rangeAxis.configure();
1323:         }
1324:         if (this.colorBar != null) {
1325:             this.colorBar.configure(this);
1326:         }
1327:         super.datasetChanged(event);
1328:     }
1329: 
1330:     /**
1331:      * Returns the colorbar.
1332:      *
1333:      * @return The colorbar.
1334:      */
1335:     public ColorBar getColorBar() {
1336:         return this.colorBar;
1337:     }
1338: 
1339:     /**
1340:      * Returns a flag indicating whether or not the domain crosshair is visible.
1341:      *
1342:      * @return The flag.
1343:      */
1344:     public boolean isDomainCrosshairVisible() {
1345:         return this.domainCrosshairVisible;
1346:     }
1347: 
1348:     /**
1349:      * Sets the flag indicating whether or not the domain crosshair is visible.
1350:      *
1351:      * @param flag  the new value of the flag.
1352:      */
1353:     public void setDomainCrosshairVisible(boolean flag) {
1354: 
1355:         if (this.domainCrosshairVisible != flag) {
1356:             this.domainCrosshairVisible = flag;
1357:             notifyListeners(new PlotChangeEvent(this));
1358:         }
1359: 
1360:     }
1361: 
1362:     /**
1363:      * Returns a flag indicating whether or not the crosshair should "lock-on"
1364:      * to actual data values.
1365:      *
1366:      * @return The flag.
1367:      */
1368:     public boolean isDomainCrosshairLockedOnData() {
1369:         return this.domainCrosshairLockedOnData;
1370:     }
1371: 
1372:     /**
1373:      * Sets the flag indicating whether or not the domain crosshair should 
1374:      * "lock-on" to actual data values.
1375:      *
1376:      * @param flag  the flag.
1377:      */
1378:     public void setDomainCrosshairLockedOnData(boolean flag) {
1379:         if (this.domainCrosshairLockedOnData != flag) {
1380:             this.domainCrosshairLockedOnData = flag;
1381:             notifyListeners(new PlotChangeEvent(this));
1382:         }
1383:     }
1384: 
1385:     /**
1386:      * Returns the domain crosshair value.
1387:      *
1388:      * @return The value.
1389:      */
1390:     public double getDomainCrosshairValue() {
1391:         return this.domainCrosshairValue;
1392:     }
1393: 
1394:     /**
1395:      * Sets the domain crosshair value.
1396:      * <P>
1397:      * Registered listeners are notified that the plot has been modified, but
1398:      * only if the crosshair is visible.
1399:      *
1400:      * @param value  the new value.
1401:      */
1402:     public void setDomainCrosshairValue(double value) {
1403: 
1404:         setDomainCrosshairValue(value, true);
1405: 
1406:     }
1407: 
1408:     /**
1409:      * Sets the domain crosshair value.
1410:      * <P>
1411:      * Registered listeners are notified that the axis has been modified, but
1412:      * only if the crosshair is visible.
1413:      *
1414:      * @param value  the new value.
1415:      * @param notify  a flag that controls whether or not listeners are 
1416:      *                notified.
1417:      */
1418:     public void setDomainCrosshairValue(double value, boolean notify) {
1419: 
1420:         this.domainCrosshairValue = value;
1421:         if (isDomainCrosshairVisible() && notify) {
1422:             notifyListeners(new PlotChangeEvent(this));
1423:         }
1424: 
1425:     }
1426: 
1427:     /**
1428:      * Returns the Stroke used to draw the crosshair (if visible).
1429:      *
1430:      * @return The crosshair stroke.
1431:      */
1432:     public Stroke getDomainCrosshairStroke() {
1433:         return this.domainCrosshairStroke;
1434:     }
1435: 
1436:     /**
1437:      * Sets the Stroke used to draw the crosshairs (if visible) and notifies
1438:      * registered listeners that the axis has been modified.
1439:      *
1440:      * @param stroke  the new crosshair stroke.
1441:      */
1442:     public void setDomainCrosshairStroke(Stroke stroke) {
1443:         this.domainCrosshairStroke = stroke;
1444:         notifyListeners(new PlotChangeEvent(this));
1445:     }
1446: 
1447:     /**
1448:      * Returns the domain crosshair color.
1449:      *
1450:      * @return The crosshair color.
1451:      */
1452:     public Paint getDomainCrosshairPaint() {
1453:         return this.domainCrosshairPaint;
1454:     }
1455: 
1456:     /**
1457:      * Sets the Paint used to color the crosshairs (if visible) and notifies
1458:      * registered listeners that the axis has been modified.
1459:      *
1460:      * @param paint the new crosshair paint.
1461:      */
1462:     public void setDomainCrosshairPaint(Paint paint) {
1463:         this.domainCrosshairPaint = paint;
1464:         notifyListeners(new PlotChangeEvent(this));
1465:     }
1466: 
1467:     /**
1468:      * Returns a flag indicating whether or not the range crosshair is visible.
1469:      *
1470:      * @return The flag.
1471:      */
1472:     public boolean isRangeCrosshairVisible() {
1473:         return this.rangeCrosshairVisible;
1474:     }
1475: 
1476:     /**
1477:      * Sets the flag indicating whether or not the range crosshair is visible.
1478:      *
1479:      * @param flag  the new value of the flag.
1480:      */
1481:     public void setRangeCrosshairVisible(boolean flag) {
1482: 
1483:         if (this.rangeCrosshairVisible != flag) {
1484:             this.rangeCrosshairVisible = flag;
1485:             notifyListeners(new PlotChangeEvent(this));
1486:         }
1487: 
1488:     }
1489: 
1490:     /**
1491:      * Returns a flag indicating whether or not the crosshair should "lock-on"
1492:      * to actual data values.
1493:      *
1494:      * @return The flag.
1495:      */
1496:     public boolean isRangeCrosshairLockedOnData() {
1497:         return this.rangeCrosshairLockedOnData;
1498:     }
1499: 
1500:     /**
1501:      * Sets the flag indicating whether or not the range crosshair should 
1502:      * "lock-on" to actual data values.
1503:      *
1504:      * @param flag  the flag.
1505:      */
1506:     public void setRangeCrosshairLockedOnData(boolean flag) {
1507: 
1508:         if (this.rangeCrosshairLockedOnData != flag) {
1509:             this.rangeCrosshairLockedOnData = flag;
1510:             notifyListeners(new PlotChangeEvent(this));
1511:         }
1512: 
1513:     }
1514: 
1515:     /**
1516:      * Returns the range crosshair value.
1517:      *
1518:      * @return The value.
1519:      */
1520:     public double getRangeCrosshairValue() {
1521:         return this.rangeCrosshairValue;
1522:     }
1523: 
1524:     /**
1525:      * Sets the domain crosshair value.
1526:      * <P>
1527:      * Registered listeners are notified that the plot has been modified, but
1528:      * only if the crosshair is visible.
1529:      *
1530:      * @param value  the new value.
1531:      */
1532:     public void setRangeCrosshairValue(double value) {
1533: 
1534:         setRangeCrosshairValue(value, true);
1535: 
1536:     }
1537: 
1538:     /**
1539:      * Sets the range crosshair value.
1540:      * <P>
1541:      * Registered listeners are notified that the axis has been modified, but
1542:      * only if the crosshair is visible.
1543:      *
1544:      * @param value  the new value.
1545:      * @param notify  a flag that controls whether or not listeners are 
1546:      *                notified.
1547:      */
1548:     public void setRangeCrosshairValue(double value, boolean notify) {
1549: 
1550:         this.rangeCrosshairValue = value;
1551:         if (isRangeCrosshairVisible() && notify) {
1552:             notifyListeners(new PlotChangeEvent(this));
1553:         }
1554: 
1555:     }
1556: 
1557:     /**
1558:      * Returns the Stroke used to draw the crosshair (if visible).
1559:      *
1560:      * @return The crosshair stroke.
1561:      */
1562:     public Stroke getRangeCrosshairStroke() {
1563:         return this.rangeCrosshairStroke;
1564:     }
1565: 
1566:     /**
1567:      * Sets the Stroke used to draw the crosshairs (if visible) and notifies
1568:      * registered listeners that the axis has been modified.
1569:      *
1570:      * @param stroke  the new crosshair stroke.
1571:      */
1572:     public void setRangeCrosshairStroke(Stroke stroke) {
1573:         this.rangeCrosshairStroke = stroke;
1574:         notifyListeners(new PlotChangeEvent(this));
1575:     }
1576: 
1577:     /**
1578:      * Returns the range crosshair color.
1579:      *
1580:      * @return The crosshair color.
1581:      */
1582:     public Paint getRangeCrosshairPaint() {
1583:         return this.rangeCrosshairPaint;
1584:     }
1585: 
1586:     /**
1587:      * Sets the Paint used to color the crosshairs (if visible) and notifies
1588:      * registered listeners that the axis has been modified.
1589:      *
1590:      * @param paint the new crosshair paint.
1591:      */
1592:     public void setRangeCrosshairPaint(Paint paint) {
1593:         this.rangeCrosshairPaint = paint;
1594:         notifyListeners(new PlotChangeEvent(this));
1595:     }
1596: 
1597:     /**
1598:      * Returns the tool tip generator.
1599:      *
1600:      * @return The tool tip generator (possibly null).
1601:      */
1602:     public ContourToolTipGenerator getToolTipGenerator() {
1603:         return this.toolTipGenerator;
1604:     }
1605: 
1606:     /**
1607:      * Sets the tool tip generator.
1608:      *
1609:      * @param generator  the tool tip generator (null permitted).
1610:      */
1611:     public void setToolTipGenerator(ContourToolTipGenerator generator) {
1612: 
1613:         //Object oldValue = this.toolTipGenerator;
1614:         this.toolTipGenerator = generator;
1615: 
1616:     }
1617: 
1618:     /**
1619:      * Returns the URL generator for HTML image maps.
1620:      *
1621:      * @return The URL generator (possibly null).
1622:      */
1623:     public XYURLGenerator getURLGenerator() {
1624:         return this.urlGenerator;
1625:     }
1626: 
1627:     /**
1628:      * Sets the URL generator for HTML image maps.
1629:      *
1630:      * @param urlGenerator  the URL generator (null permitted).
1631:      */
1632:     public void setURLGenerator(XYURLGenerator urlGenerator) {
1633: 
1634:         //Object oldValue = this.urlGenerator;
1635:         this.urlGenerator = urlGenerator;
1636: 
1637:     }
1638: 
1639:     /**
1640:      * Draws a vertical line on the chart to represent a 'range marker'.
1641:      *
1642:      * @param g2  the graphics device.
1643:      * @param plot  the plot.
1644:      * @param domainAxis  the domain axis.
1645:      * @param marker  the marker line.
1646:      * @param dataArea  the axis data area.
1647:      */
1648:     public void drawDomainMarker(Graphics2D g2,
1649:                                  ContourPlot plot,
1650:                                  ValueAxis domainAxis,
1651:                                  Marker marker,
1652:                                  Rectangle2D dataArea) {
1653: 
1654:         if (marker instanceof ValueMarker) {
1655:             ValueMarker vm = (ValueMarker) marker;
1656:             double value = vm.getValue();
1657:             Range range = domainAxis.getRange();
1658:             if (!range.contains(value)) {
1659:                 return;
1660:             }
1661:   
1662:             double x = domainAxis.valueToJava2D(
1663:                 value, dataArea, RectangleEdge.BOTTOM
1664:             );
1665:             Line2D line = new Line2D.Double(
1666:                 x, dataArea.getMinY(), x, dataArea.getMaxY()
1667:             );
1668:             Paint paint = marker.getOutlinePaint();
1669:             Stroke stroke = marker.getOutlineStroke();
1670:             g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
1671:             g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
1672:             g2.draw(line);
1673:         }
1674: 
1675:     }
1676: 
1677:     /**
1678:      * Draws a horizontal line across the chart to represent a 'range marker'.
1679:      *
1680:      * @param g2  the graphics device.
1681:      * @param plot  the plot.
1682:      * @param rangeAxis  the range axis.
1683:      * @param marker  the marker line.
1684:      * @param dataArea  the axis data area.
1685:      */
1686:     public void drawRangeMarker(Graphics2D g2,
1687:                                 ContourPlot plot,
1688:                                 ValueAxis rangeAxis,
1689:                                 Marker marker,
1690:                                 Rectangle2D dataArea) {
1691: 
1692:         if (marker instanceof ValueMarker) {
1693:             ValueMarker vm = (ValueMarker) marker;
1694:             double value = vm.getValue();
1695:             Range range = rangeAxis.getRange();
1696:             if (!range.contains(value)) {
1697:                 return;
1698:             }
1699: 
1700:             double y = rangeAxis.valueToJava2D(
1701:                 value, dataArea, RectangleEdge.LEFT
1702:             );
1703:             Line2D line = new Line2D.Double(
1704:                 dataArea.getMinX(), y, dataArea.getMaxX(), y
1705:             );
1706:             Paint paint = marker.getOutlinePaint();
1707:             Stroke stroke = marker.getOutlineStroke();
1708:             g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
1709:             g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
1710:             g2.draw(line);
1711:         }
1712: 
1713:     }
1714: 
1715:     /**
1716:      * Returns the clipPath.
1717:      * @return ClipPath
1718:      */
1719:     public ClipPath getClipPath() {
1720:         return this.clipPath;
1721:     }
1722: 
1723:     /**
1724:      * Sets the clipPath.
1725:      * @param clipPath The clipPath to set
1726:      */
1727:     public void setClipPath(ClipPath clipPath) {
1728:         this.clipPath = clipPath;
1729:     }
1730: 
1731:     /**
1732:      * Returns the ptSizePct.
1733:      * @return double
1734:      */
1735:     public double getPtSizePct() {
1736:         return this.ptSizePct;
1737:     }
1738: 
1739:     /**
1740:      * Returns the renderAsPoints.
1741:      * @return boolean
1742:      */
1743:     public boolean isRenderAsPoints() {
1744:         return this.renderAsPoints;
1745:     }
1746: 
1747:     /**
1748:      * Sets the ptSizePct.
1749:      * @param ptSizePct The ptSizePct to set
1750:      */
1751:     public void setPtSizePct(double ptSizePct) {
1752:         this.ptSizePct = ptSizePct;
1753:     }
1754: 
1755:     /**
1756:      * Sets the renderAsPoints.
1757:      * @param renderAsPoints The renderAsPoints to set
1758:      */
1759:     public void setRenderAsPoints(boolean renderAsPoints) {
1760:         this.renderAsPoints = renderAsPoints;
1761:     }
1762: 
1763:     /**
1764:      * Receives notification of a change to one of the plot's axes.
1765:      *
1766:      * @param event  information about the event.
1767:      */
1768:     public void axisChanged(AxisChangeEvent event) {
1769:         Object source = event.getSource();
1770:         if (source.equals(this.rangeAxis) || source.equals(this.domainAxis)) {
1771:             ColorBar cba = this.colorBar;
1772:             if (this.colorBar.getAxis().isAutoRange()) {
1773:                 cba.getAxis().configure();
1774:             }
1775: 
1776:         }
1777:         super.axisChanged(event);
1778:     }
1779: 
1780:     /**
1781:      * Returns the visible z-range.
1782:      *
1783:      * @param data  the dataset.
1784:      * @param x  the x range.
1785:      * @param y  the y range.
1786:      *
1787:      * @return The range.
1788:      */
1789:     public Range visibleRange(ContourDataset data, Range x, Range y) {
1790:         Range range = null;
1791:         range = data.getZValueRange(x, y);
1792:         return range;
1793:     }
1794: 
1795:     /**
1796:      * Returns the missingPaint.
1797:      * @return Paint
1798:      */
1799:     public Paint getMissingPaint() {
1800:         return this.missingPaint;
1801:     }
1802: 
1803:     /**
1804:      * Sets the missingPaint.
1805:      * 
1806:      * @param paint  the missingPaint to set.
1807:      */
1808:     public void setMissingPaint(Paint paint) {
1809:         this.missingPaint = paint;
1810:     }
1811:     
1812:     /**
1813:      * Multiplies the range on the domain axis/axes by the specified factor 
1814:      * (to be implemented).
1815:      * 
1816:      * @param x  the x-coordinate (in Java2D space).
1817:      * @param y  the y-coordinate (in Java2D space).
1818:      * @param factor  the zoom factor.
1819:      */
1820:     public void zoomDomainAxes(double x, double y, double factor) {
1821:         // TODO: to be implemented
1822:     }
1823:     
1824:     /**
1825:      * Zooms the domain axes (not yet implemented).
1826:      * 
1827:      * @param x  the x-coordinate (in Java2D space).
1828:      * @param y  the y-coordinate (in Java2D space).
1829:      * @param lowerPercent  the new lower bound.
1830:      * @param upperPercent  the new upper bound.
1831:      */
1832:     public void zoomDomainAxes(double x, double y, double lowerPercent, 
1833:                                double upperPercent) {
1834:         // TODO: to be implemented
1835:     }
1836:     
1837:     /**
1838:      * Multiplies the range on the range axis/axes by the specified factor.
1839:      * 
1840:      * @param x  the x-coordinate (in Java2D space).
1841:      * @param y  the y-coordinate (in Java2D space).
1842:      * @param factor  the zoom factor.
1843:      */
1844:     public void zoomRangeAxes(double x, double y, double factor) {
1845:         // TODO: to be implemented
1846:     }
1847: 
1848:     /**
1849:      * Zooms the range axes (not yet implemented).
1850:      * 
1851:      * @param x  the x-coordinate (in Java2D space).
1852:      * @param y  the y-coordinate (in Java2D space).
1853:      * @param lowerPercent  the new lower bound.
1854:      * @param upperPercent  the new upper bound.
1855:      */
1856:     public void zoomRangeAxes(double x, double y, double lowerPercent, 
1857:                               double upperPercent) {
1858:         // TODO: to be implemented
1859:     }
1860: 
1861:     /**
1862:      * Returns <code>false</code>.
1863:      * 
1864:      * @return A boolean.
1865:      */
1866:     public boolean isDomainZoomable() {
1867:         return false;
1868:     }
1869:     
1870:     /**
1871:      * Returns <code>false</code>.
1872:      * 
1873:      * @return A boolean.
1874:      */
1875:     public boolean isRangeZoomable() {
1876:         return false;
1877:     }
1878: 
1879:     /** 
1880:      * Extends plot cloning to this plot type
1881:      * @see org.jfree.chart.plot.Plot#clone()
1882:      */
1883:     public Object clone() throws CloneNotSupportedException {
1884:         ContourPlot clone = (ContourPlot) super.clone();
1885:         
1886:         if (this.domainAxis != null) {
1887:             clone.domainAxis = (ValueAxis) this.domainAxis.clone();
1888:             clone.domainAxis.setPlot(clone);
1889:             clone.domainAxis.addChangeListener(clone);
1890:         }
1891:         if (this.rangeAxis != null) {
1892:             clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
1893:             clone.rangeAxis.setPlot(clone);
1894:             clone.rangeAxis.addChangeListener(clone);
1895:         }
1896: 
1897:         if (clone.dataset != null) {
1898:             clone.dataset.addChangeListener(clone); 
1899:         }
1900:     
1901:         if (this.colorBar != null) {
1902:             clone.colorBar = (ColorBar) this.colorBar.clone();
1903:         }
1904: 
1905:         clone.domainMarkers = (List) ObjectUtilities.deepClone(
1906:             this.domainMarkers
1907:         );
1908:         clone.rangeMarkers = (List) ObjectUtilities.deepClone(
1909:             this.rangeMarkers
1910:         );
1911:         clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
1912: 
1913:         if (this.clipPath != null) {
1914:             clone.clipPath = (ClipPath) this.clipPath.clone(); 
1915:         }
1916: 
1917:         return clone;
1918:     }
1919: 
1920: }