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