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}