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