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