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