001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------
028 * ContourPlot.java
029 * ----------------
030 * (C) Copyright 2002-2014, by David M. O'Donnell and Contributors.
031 *
032 * Original Author:  David M. O'Donnell;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Arnaud Lelievre;
035 *                   Nicolas Brodu;
036 *
037 * Changes
038 * -------
039 * 26-Nov-2002 : Version 1 contributed by David M. O'Donnell (DG);
040 * 14-Jan-2003 : Added crosshair attributes (DG);
041 * 23-Jan-2003 : Removed two constructors (DG);
042 * 21-Mar-2003 : Bug fix 701744 (DG);
043 * 26-Mar-2003 : Implemented Serializable (DG);
044 * 09-Jul-2003 : Changed ColorBar from extending axis classes to enclosing
045 *               them (DG);
046 * 05-Aug-2003 : Applied changes in bug report 780298 (DG);
047 * 08-Sep-2003 : Added internationalization via use of properties
048 *               resourceBundle (RFE 690236) (AL);
049 * 11-Sep-2003 : Cloning support (NB);
050 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
051 * 17-Jan-2004 : Removed references to DefaultContourDataset class, replaced
052 *               with ContourDataset interface (with changes to the interface).
053 *               See bug 741048 (DG);
054 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
055 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
056 * 06-Oct-2004 : Updated for changes in DatasetUtilities class (DG);
057 * 11-Nov-2004 : Renamed zoom methods to match ValueAxisPlot interface (DG);
058 * 25-Nov-2004 : Small update to clone() implementation (DG);
059 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
060 * 05-May-2005 : Updated draw() method parameters (DG);
061 * 16-Jun-2005 : Added default constructor (DG);
062 * 01-Sep-2005 : Moved dataAreaRatio from Plot to here (DG);
063 * ------------- JFREECHART 1.0.x ---------------------------------------------
064 * 31-Jan-2007 : Deprecated (DG);
065 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
066 *               Jess Thrysoee (DG);
067 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
068 * 02-Jul-2013 : Fix NB warnings (DG);
069 *
070 */
071
072package org.jfree.chart.plot;
073
074import java.awt.AlphaComposite;
075import java.awt.Composite;
076import java.awt.Graphics2D;
077import java.awt.Paint;
078import java.awt.RenderingHints;
079import java.awt.Shape;
080import java.awt.Stroke;
081import java.awt.geom.Ellipse2D;
082import java.awt.geom.GeneralPath;
083import java.awt.geom.Line2D;
084import java.awt.geom.Point2D;
085import java.awt.geom.Rectangle2D;
086import java.awt.geom.RectangularShape;
087import java.beans.PropertyChangeEvent;
088import java.beans.PropertyChangeListener;
089import java.io.Serializable;
090import java.util.Iterator;
091import java.util.List;
092import java.util.ResourceBundle;
093
094import org.jfree.chart.ClipPath;
095import org.jfree.chart.annotations.XYAnnotation;
096import org.jfree.chart.axis.AxisSpace;
097import org.jfree.chart.axis.ColorBar;
098import org.jfree.chart.axis.NumberAxis;
099import org.jfree.chart.axis.ValueAxis;
100import org.jfree.chart.entity.ContourEntity;
101import org.jfree.chart.entity.EntityCollection;
102import org.jfree.chart.event.AxisChangeEvent;
103import org.jfree.chart.event.PlotChangeEvent;
104import org.jfree.chart.labels.ContourToolTipGenerator;
105import org.jfree.chart.labels.StandardContourToolTipGenerator;
106import org.jfree.chart.renderer.xy.XYBlockRenderer;
107import org.jfree.chart.urls.XYURLGenerator;
108import org.jfree.chart.util.ResourceBundleWrapper;
109import org.jfree.data.Range;
110import org.jfree.data.contour.ContourDataset;
111import org.jfree.data.general.DatasetChangeEvent;
112import org.jfree.data.general.DatasetUtilities;
113import org.jfree.ui.RectangleEdge;
114import org.jfree.ui.RectangleInsets;
115import org.jfree.util.ObjectUtilities;
116
117/**
118 * A class for creating shaded contours.
119 *
120 * @deprecated This plot is no longer supported, please use {@link XYPlot} with
121 *     an {@link XYBlockRenderer}.
122 */
123public class ContourPlot extends Plot implements ContourValuePlot,
124        ValueAxisPlot, PropertyChangeListener, Serializable, Cloneable {
125
126    /** For serialization. */
127    private static final long serialVersionUID = 7861072556590502247L;
128
129    /** The default insets. */
130    protected static final RectangleInsets DEFAULT_INSETS
131            = new RectangleInsets(2.0, 2.0, 100.0, 10.0);
132
133    /** The domain axis (used for the x-values). */
134    private ValueAxis domainAxis;
135
136    /** The range axis (used for the y-values). */
137    private ValueAxis rangeAxis;
138
139    /** The dataset. */
140    private ContourDataset dataset;
141
142    /** The colorbar axis (used for the z-values). */
143    private ColorBar colorBar = null;
144
145    /** The color bar location. */
146    private RectangleEdge colorBarLocation;
147
148    /** A flag that controls whether or not a domain crosshair is drawn..*/
149    private boolean domainCrosshairVisible;
150
151    /** The domain crosshair value. */
152    private double domainCrosshairValue;
153
154    /** The pen/brush used to draw the crosshair (if any). */
155    private transient Stroke domainCrosshairStroke;
156
157    /** The color used to draw the crosshair (if any). */
158    private transient Paint domainCrosshairPaint;
159
160    /**
161     * A flag that controls whether or not the crosshair locks onto actual data
162     * points.
163     */
164    private boolean domainCrosshairLockedOnData = true;
165
166    /** A flag that controls whether or not a range crosshair is drawn..*/
167    private boolean rangeCrosshairVisible;
168
169    /** The range crosshair value. */
170    private double rangeCrosshairValue;
171
172    /** The pen/brush used to draw the crosshair (if any). */
173    private transient Stroke rangeCrosshairStroke;
174
175    /** The color used to draw the crosshair (if any). */
176    private transient Paint rangeCrosshairPaint;
177
178    /**
179     * A flag that controls whether or not the crosshair locks onto actual data
180     * points.
181     */
182    private boolean rangeCrosshairLockedOnData = true;
183
184    /**
185     * Defines dataArea rectangle as the ratio formed from dividing height by
186     * width (of the dataArea).  Modifies plot area calculations.
187     * ratio > 0 will attempt to layout the plot so that the
188     * dataArea.height/dataArea.width = ratio.
189     * ratio < 0 will attempt to layout the plot so that the
190     * dataArea.height/dataArea.width in plot units (not java2D units as when
191     * ratio > 0) = -1.*ratio.
192     */         //dmo
193    private double dataAreaRatio = 0.0;  //zero when the parameter is not set
194
195    /** A list of markers (optional) for the domain axis. */
196    private List domainMarkers;
197
198    /** A list of markers (optional) for the range axis. */
199    private List rangeMarkers;
200
201    /** A list of annotations (optional) for the plot. */
202    private List annotations;
203
204    /** The tool tip generator. */
205    private ContourToolTipGenerator toolTipGenerator;
206
207    /** The URL text generator. */
208    private XYURLGenerator urlGenerator;
209
210    /**
211     * Controls whether data are render as filled rectangles or rendered as
212     * points
213     */
214    private boolean renderAsPoints = false;
215
216    /**
217     * Size of points rendered when renderAsPoints = true.  Size is relative to
218     * dataArea
219     */
220    private double ptSizePct = 0.05;
221
222    /** Contains the a ClipPath to "trim" the contours. */
223    private transient ClipPath clipPath = null;
224
225    /** Set to Paint to represent missing values. */
226    private transient Paint missingPaint = null;
227
228    /** The resourceBundle for the localization. */
229    protected static ResourceBundle localizationResources
230            = ResourceBundleWrapper.getBundle(
231                    "org.jfree.chart.plot.LocalizationBundle");
232
233    /**
234     * Creates a new plot with no dataset or axes.
235     */
236    public ContourPlot() {
237        this(null, null, null, null);
238    }
239
240    /**
241     * Constructs a contour plot with the specified axes (other attributes take
242     * default values).
243     *
244     * @param dataset  The dataset.
245     * @param domainAxis  The domain axis.
246     * @param rangeAxis  The range axis.
247     * @param colorBar  The z-axis axis.
248    */
249    public ContourPlot(ContourDataset dataset,
250                       ValueAxis domainAxis, ValueAxis rangeAxis,
251                       ColorBar colorBar) {
252
253        super();
254
255        this.dataset = dataset;
256        if (dataset != null) {
257            dataset.addChangeListener(this);
258        }
259
260        this.domainAxis = domainAxis;
261        if (domainAxis != null) {
262            domainAxis.setPlot(this);
263            domainAxis.addChangeListener(this);
264        }
265
266        this.rangeAxis = rangeAxis;
267        if (rangeAxis != null) {
268            rangeAxis.setPlot(this);
269            rangeAxis.addChangeListener(this);
270        }
271
272        this.colorBar = colorBar;
273        if (colorBar != null) {
274            colorBar.getAxis().setPlot(this);
275            colorBar.getAxis().addChangeListener(this);
276            colorBar.configure(this);
277        }
278        this.colorBarLocation = RectangleEdge.LEFT;
279
280        this.toolTipGenerator = new StandardContourToolTipGenerator();
281
282    }
283
284    /**
285     * Returns the color bar location.
286     *
287     * @return The color bar location.
288     */
289    public RectangleEdge getColorBarLocation() {
290        return this.colorBarLocation;
291    }
292
293    /**
294     * Sets the color bar location and sends a {@link PlotChangeEvent} to all
295     * registered listeners.
296     *
297     * @param edge  the location.
298     */
299    public void setColorBarLocation(RectangleEdge edge) {
300        this.colorBarLocation = edge;
301        fireChangeEvent();
302    }
303
304    /**
305     * Returns the primary dataset for the plot.
306     *
307     * @return The primary dataset (possibly <code>null</code>).
308     */
309    public ContourDataset getDataset() {
310        return this.dataset;
311    }
312
313    /**
314     * Sets the dataset for the plot, replacing the existing dataset if there
315     * is one.
316     *
317     * @param dataset  the dataset (<code>null</code> permitted).
318     */
319    public void setDataset(ContourDataset dataset) {
320
321        // if there is an existing dataset, remove the plot from the list of
322        // change listeners...
323        ContourDataset existing = this.dataset;
324        if (existing != null) {
325            existing.removeChangeListener(this);
326        }
327
328        // set the new dataset, and register the chart as a change listener...
329        this.dataset = dataset;
330        if (dataset != null) {
331            setDatasetGroup(dataset.getGroup());
332            dataset.addChangeListener(this);
333        }
334
335        // send a dataset change event to self...
336        DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
337        datasetChanged(event);
338
339    }
340
341    /**
342     * Returns the domain axis for the plot.
343     *
344     * @return The domain axis.
345     */
346    public ValueAxis getDomainAxis() {
347
348        ValueAxis result = this.domainAxis;
349
350        return result;
351
352    }
353
354    /**
355     * Sets the domain axis for the plot (this must be compatible with the plot
356     * type or an exception is thrown).
357     *
358     * @param axis The new axis.
359     */
360    public void setDomainAxis(ValueAxis axis) {
361
362        if (isCompatibleDomainAxis(axis)) {
363
364            if (axis != null) {
365                axis.setPlot(this);
366                axis.addChangeListener(this);
367            }
368
369            // plot is likely registered as a listener with the existing axis...
370            if (this.domainAxis != null) {
371                this.domainAxis.removeChangeListener(this);
372            }
373
374            this.domainAxis = axis;
375            fireChangeEvent();
376
377        }
378
379    }
380
381    /**
382     * Returns the range axis for the plot.
383     *
384     * @return The range axis.
385     */
386    public ValueAxis getRangeAxis() {
387
388        ValueAxis result = this.rangeAxis;
389
390        return result;
391
392    }
393
394    /**
395     * Sets the range axis for the plot.
396     * <P>
397     * An exception is thrown if the new axis and the plot are not mutually
398     * compatible.
399     *
400     * @param axis The new axis (null permitted).
401     */
402    public void setRangeAxis(ValueAxis axis) {
403
404        if (axis != null) {
405            axis.setPlot(this);
406            axis.addChangeListener(this);
407        }
408
409        // plot is likely registered as a listener with the existing axis...
410        if (this.rangeAxis != null) {
411            this.rangeAxis.removeChangeListener(this);
412        }
413
414        this.rangeAxis = axis;
415        fireChangeEvent();
416
417    }
418
419    /**
420     * Sets the colorbar for the plot.
421     *
422     * @param axis The new axis (null permitted).
423     */
424    public void setColorBarAxis(ColorBar axis) {
425
426        this.colorBar = axis;
427        fireChangeEvent();
428
429    }
430
431    /**
432     * Returns the data area ratio.
433     *
434     * @return The ratio.
435     */
436    public double getDataAreaRatio() {
437        return this.dataAreaRatio;
438    }
439
440    /**
441     * Sets the data area ratio.
442     *
443     * @param ratio  the ratio.
444     */
445    public void setDataAreaRatio(double ratio) {
446        this.dataAreaRatio = ratio;
447    }
448
449    /**
450     * Adds a marker for the domain axis.
451     * <P>
452     * Typically a marker will be drawn by the renderer as a line perpendicular
453     * to the range axis, however this is entirely up to the renderer.
454     *
455     * @param marker the marker.
456     */
457    public void addDomainMarker(Marker marker) {
458
459        if (this.domainMarkers == null) {
460            this.domainMarkers = new java.util.ArrayList();
461        }
462        this.domainMarkers.add(marker);
463        fireChangeEvent();
464
465    }
466
467    /**
468     * Clears all the domain markers.
469     */
470    public void clearDomainMarkers() {
471        if (this.domainMarkers != null) {
472            this.domainMarkers.clear();
473            fireChangeEvent();
474        }
475    }
476
477    /**
478     * Adds a marker for the range axis.
479     * <P>
480     * Typically a marker will be drawn by the renderer as a line perpendicular
481     * to the range axis, however this is entirely up to the renderer.
482     *
483     * @param marker The marker.
484     */
485    public void addRangeMarker(Marker marker) {
486
487        if (this.rangeMarkers == null) {
488            this.rangeMarkers = new java.util.ArrayList();
489        }
490        this.rangeMarkers.add(marker);
491        fireChangeEvent();
492
493    }
494
495    /**
496     * Clears all the range markers.
497     */
498    public void clearRangeMarkers() {
499        if (this.rangeMarkers != null) {
500            this.rangeMarkers.clear();
501            fireChangeEvent();
502        }
503    }
504
505    /**
506     * Adds an annotation to the plot.
507     *
508     * @param annotation  the annotation.
509     */
510    public void addAnnotation(XYAnnotation annotation) {
511
512        if (this.annotations == null) {
513            this.annotations = new java.util.ArrayList();
514        }
515        this.annotations.add(annotation);
516        fireChangeEvent();
517
518    }
519
520    /**
521     * Clears all the annotations.
522     */
523    public void clearAnnotations() {
524        if (this.annotations != null) {
525            this.annotations.clear();
526            fireChangeEvent();
527        }
528    }
529
530    /**
531     * Checks the compatibility of a domain axis, returning true if the axis is
532     * compatible with the plot, and false otherwise.
533     *
534     * @param axis The proposed axis.
535     *
536     * @return <code>true</code> if the axis is compatible with the plot.
537     */
538    public boolean isCompatibleDomainAxis(ValueAxis axis) {
539
540        return true;
541
542    }
543
544    /**
545     * Draws the plot on a Java 2D graphics device (such as the screen or a
546     * printer).
547     * <P>
548     * The optional <code>info</code> argument collects information about the
549     * rendering of the plot (dimensions, tooltip information etc).  Just pass
550     * in <code>null</code> if you do not need this information.
551     *
552     * @param g2  the graphics device.
553     * @param area  the area within which the plot (including axis labels)
554     *              should be drawn.
555     * @param anchor  the anchor point (<code>null</code> permitted).
556     * @param parentState  the state from the parent plot, if there is one.
557     * @param info  collects chart drawing information (<code>null</code>
558     *              permitted).
559     */
560    @Override
561    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
562                     PlotState parentState, PlotRenderingInfo info) {
563
564        // if the plot area is too small, just return...
565        boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
566        boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
567        if (b1 || b2) {
568            return;
569        }
570
571        // record the plot area...
572        if (info != null) {
573            info.setPlotArea(area);
574        }
575
576        // adjust the drawing area for plot insets (if any)...
577        RectangleInsets insets = getInsets();
578        insets.trim(area);
579
580        AxisSpace space = new AxisSpace();
581
582        space = this.domainAxis.reserveSpace(g2, this, area,
583                RectangleEdge.BOTTOM, space);
584        space = this.rangeAxis.reserveSpace(g2, this, area,
585                RectangleEdge.LEFT, space);
586
587        Rectangle2D estimatedDataArea = space.shrink(area, null);
588
589        AxisSpace space2 = new AxisSpace();
590        space2 = this.colorBar.reserveSpace(g2, this, area, estimatedDataArea,
591                this.colorBarLocation, space2);
592        Rectangle2D adjustedPlotArea = space2.shrink(area, null);
593
594        Rectangle2D dataArea = space.shrink(adjustedPlotArea, null);
595
596        Rectangle2D colorBarArea = space2.reserved(area, this.colorBarLocation);
597
598        // additional dataArea modifications
599        if (getDataAreaRatio() != 0.0) { //check whether modification is
600            double ratio = getDataAreaRatio();
601            Rectangle2D tmpDataArea = (Rectangle2D) dataArea.clone();
602            double h = tmpDataArea.getHeight();
603            double w = tmpDataArea.getWidth();
604
605            if (ratio > 0) { // ratio represents pixels
606                if (w * ratio <= h) {
607                    h = ratio * w;
608                }
609                else {
610                    w = h / ratio;
611                }
612            }
613            else {  // ratio represents axis units
614                ratio *= -1.0;
615                double xLength = getDomainAxis().getRange().getLength();
616                double yLength = getRangeAxis().getRange().getLength();
617                double unitRatio = yLength / xLength;
618
619                ratio = unitRatio * ratio;
620
621                if (w * ratio <= h) {
622                    h = ratio * w;
623                }
624                else {
625                    w = h / ratio;
626                }
627            }
628
629            dataArea.setRect(tmpDataArea.getX() + tmpDataArea.getWidth() / 2
630                    - w / 2, tmpDataArea.getY(), w, h);
631        }
632
633        if (info != null) {
634            info.setDataArea(dataArea);
635        }
636
637        CrosshairState crosshairState = new CrosshairState();
638        crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
639
640        // draw the plot background...
641        drawBackground(g2, dataArea);
642
643        double cursor = dataArea.getMaxY();
644        if (this.domainAxis != null) {
645            this.domainAxis.draw(g2, cursor, adjustedPlotArea, dataArea,
646                    RectangleEdge.BOTTOM, info);
647        }
648
649        if (this.rangeAxis != null) {
650            cursor = dataArea.getMinX();
651            this.rangeAxis.draw(g2, cursor, adjustedPlotArea, dataArea,
652                    RectangleEdge.LEFT, info);
653        }
654
655        if (this.colorBar != null) {
656            cursor = 0.0;
657            this.colorBar.draw(g2, cursor, adjustedPlotArea, dataArea,
658                    colorBarArea, this.colorBarLocation);
659        }
660        Shape originalClip = g2.getClip();
661        Composite originalComposite = g2.getComposite();
662
663        g2.clip(dataArea);
664        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
665                getForegroundAlpha()));
666        render(g2, dataArea, info, crosshairState);
667
668        if (this.domainMarkers != null) {
669            Iterator iterator = this.domainMarkers.iterator();
670            while (iterator.hasNext()) {
671                Marker marker = (Marker) iterator.next();
672                drawDomainMarker(g2, this, getDomainAxis(), marker, dataArea);
673            }
674        }
675
676        if (this.rangeMarkers != null) {
677            Iterator iterator = this.rangeMarkers.iterator();
678            while (iterator.hasNext()) {
679                Marker marker = (Marker) iterator.next();
680                drawRangeMarker(g2, this, getRangeAxis(), marker, dataArea);
681            }
682        }
683
684// TO DO:  these annotations only work with XYPlot, see if it is possible to
685// make ContourPlot a subclass of XYPlot (DG);
686
687//        // draw the annotations...
688//        if (this.annotations != null) {
689//            Iterator iterator = this.annotations.iterator();
690//            while (iterator.hasNext()) {
691//                Annotation annotation = (Annotation) iterator.next();
692//                if (annotation instanceof XYAnnotation) {
693//                    XYAnnotation xya = (XYAnnotation) annotation;
694//                    // get the annotation to draw itself...
695//                    xya.draw(g2, this, dataArea, getDomainAxis(),
696//                             getRangeAxis());
697//                }
698//            }
699//        }
700
701        g2.setClip(originalClip);
702        g2.setComposite(originalComposite);
703        drawOutline(g2, dataArea);
704
705    }
706
707    /**
708     * Draws a representation of the data within the dataArea region, using the
709     * current renderer.
710     * <P>
711     * The <code>info</code> and <code>crosshairState</code> arguments may be
712     * <code>null</code>.
713     *
714     * @param g2  the graphics device.
715     * @param dataArea  the region in which the data is to be drawn.
716     * @param info  an optional object for collection dimension information.
717     * @param crosshairState  an optional object for collecting crosshair info.
718     */
719    public void render(Graphics2D g2, Rectangle2D dataArea,
720                       PlotRenderingInfo info, CrosshairState crosshairState) {
721
722        // now get the data and plot it (the visual representation will depend
723        // on the renderer that has been set)...
724        ContourDataset data = getDataset();
725        if (data != null) {
726
727            ColorBar zAxis = getColorBar();
728
729            if (this.clipPath != null) {
730                GeneralPath clipper = getClipPath().draw(g2, dataArea,
731                        this.domainAxis, this.rangeAxis);
732                if (this.clipPath.isClip()) {
733                    g2.clip(clipper);
734                }
735            }
736
737            if (this.renderAsPoints) {
738                pointRenderer(g2, dataArea, info, this, this.domainAxis,
739                        this.rangeAxis, zAxis, data, crosshairState);
740            }
741            else {
742                contourRenderer(g2, dataArea, info, this, this.domainAxis,
743                        this.rangeAxis, zAxis, data, crosshairState);
744            }
745
746            // draw vertical crosshair if required...
747            setDomainCrosshairValue(crosshairState.getCrosshairX(), false);
748            if (isDomainCrosshairVisible()) {
749                drawVerticalLine(g2, dataArea,
750                                 getDomainCrosshairValue(),
751                                 getDomainCrosshairStroke(),
752                                 getDomainCrosshairPaint());
753            }
754
755            // draw horizontal crosshair if required...
756            setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
757            if (isRangeCrosshairVisible()) {
758                drawHorizontalLine(g2, dataArea,
759                                   getRangeCrosshairValue(),
760                                   getRangeCrosshairStroke(),
761                                   getRangeCrosshairPaint());
762            }
763
764        }
765        else if (this.clipPath != null) {
766            getClipPath().draw(g2, dataArea, this.domainAxis, this.rangeAxis);
767        }
768
769    }
770
771    /**
772     * Fills the plot.
773     *
774     * @param g2  the graphics device.
775     * @param dataArea  the area within which the data is being drawn.
776     * @param info  collects information about the drawing.
777     * @param plot  the plot (can be used to obtain standard color
778     *              information etc).
779     * @param horizontalAxis  the domain (horizontal) axis.
780     * @param verticalAxis  the range (vertical) axis.
781     * @param colorBar  the color bar axis.
782     * @param data  the dataset.
783     * @param crosshairState  information about crosshairs on a plot.
784     */
785    public void contourRenderer(Graphics2D g2,
786                                Rectangle2D dataArea,
787                                PlotRenderingInfo info,
788                                ContourPlot plot,
789                                ValueAxis horizontalAxis,
790                                ValueAxis verticalAxis,
791                                ColorBar colorBar,
792                                ContourDataset data,
793                                CrosshairState crosshairState) {
794
795        // setup for collecting optional entity info...
796        Rectangle2D.Double entityArea;
797        EntityCollection entities = null;
798        if (info != null) {
799            entities = info.getOwner().getEntityCollection();
800        }
801
802        Rectangle2D.Double rect;
803        rect = new Rectangle2D.Double();
804
805        //turn off anti-aliasing when filling rectangles
806        Object antiAlias = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
807        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
808                RenderingHints.VALUE_ANTIALIAS_OFF);
809
810        // get the data points
811        Number[] xNumber = data.getXValues();
812        Number[] yNumber = data.getYValues();
813        Number[] zNumber = data.getZValues();
814
815        double[] x = new double[xNumber.length];
816        double[] y = new double[yNumber.length];
817
818        for (int i = 0; i < x.length; i++) {
819            x[i] = xNumber[i].doubleValue();
820            y[i] = yNumber[i].doubleValue();
821        }
822
823        int[] xIndex = data.indexX();
824        int[] indexX = data.getXIndices();
825        boolean vertInverted = ((NumberAxis) verticalAxis).isInverted();
826        boolean horizInverted = false;
827        if (horizontalAxis instanceof NumberAxis) {
828            horizInverted = ((NumberAxis) horizontalAxis).isInverted();
829        }
830        double transX = 0.0;
831        double transXm1;
832        double transXp1;
833        double transDXm1;
834        double transDXp1 = 0.0;
835        double transDX = 0.0;
836        double transY;
837        double transYm1;
838        double transYp1;
839        double transDYm1;
840        double transDYp1 = 0.0;
841        double transDY;
842        int iMax = xIndex[xIndex.length - 1];
843        for (int k = 0; k < x.length; k++) {
844            int i = xIndex[k];
845            if (indexX[i] == k) { // this is a new column
846                if (i == 0) {
847                    transX = horizontalAxis.valueToJava2D(x[k], dataArea,
848                            RectangleEdge.BOTTOM);
849                    transXm1 = transX;
850                    transXp1 = horizontalAxis.valueToJava2D(
851                            x[indexX[i + 1]], dataArea, RectangleEdge.BOTTOM);
852                    transDXm1 = Math.abs(0.5 * (transX - transXm1));
853                    transDXp1 = Math.abs(0.5 * (transX - transXp1));
854                }
855                else if (i == iMax) {
856                    transX = horizontalAxis.valueToJava2D(x[k], dataArea,
857                            RectangleEdge.BOTTOM);
858                    transXm1 = horizontalAxis.valueToJava2D(x[indexX[i - 1]],
859                            dataArea, RectangleEdge.BOTTOM);
860                    transXp1 = transX;
861                    transDXm1 = Math.abs(0.5 * (transX - transXm1));
862                    transDXp1 = Math.abs(0.5 * (transX - transXp1));
863                }
864                else {
865                    transX = horizontalAxis.valueToJava2D(x[k], dataArea,
866                            RectangleEdge.BOTTOM);
867                    transXp1 = horizontalAxis.valueToJava2D(x[indexX[i + 1]],
868                            dataArea, RectangleEdge.BOTTOM);
869                    transDXm1 = transDXp1;
870                    transDXp1 = Math.abs(0.5 * (transX - transXp1));
871                }
872
873                if (horizInverted) {
874                    transX -= transDXp1;
875                }
876                else {
877                    transX -= transDXm1;
878                }
879
880                transDX = transDXm1 + transDXp1;
881
882                transY = verticalAxis.valueToJava2D(y[k], dataArea,
883                        RectangleEdge.LEFT);
884                transYm1 = transY;
885                if (k + 1 == y.length) {
886                    continue;
887                }
888                transYp1 = verticalAxis.valueToJava2D(y[k + 1], dataArea,
889                        RectangleEdge.LEFT);
890                transDYm1 = Math.abs(0.5 * (transY - transYm1));
891                transDYp1 = Math.abs(0.5 * (transY - transYp1));
892            }
893            else if ((i < indexX.length - 1
894                     && indexX[i + 1] - 1 == k) || k == x.length - 1) {
895                // end of column
896                transY = verticalAxis.valueToJava2D(y[k], dataArea,
897                        RectangleEdge.LEFT);
898                transYm1 = verticalAxis.valueToJava2D(y[k - 1], dataArea,
899                        RectangleEdge.LEFT);
900                transYp1 = transY;
901                transDYm1 = Math.abs(0.5 * (transY - transYm1));
902                transDYp1 = Math.abs(0.5 * (transY - transYp1));
903            }
904            else {
905                transY = verticalAxis.valueToJava2D(y[k], dataArea,
906                        RectangleEdge.LEFT);
907                transYp1 = verticalAxis.valueToJava2D(y[k + 1], dataArea,
908                        RectangleEdge.LEFT);
909                transDYm1 = transDYp1;
910                transDYp1 = Math.abs(0.5 * (transY - transYp1));
911            }
912            if (vertInverted) {
913                transY -= transDYm1;
914            }
915            else {
916                transY -= transDYp1;
917            }
918
919            transDY = transDYm1 + transDYp1;
920
921            rect.setRect(transX, transY, transDX, transDY);
922            if (zNumber[k] != null) {
923                g2.setPaint(colorBar.getPaint(zNumber[k].doubleValue()));
924                g2.fill(rect);
925            }
926            else if (this.missingPaint != null) {
927                g2.setPaint(this.missingPaint);
928                g2.fill(rect);
929            }
930
931            entityArea = rect;
932
933            // add an entity for the item...
934            if (entities != null) {
935                String tip = "";
936                if (getToolTipGenerator() != null) {
937                    tip = this.toolTipGenerator.generateToolTip(data, k);
938                }
939//              Shape s = g2.getClip();
940//              if (s.contains(rect) || s.intersects(rect)) {
941                String url = null;
942                // if (getURLGenerator() != null) {    //dmo: look at this later
943                //      url = getURLGenerator().generateURL(data, series, item);
944                // }
945                // Unlike XYItemRenderer, we need to clone entityArea since it
946                // reused.
947                ContourEntity entity = new ContourEntity(
948                        (Rectangle2D.Double) entityArea.clone(), tip, url);
949                entity.setIndex(k);
950                entities.add(entity);
951//              }
952            }
953
954            // do we need to update the crosshair values?
955            if (plot.isDomainCrosshairLockedOnData()) {
956                if (plot.isRangeCrosshairLockedOnData()) {
957                    // both axes
958                    crosshairState.updateCrosshairPoint(x[k], y[k], transX,
959                            transY, PlotOrientation.VERTICAL);
960                }
961                else {
962                    // just the horizontal axis...
963                    crosshairState.updateCrosshairX(transX);
964                }
965            }
966            else {
967                if (plot.isRangeCrosshairLockedOnData()) {
968                    // just the vertical axis...
969                    crosshairState.updateCrosshairY(transY);
970                }
971            }
972        }
973
974        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias);
975
976    }
977
978    /**
979     * Draws the visual representation of a single data item.
980     *
981     * @param g2  the graphics device.
982     * @param dataArea  the area within which the data is being drawn.
983     * @param info  collects information about the drawing.
984     * @param plot  the plot (can be used to obtain standard color
985     *              information etc).
986     * @param domainAxis  the domain (horizontal) axis.
987     * @param rangeAxis  the range (vertical) axis.
988     * @param colorBar  the color bar axis.
989     * @param data  the dataset.
990     * @param crosshairState  information about crosshairs on a plot.
991     */
992    public void pointRenderer(Graphics2D g2,
993                              Rectangle2D dataArea,
994                              PlotRenderingInfo info,
995                              ContourPlot plot,
996                              ValueAxis domainAxis,
997                              ValueAxis rangeAxis,
998                              ColorBar colorBar,
999                              ContourDataset data,
1000                              CrosshairState crosshairState) {
1001
1002        // setup for collecting optional entity info...
1003        RectangularShape entityArea;
1004        EntityCollection entities = null;
1005        if (info != null) {
1006            entities = info.getOwner().getEntityCollection();
1007        }
1008
1009//      Rectangle2D.Double rect = null;
1010//      rect = new Rectangle2D.Double();
1011        RectangularShape rect = new Ellipse2D.Double();
1012
1013
1014        //turn off anti-aliasing when filling rectangles
1015        Object antiAlias = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
1016        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1017                RenderingHints.VALUE_ANTIALIAS_OFF);
1018
1019        // if (tooltips!=null) tooltips.clearToolTips(); // reset collection
1020        // get the data points
1021        Number[] xNumber = data.getXValues();
1022        Number[] yNumber = data.getYValues();
1023        Number[] zNumber = data.getZValues();
1024
1025        double[] x = new double[xNumber.length];
1026        double[] y = new double[yNumber.length];
1027
1028        for (int i = 0; i < x.length; i++) {
1029            x[i] = xNumber[i].doubleValue();
1030            y[i] = yNumber[i].doubleValue();
1031        }
1032
1033        double transX;
1034        double transDX;
1035        double transY;
1036        double transDY;
1037        double size = dataArea.getWidth() * this.ptSizePct;
1038        for (int k = 0; k < x.length; k++) {
1039
1040            transX = domainAxis.valueToJava2D(x[k], dataArea,
1041                    RectangleEdge.BOTTOM) - 0.5 * size;
1042            transY = rangeAxis.valueToJava2D(y[k], dataArea, RectangleEdge.LEFT)
1043                     - 0.5 * size;
1044            transDX = size;
1045            transDY = size;
1046
1047            rect.setFrame(transX, transY, transDX, transDY);
1048
1049            if (zNumber[k] != null) {
1050                g2.setPaint(colorBar.getPaint(zNumber[k].doubleValue()));
1051                g2.fill(rect);
1052            }
1053            else if (this.missingPaint != null) {
1054                g2.setPaint(this.missingPaint);
1055                g2.fill(rect);
1056            }
1057
1058
1059            entityArea = rect;
1060
1061            // add an entity for the item...
1062            if (entities != null) {
1063                String tip = null;
1064                if (getToolTipGenerator() != null) {
1065                    tip = this.toolTipGenerator.generateToolTip(data, k);
1066                }
1067                String url = null;
1068                // if (getURLGenerator() != null) {   //dmo: look at this later
1069                //   url = getURLGenerator().generateURL(data, series, item);
1070                // }
1071                // Unlike XYItemRenderer, we need to clone entityArea since it
1072                // reused.
1073                ContourEntity entity = new ContourEntity(
1074                        (RectangularShape) entityArea.clone(), tip, url);
1075                entity.setIndex(k);
1076                entities.add(entity);
1077            }
1078
1079            // do we need to update the crosshair values?
1080            if (plot.isDomainCrosshairLockedOnData()) {
1081                if (plot.isRangeCrosshairLockedOnData()) {
1082                    // both axes
1083                    crosshairState.updateCrosshairPoint(x[k], y[k], transX,
1084                            transY, PlotOrientation.VERTICAL);
1085                }
1086                else {
1087                    // just the horizontal axis...
1088                    crosshairState.updateCrosshairX(transX);
1089                }
1090            }
1091            else {
1092                if (plot.isRangeCrosshairLockedOnData()) {
1093                    // just the vertical axis...
1094                    crosshairState.updateCrosshairY(transY);
1095                }
1096            }
1097        }
1098
1099
1100        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias);
1101
1102    }
1103
1104    /**
1105     * Utility method for drawing a crosshair on the chart (if required).
1106     *
1107     * @param g2  The graphics device.
1108     * @param dataArea  The data area.
1109     * @param value  The coordinate, where to draw the line.
1110     * @param stroke  The stroke to use.
1111     * @param paint  The paint to use.
1112     */
1113    protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea,
1114                                    double value, Stroke stroke, Paint paint) {
1115
1116        double xx = getDomainAxis().valueToJava2D(value, dataArea,
1117                RectangleEdge.BOTTOM);
1118        Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx,
1119                dataArea.getMaxY());
1120        g2.setStroke(stroke);
1121        g2.setPaint(paint);
1122        g2.draw(line);
1123
1124    }
1125
1126    /**
1127     * Utility method for drawing a crosshair on the chart (if required).
1128     *
1129     * @param g2  The graphics device.
1130     * @param dataArea  The data area.
1131     * @param value  The coordinate, where to draw the line.
1132     * @param stroke  The stroke to use.
1133     * @param paint  The paint to use.
1134     */
1135    protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea,
1136                                      double value, Stroke stroke,
1137                                      Paint paint) {
1138
1139        double yy = getRangeAxis().valueToJava2D(value, dataArea,
1140                RectangleEdge.LEFT);
1141        Line2D line = new Line2D.Double(dataArea.getMinX(), yy,
1142                dataArea.getMaxX(), yy);
1143        g2.setStroke(stroke);
1144        g2.setPaint(paint);
1145        g2.draw(line);
1146
1147    }
1148
1149    /**
1150     * Handles a 'click' on the plot by updating the anchor values...
1151     *
1152     * @param x  x-coordinate, where the click occured.
1153     * @param y  y-coordinate, where the click occured.
1154     * @param info  An object for collection dimension information.
1155     */
1156    @Override
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    @Override
1188    public void zoom(double percent) {
1189
1190        if (percent > 0) {
1191          //  double range = this.domainAxis.getRange().getLength();
1192          //  double scaledRange = range * percent;
1193          //  domainAxis.setAnchoredRange(scaledRange);
1194
1195          //  range = this.rangeAxis.getRange().getLength();
1196         //  scaledRange = range * percent;
1197         //   rangeAxis.setAnchoredRange(scaledRange);
1198        }
1199        else {
1200            getRangeAxis().setAutoRange(true);
1201            getDomainAxis().setAutoRange(true);
1202        }
1203
1204    }
1205
1206    /**
1207     * Returns the plot type as a string.
1208     *
1209     * @return A short string describing the type of plot.
1210     */
1211    @Override
1212    public String getPlotType() {
1213        return localizationResources.getString("Contour_Plot");
1214    }
1215
1216    /**
1217     * Returns the range for an axis.
1218     *
1219     * @param axis  the axis.
1220     *
1221     * @return The range for an axis.
1222     */
1223    @Override
1224    public Range getDataRange(ValueAxis axis) {
1225
1226        if (this.dataset == null) {
1227            return null;
1228        }
1229
1230        Range result = null;
1231
1232        if (axis == getDomainAxis()) {
1233            result = DatasetUtilities.findDomainBounds(this.dataset);
1234        }
1235        else if (axis == getRangeAxis()) {
1236            result = DatasetUtilities.findRangeBounds(this.dataset);
1237        }
1238        return result;
1239    }
1240
1241    /**
1242     * Returns the range for the Contours.
1243     *
1244     * @return The range for the Contours (z-axis).
1245     */
1246    @Override
1247    public Range getContourDataRange() {
1248        Range result = null;
1249        ContourDataset data = getDataset();
1250        if (data != null) {
1251            Range h = getDomainAxis().getRange();
1252            Range v = getRangeAxis().getRange();
1253            result = this.visibleRange(data, h, v);
1254        }
1255        return result;
1256    }
1257
1258    /**
1259     * Notifies all registered listeners of a property change.
1260     * <P>
1261     * One source of property change events is the plot's renderer.
1262     *
1263     * @param event  Information about the property change.
1264     */
1265    @Override
1266    public void propertyChange(PropertyChangeEvent event) {
1267        fireChangeEvent();
1268    }
1269
1270    /**
1271     * Receives notification of a change to the plot's dataset.
1272     * <P>
1273     * The chart reacts by passing on a chart change event to all registered
1274     * listeners.
1275     *
1276     * @param event  Information about the event (not used here).
1277     */
1278    @Override
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    @Override
1711    public void axisChanged(AxisChangeEvent event) {
1712        Object source = event.getSource();
1713        if (source.equals(this.rangeAxis) || source.equals(this.domainAxis)) {
1714            ColorBar cba = this.colorBar;
1715            if (this.colorBar.getAxis().isAutoRange()) {
1716                cba.getAxis().configure();
1717            }
1718
1719        }
1720        super.axisChanged(event);
1721    }
1722
1723    /**
1724     * Returns the visible z-range.
1725     *
1726     * @param data  the dataset.
1727     * @param x  the x range.
1728     * @param y  the y range.
1729     *
1730     * @return The range.
1731     */
1732    public Range visibleRange(ContourDataset data, Range x, Range y) {
1733        Range range = 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    @Override
1826    public Object clone() throws CloneNotSupportedException {
1827        ContourPlot clone = (ContourPlot) super.clone();
1828
1829        if (this.domainAxis != null) {
1830            clone.domainAxis = (ValueAxis) this.domainAxis.clone();
1831            clone.domainAxis.setPlot(clone);
1832            clone.domainAxis.addChangeListener(clone);
1833        }
1834        if (this.rangeAxis != null) {
1835            clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
1836            clone.rangeAxis.setPlot(clone);
1837            clone.rangeAxis.addChangeListener(clone);
1838        }
1839
1840        if (clone.dataset != null) {
1841            clone.dataset.addChangeListener(clone);
1842        }
1843
1844        if (this.colorBar != null) {
1845            clone.colorBar = (ColorBar) this.colorBar.clone();
1846        }
1847
1848        clone.domainMarkers = (List) ObjectUtilities.deepClone(
1849                this.domainMarkers);
1850        clone.rangeMarkers = (List) ObjectUtilities.deepClone(
1851                this.rangeMarkers);
1852        clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
1853
1854        if (this.clipPath != null) {
1855            clone.clipPath = (ClipPath) this.clipPath.clone();
1856        }
1857
1858        return clone;
1859    }
1860
1861}