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