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