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