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
068package org.jfree.chart.plot;
069
070import java.awt.AlphaComposite;
071import java.awt.Composite;
072import java.awt.Graphics2D;
073import java.awt.Paint;
074import java.awt.RenderingHints;
075import java.awt.Shape;
076import java.awt.Stroke;
077import java.awt.geom.Ellipse2D;
078import java.awt.geom.GeneralPath;
079import java.awt.geom.Line2D;
080import java.awt.geom.Point2D;
081import java.awt.geom.Rectangle2D;
082import java.awt.geom.RectangularShape;
083import java.beans.PropertyChangeEvent;
084import java.beans.PropertyChangeListener;
085import java.io.Serializable;
086import java.util.Iterator;
087import java.util.List;
088import java.util.ResourceBundle;
089
090import org.jfree.chart.ClipPath;
091import org.jfree.chart.annotations.XYAnnotation;
092import org.jfree.chart.axis.AxisSpace;
093import org.jfree.chart.axis.ColorBar;
094import org.jfree.chart.axis.NumberAxis;
095import org.jfree.chart.axis.ValueAxis;
096import org.jfree.chart.entity.ContourEntity;
097import org.jfree.chart.entity.EntityCollection;
098import org.jfree.chart.event.AxisChangeEvent;
099import org.jfree.chart.event.PlotChangeEvent;
100import org.jfree.chart.labels.ContourToolTipGenerator;
101import org.jfree.chart.labels.StandardContourToolTipGenerator;
102import org.jfree.chart.renderer.xy.XYBlockRenderer;
103import org.jfree.chart.urls.XYURLGenerator;
104import org.jfree.data.Range;
105import org.jfree.data.contour.ContourDataset;
106import org.jfree.data.general.DatasetChangeEvent;
107import org.jfree.data.general.DatasetUtilities;
108import org.jfree.ui.RectangleEdge;
109import org.jfree.ui.RectangleInsets;
110import 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 */
118public 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        notifyListeners(new PlotChangeEvent(this));    
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            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
514
515    }
516
517    /**
518     * Clears all the annotations.
519     */
520    public void clearAnnotations() {
521        if (this.annotations != null) {
522            this.annotations.clear();
523            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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            notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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        notifyListeners(new PlotChangeEvent(this));
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}