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    
070    package org.jfree.chart.plot;
071    
072    import java.awt.AlphaComposite;
073    import java.awt.Composite;
074    import java.awt.Graphics2D;
075    import java.awt.Paint;
076    import java.awt.RenderingHints;
077    import java.awt.Shape;
078    import java.awt.Stroke;
079    import java.awt.geom.Ellipse2D;
080    import java.awt.geom.GeneralPath;
081    import java.awt.geom.Line2D;
082    import java.awt.geom.Point2D;
083    import java.awt.geom.Rectangle2D;
084    import java.awt.geom.RectangularShape;
085    import java.beans.PropertyChangeEvent;
086    import java.beans.PropertyChangeListener;
087    import java.io.Serializable;
088    import java.util.Iterator;
089    import java.util.List;
090    import java.util.ResourceBundle;
091    
092    import org.jfree.chart.ClipPath;
093    import org.jfree.chart.annotations.XYAnnotation;
094    import org.jfree.chart.axis.AxisSpace;
095    import org.jfree.chart.axis.ColorBar;
096    import org.jfree.chart.axis.NumberAxis;
097    import org.jfree.chart.axis.ValueAxis;
098    import org.jfree.chart.entity.ContourEntity;
099    import org.jfree.chart.entity.EntityCollection;
100    import org.jfree.chart.event.AxisChangeEvent;
101    import org.jfree.chart.event.PlotChangeEvent;
102    import org.jfree.chart.labels.ContourToolTipGenerator;
103    import org.jfree.chart.labels.StandardContourToolTipGenerator;
104    import org.jfree.chart.renderer.xy.XYBlockRenderer;
105    import org.jfree.chart.urls.XYURLGenerator;
106    import org.jfree.chart.util.ResourceBundleWrapper;
107    import org.jfree.data.Range;
108    import org.jfree.data.contour.ContourDataset;
109    import org.jfree.data.general.DatasetChangeEvent;
110    import org.jfree.data.general.DatasetUtilities;
111    import org.jfree.ui.RectangleEdge;
112    import org.jfree.ui.RectangleInsets;
113    import 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     */
121    public 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    }