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