001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * ---------------------------
028     * StandardXYItemRenderer.java
029     * ---------------------------
030     * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Mark Watson (www.markwatson.com);
034     *                   Jonathan Nash;
035     *                   Andreas Schneider;
036     *                   Norbert Kiesel (for TBD Networks);
037     *                   Christian W. Zuckschwerdt;
038     *                   Bill Kelemen;
039     *                   Nicolas Brodu (for Astrium and EADS Corporate Research 
040     *                   Center);
041     *
042     * $Id: StandardXYItemRenderer.java,v 1.18.2.14 2007/06/08 13:29:38 mungady Exp $
043     *
044     * Changes:
045     * --------
046     * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
047     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
048     * 21-Dec-2001 : Added working line instance to improve performance (DG);
049     * 22-Jan-2002 : Added code to lock crosshairs to data points.  Based on code 
050     *               by Jonathan Nash (DG);
051     * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
052     * 28-Mar-2002 : Added a property change listener mechanism so that the 
053     *               renderer no longer needs to be immutable (DG);
054     * 02-Apr-2002 : Modified to handle null values (DG);
055     * 09-Apr-2002 : Modified draw method to return void.  Removed the translated 
056     *               zero from the drawItem method.  Override the initialise() 
057     *               method to calculate it (DG);
058     * 13-May-2002 : Added code from Andreas Schneider to allow changing 
059     *               shapes/colors per item (DG);
060     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
061     * 25-Jun-2002 : Removed redundant code (DG);
062     * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
063     * 08-Aug-2002 : Added discontinuous lines option contributed by 
064     *               Norbert Kiesel (DG);
065     * 20-Aug-2002 : Added user definable default values to be returned by 
066     *               protected methods unless overridden by a subclass (DG);
067     * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
068     * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
069     * 25-Mar-2003 : Implemented Serializable (DG);
070     * 01-May-2003 : Modified drawItem() method signature (DG);
071     * 15-May-2003 : Modified to take into account the plot orientation (DG);
072     * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
073     * 30-Jul-2003 : Modified entity constructor (CZ);
074     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
075     * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
076     * 08-Sep-2003 : Fixed serialization (NB);
077     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
078     * 21-Jan-2004 : Override for getLegendItem() method (DG);
079     * 27-Jan-2004 : Moved working line into state object (DG);
080     * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding 
081     *               easier (DG);
082     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
083     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
084     * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
085     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
086     *               getYValue() (DG);
087     * 25-Aug-2004 : Created addEntity() method in superclass (DG);
088     * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
089     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
090     * 23-Feb-2005 : Fixed getLegendItem() method to show lines.  Fixed bug
091     *               1077108 (shape not visible for first item in series) (DG);
092     * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG);
093     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
094     * 27-Apr-2005 : Use generator for series label in legend (DG);
095     * ------------- JFREECHART 1.0.x ---------------------------------------------
096     * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG); 
097     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
098     * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG);
099     * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG);
100     * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer 
101     *               change (DG);
102     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() 
103     *               method (DG);
104     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
105     * 08-Jun-2007 : Fixed bug in entity creation (DG);
106     *
107     */
108    
109    package org.jfree.chart.renderer.xy;
110    
111    
112    import java.awt.Graphics2D;
113    import java.awt.Image;
114    import java.awt.Paint;
115    import java.awt.Point;
116    import java.awt.Shape;
117    import java.awt.Stroke;
118    import java.awt.geom.GeneralPath;
119    import java.awt.geom.Line2D;
120    import java.awt.geom.Rectangle2D;
121    import java.io.IOException;
122    import java.io.ObjectInputStream;
123    import java.io.ObjectOutputStream;
124    import java.io.Serializable;
125    
126    import org.jfree.chart.LegendItem;
127    import org.jfree.chart.axis.ValueAxis;
128    import org.jfree.chart.entity.EntityCollection;
129    import org.jfree.chart.event.RendererChangeEvent;
130    import org.jfree.chart.labels.XYToolTipGenerator;
131    import org.jfree.chart.plot.CrosshairState;
132    import org.jfree.chart.plot.Plot;
133    import org.jfree.chart.plot.PlotOrientation;
134    import org.jfree.chart.plot.PlotRenderingInfo;
135    import org.jfree.chart.plot.XYPlot;
136    import org.jfree.chart.urls.XYURLGenerator;
137    import org.jfree.data.xy.XYDataset;
138    import org.jfree.io.SerialUtilities;
139    import org.jfree.ui.RectangleEdge;
140    import org.jfree.util.BooleanList;
141    import org.jfree.util.BooleanUtilities;
142    import org.jfree.util.ObjectUtilities;
143    import org.jfree.util.PublicCloneable;
144    import org.jfree.util.ShapeUtilities;
145    import org.jfree.util.UnitType;
146    
147    /**
148     * Standard item renderer for an {@link XYPlot}.  This class can draw (a) 
149     * shapes at each point, or (b) lines between points, or (c) both shapes and 
150     * lines.
151     * <P>
152     * This renderer has been retained for historical reasons and, in general, you
153     * should use the {@link XYLineAndShapeRenderer} class instead.
154     */
155    public class StandardXYItemRenderer extends AbstractXYItemRenderer 
156            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
157    
158        /** For serialization. */
159        private static final long serialVersionUID = -3271351259436865995L;
160        
161        /** Constant for the type of rendering (shapes only). */
162        public static final int SHAPES = 1;
163    
164        /** Constant for the type of rendering (lines only). */
165        public static final int LINES = 2;
166    
167        /** Constant for the type of rendering (shapes and lines). */
168        public static final int SHAPES_AND_LINES = SHAPES | LINES;
169    
170        /** Constant for the type of rendering (images only). */
171        public static final int IMAGES = 4;
172    
173        /** Constant for the type of rendering (discontinuous lines). */
174        public static final int DISCONTINUOUS = 8;
175    
176        /** Constant for the type of rendering (discontinuous lines). */
177        public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
178    
179        /** A flag indicating whether or not shapes are drawn at each XY point. */
180        private boolean baseShapesVisible;
181    
182        /** A flag indicating whether or not lines are drawn between XY points. */
183        private boolean plotLines;
184    
185        /** A flag indicating whether or not images are drawn between XY points. */
186        private boolean plotImages;
187    
188        /** A flag controlling whether or not discontinuous lines are used. */
189        private boolean plotDiscontinuous;
190    
191        /** Specifies how the gap threshold value is interpreted. */
192        private UnitType gapThresholdType = UnitType.RELATIVE;
193        
194        /** Threshold for deciding when to discontinue a line. */
195        private double gapThreshold = 1.0;
196    
197        /** A flag that controls whether or not shapes are filled for ALL series. */
198        private Boolean shapesFilled;
199    
200        /** 
201         * A table of flags that control (per series) whether or not shapes are 
202         * filled. 
203         */
204        private BooleanList seriesShapesFilled;
205    
206        /** The default value returned by the getShapeFilled() method. */
207        private boolean baseShapesFilled;
208    
209        /** 
210         * A flag that controls whether or not each series is drawn as a single 
211         * path. 
212         */
213        private boolean drawSeriesLineAsPath;
214    
215        /** 
216         * The shape that is used to represent a line in the legend. 
217         * This should never be set to <code>null</code>. 
218         */
219        private transient Shape legendLine;
220        
221        /**
222         * Constructs a new renderer.
223         */
224        public StandardXYItemRenderer() {
225            this(LINES, null);
226        }
227    
228        /**
229         * Constructs a new renderer.  To specify the type of renderer, use one of 
230         * the constants: {@link #SHAPES}, {@link #LINES} or 
231         * {@link #SHAPES_AND_LINES}.
232         *
233         * @param type  the type.
234         */
235        public StandardXYItemRenderer(int type) {
236            this(type, null);
237        }
238    
239        /**
240         * Constructs a new renderer.  To specify the type of renderer, use one of 
241         * the constants: {@link #SHAPES}, {@link #LINES} or 
242         * {@link #SHAPES_AND_LINES}.
243         *
244         * @param type  the type of renderer.
245         * @param toolTipGenerator  the item label generator (<code>null</code> 
246         *                          permitted).
247         */
248        public StandardXYItemRenderer(int type, 
249                                      XYToolTipGenerator toolTipGenerator) {
250            this(type, toolTipGenerator, null);
251        }
252    
253        /**
254         * Constructs a new renderer.  To specify the type of renderer, use one of 
255         * the constants: {@link #SHAPES}, {@link #LINES} or 
256         * {@link #SHAPES_AND_LINES}.
257         *
258         * @param type  the type of renderer.
259         * @param toolTipGenerator  the item label generator (<code>null</code> 
260         *                          permitted).
261         * @param urlGenerator  the URL generator.
262         */
263        public StandardXYItemRenderer(int type,
264                                      XYToolTipGenerator toolTipGenerator,
265                                      XYURLGenerator urlGenerator) {
266    
267            super();
268            setBaseToolTipGenerator(toolTipGenerator);
269            setURLGenerator(urlGenerator);
270            if ((type & SHAPES) != 0) {
271                this.baseShapesVisible = true;
272            }
273            if ((type & LINES) != 0) {
274                this.plotLines = true;
275            }
276            if ((type & IMAGES) != 0) {
277                this.plotImages = true;
278            }
279            if ((type & DISCONTINUOUS) != 0) {
280                this.plotDiscontinuous = true;
281            }
282    
283            this.shapesFilled = null;
284            this.seriesShapesFilled = new BooleanList();
285            this.baseShapesFilled = true;
286            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
287            this.drawSeriesLineAsPath = false;
288        }
289    
290        /**
291         * Returns true if shapes are being plotted by the renderer.
292         *
293         * @return <code>true</code> if shapes are being plotted by the renderer.
294         * 
295         * @see #setBaseShapesVisible
296         */
297        public boolean getBaseShapesVisible() {
298            return this.baseShapesVisible;
299        }
300    
301        /**
302         * Sets the flag that controls whether or not a shape is plotted at each 
303         * data point.
304         *
305         * @param flag  the flag.
306         * 
307         * @see #getBaseShapesVisible
308         */
309        public void setBaseShapesVisible(boolean flag) {
310            if (this.baseShapesVisible != flag) {
311                this.baseShapesVisible = flag;
312                notifyListeners(new RendererChangeEvent(this));
313            }
314        }
315    
316        // SHAPES FILLED
317    
318        /**
319         * Returns the flag used to control whether or not the shape for an item is
320         * filled.
321         * <p>
322         * The default implementation passes control to the 
323         * <code>getSeriesShapesFilled</code> method.  You can override this method 
324         * if you require different behaviour.
325         *
326         * @param series  the series index (zero-based).
327         * @param item  the item index (zero-based).
328         *
329         * @return A boolean.
330         * 
331         * @see #getSeriesShapesFilled(int)
332         */
333        public boolean getItemShapeFilled(int series, int item) {
334            // return the overall setting, if there is one...
335            if (this.shapesFilled != null) {
336                return this.shapesFilled.booleanValue();
337            }
338    
339            // otherwise look up the paint table
340            Boolean flag = this.seriesShapesFilled.getBoolean(series);
341            if (flag != null) {
342                return flag.booleanValue();
343            }
344            else {
345                return this.baseShapesFilled;
346            }
347        }
348    
349        /**
350         * Returns the override flag that controls whether or not shapes are filled
351         * for ALL series.
352         * 
353         * @return The flag (possibly <code>null</code>).
354         * 
355         * @since 1.0.5
356         */
357        public Boolean getShapesFilled() {
358            return this.shapesFilled;
359        }
360        
361        /**
362         * Sets the 'shapes filled' for ALL series.
363         *
364         * @param filled  the flag.
365         * 
366         * @see #setShapesFilled(Boolean)
367         */
368        public void setShapesFilled(boolean filled) {
369            // here we use BooleanUtilities to remain compatible with JDKs < 1.4 
370            setShapesFilled(BooleanUtilities.valueOf(filled));
371        }
372    
373        /**
374         * Sets the override flag that controls whether or not shapes are filled
375         * for ALL series and sends a {@link RendererChangeEvent} to all registered
376         * listeners. 
377         *
378         * @param filled  the flag (<code>null</code> permitted).
379         * 
380         * @see #setShapesFilled(boolean)
381         */
382        public void setShapesFilled(Boolean filled) {
383            this.shapesFilled = filled;
384            fireChangeEvent();
385        }
386    
387        /**
388         * Returns the flag used to control whether or not the shapes for a series
389         * are filled.
390         *
391         * @param series  the series index (zero-based).
392         *
393         * @return A boolean.
394         */
395        public Boolean getSeriesShapesFilled(int series) {
396            return this.seriesShapesFilled.getBoolean(series);
397        }
398    
399        /**
400         * Sets the 'shapes filled' flag for a series.
401         *
402         * @param series  the series index (zero-based).
403         * @param flag  the flag.
404         * 
405         * @see #getSeriesShapesFilled(int)
406         */
407        public void setSeriesShapesFilled(int series, Boolean flag) {
408            this.seriesShapesFilled.setBoolean(series, flag);
409            fireChangeEvent();
410        }
411    
412        /**
413         * Returns the base 'shape filled' attribute.
414         *
415         * @return The base flag.
416         * 
417         * @see #setBaseShapesFilled(boolean)
418         */
419        public boolean getBaseShapesFilled() {
420            return this.baseShapesFilled;
421        }
422    
423        /**
424         * Sets the base 'shapes filled' flag.
425         *
426         * @param flag  the flag.
427         * 
428         * @see #getBaseShapesFilled()
429         */
430        public void setBaseShapesFilled(boolean flag) {
431            this.baseShapesFilled = flag;
432        }
433    
434        /**
435         * Returns true if lines are being plotted by the renderer.
436         *
437         * @return <code>true</code> if lines are being plotted by the renderer.
438         * 
439         * @see #setPlotLines(boolean)
440         */
441        public boolean getPlotLines() {
442            return this.plotLines;
443        }
444    
445        /**
446         * Sets the flag that controls whether or not a line is plotted between 
447         * each data point.
448         *
449         * @param flag  the flag.
450         * 
451         * @see #getPlotLines()
452         */
453        public void setPlotLines(boolean flag) {
454            if (this.plotLines != flag) {
455                this.plotLines = flag;
456                notifyListeners(new RendererChangeEvent(this));
457            }
458        }
459    
460        /**
461         * Returns the gap threshold type (relative or absolute).
462         * 
463         * @return The type.
464         * 
465         * @see #setGapThresholdType(UnitType)
466         */
467        public UnitType getGapThresholdType() {
468            return this.gapThresholdType;
469        }
470        
471        /**
472         * Sets the gap threshold type.
473         * 
474         * @param thresholdType  the type (<code>null</code> not permitted).
475         * 
476         * @see #getGapThresholdType()
477         */
478        public void setGapThresholdType(UnitType thresholdType) {
479            if (thresholdType == null) {
480                throw new IllegalArgumentException(
481                        "Null 'thresholdType' argument.");
482            }
483            this.gapThresholdType = thresholdType;
484            notifyListeners(new RendererChangeEvent(this));
485        }
486        
487        /**
488         * Returns the gap threshold for discontinuous lines.
489         *
490         * @return The gap threshold.
491         * 
492         * @see #setGapThreshold(double)
493         */
494        public double getGapThreshold() {
495            return this.gapThreshold;
496        }
497    
498        /**
499         * Sets the gap threshold for discontinuous lines.
500         *
501         * @param t  the threshold.
502         * 
503         * @see #getGapThreshold()
504         */
505        public void setGapThreshold(double t) {
506            this.gapThreshold = t;
507            notifyListeners(new RendererChangeEvent(this));
508        }
509    
510        /**
511         * Returns true if images are being plotted by the renderer.
512         *
513         * @return <code>true</code> if images are being plotted by the renderer.
514         * 
515         * @see #setPlotImages(boolean)
516         */
517        public boolean getPlotImages() {
518            return this.plotImages;
519        }
520    
521        /**
522         * Sets the flag that controls whether or not an image is drawn at each 
523         * data point.
524         *
525         * @param flag  the flag.
526         * 
527         * @see #getPlotImages()
528         */
529        public void setPlotImages(boolean flag) {
530            if (this.plotImages != flag) {
531                this.plotImages = flag;
532                notifyListeners(new RendererChangeEvent(this));
533            }
534        }
535    
536        /**
537         * Returns a flag that controls whether or not the renderer shows
538         * discontinuous lines.
539         *
540         * @return <code>true</code> if lines should be discontinuous.
541         */
542        public boolean getPlotDiscontinuous() {
543            return this.plotDiscontinuous;
544        }
545        
546        /**
547         * Sets the flag that controls whether or not the renderer shows
548         * discontinuous lines, and sends a {@link RendererChangeEvent} to all
549         * registered listeners.
550         * 
551         * @param flag  the new flag value.
552         * 
553         * @since 1.0.5
554         */
555        public void setPlotDiscontinuous(boolean flag) {
556            if (this.plotDiscontinuous != flag) {
557                this.plotDiscontinuous = flag;
558                fireChangeEvent();
559            }
560        }
561    
562        /**
563         * Returns a flag that controls whether or not each series is drawn as a 
564         * single path.
565         * 
566         * @return A boolean.
567         * 
568         * @see #setDrawSeriesLineAsPath(boolean)
569         */
570        public boolean getDrawSeriesLineAsPath() {
571            return this.drawSeriesLineAsPath;
572        }
573        
574        /**
575         * Sets the flag that controls whether or not each series is drawn as a 
576         * single path.
577         * 
578         * @param flag  the flag.
579         * 
580         * @see #getDrawSeriesLineAsPath()
581         */
582        public void setDrawSeriesLineAsPath(boolean flag) {
583            this.drawSeriesLineAsPath = flag;
584        }
585        
586        /**
587         * Returns the shape used to represent a line in the legend.
588         * 
589         * @return The legend line (never <code>null</code>).
590         * 
591         * @see #setLegendLine(Shape)
592         */
593        public Shape getLegendLine() {
594            return this.legendLine;   
595        }
596        
597        /**
598         * Sets the shape used as a line in each legend item and sends a 
599         * {@link RendererChangeEvent} to all registered listeners.
600         * 
601         * @param line  the line (<code>null</code> not permitted).
602         * 
603         * @see #getLegendLine()
604         */
605        public void setLegendLine(Shape line) {
606            if (line == null) {
607                throw new IllegalArgumentException("Null 'line' argument.");   
608            }
609            this.legendLine = line;
610            notifyListeners(new RendererChangeEvent(this));
611        }
612    
613        /**
614         * Returns a legend item for a series.
615         *
616         * @param datasetIndex  the dataset index (zero-based).
617         * @param series  the series index (zero-based).
618         *
619         * @return A legend item for the series.
620         */
621        public LegendItem getLegendItem(int datasetIndex, int series) {
622            XYPlot plot = getPlot();
623            if (plot == null) {
624                return null;
625            }
626            LegendItem result = null;
627            XYDataset dataset = plot.getDataset(datasetIndex);
628            if (dataset != null) {
629                if (getItemVisible(series, 0)) {
630                    String label = getLegendItemLabelGenerator().generateLabel(
631                            dataset, series);
632                    String description = label;
633                    String toolTipText = null;
634                    if (getLegendItemToolTipGenerator() != null) {
635                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
636                                dataset, series);
637                    }
638                    String urlText = null;
639                    if (getLegendItemURLGenerator() != null) {
640                        urlText = getLegendItemURLGenerator().generateLabel(
641                                dataset, series);
642                    }
643                    Shape shape = lookupSeriesShape(series);
644                    boolean shapeFilled = getItemShapeFilled(series, 0);
645                    Paint paint = lookupSeriesPaint(series);
646                    Paint linePaint = paint;
647                    Stroke lineStroke = lookupSeriesStroke(series);
648                    result = new LegendItem(label, description, toolTipText, 
649                            urlText, this.baseShapesVisible, shape, shapeFilled,
650                            paint, !shapeFilled, paint, lineStroke, 
651                            this.plotLines, this.legendLine, lineStroke, linePaint);
652                    result.setDataset(dataset);
653                    result.setDatasetIndex(datasetIndex);
654                    result.setSeriesKey(dataset.getSeriesKey(series));
655                    result.setSeriesIndex(series);
656                }
657            }
658            return result;
659        }
660    
661        /**
662         * Records the state for the renderer.  This is used to preserve state 
663         * information between calls to the drawItem() method for a single chart 
664         * drawing.
665         */
666        public static class State extends XYItemRendererState {
667            
668            /** The path for the current series. */
669            public GeneralPath seriesPath;
670            
671            /** The series index. */
672            private int seriesIndex;
673            
674            /** 
675             * A flag that indicates if the last (x, y) point was 'good' 
676             * (non-null). 
677             */
678            private boolean lastPointGood;
679            
680            /**
681             * Creates a new state instance.
682             * 
683             * @param info  the plot rendering info.
684             */
685            public State(PlotRenderingInfo info) {
686                super(info);
687            }
688            
689            /**
690             * Returns a flag that indicates if the last point drawn (in the 
691             * current series) was 'good' (non-null).
692             * 
693             * @return A boolean.
694             */
695            public boolean isLastPointGood() {
696                return this.lastPointGood;
697            }
698            
699            /**
700             * Sets a flag that indicates if the last point drawn (in the current 
701             * series) was 'good' (non-null).
702             * 
703             * @param good  the flag.
704             */
705            public void setLastPointGood(boolean good) {
706                this.lastPointGood = good;
707            }
708            
709            /**
710             * Returns the series index for the current path.
711             * 
712             * @return The series index for the current path.
713             */
714            public int getSeriesIndex() {
715                return this.seriesIndex;
716            }
717            
718            /**
719             * Sets the series index for the current path.
720             * 
721             * @param index  the index.
722             */
723            public void setSeriesIndex(int index) {
724                this.seriesIndex = index;
725            }
726        }
727        
728        /**
729         * Initialises the renderer.
730         * <P>
731         * This method will be called before the first item is rendered, giving the
732         * renderer an opportunity to initialise any state information it wants to 
733         * maintain. The renderer can do nothing if it chooses.
734         *
735         * @param g2  the graphics device.
736         * @param dataArea  the area inside the axes.
737         * @param plot  the plot.
738         * @param data  the data.
739         * @param info  an optional info collection object to return data back to 
740         *              the caller.
741         *
742         * @return The renderer state.
743         */
744        public XYItemRendererState initialise(Graphics2D g2,
745                                              Rectangle2D dataArea,
746                                              XYPlot plot,
747                                              XYDataset data,
748                                              PlotRenderingInfo info) {
749    
750            State state = new State(info);
751            state.seriesPath = new GeneralPath();
752            state.seriesIndex = -1;
753            return state;
754    
755        }
756        
757        /**
758         * Draws the visual representation of a single data item.
759         *
760         * @param g2  the graphics device.
761         * @param state  the renderer state.
762         * @param dataArea  the area within which the data is being drawn.
763         * @param info  collects information about the drawing.
764         * @param plot  the plot (can be used to obtain standard color information 
765         *              etc).
766         * @param domainAxis  the domain axis.
767         * @param rangeAxis  the range axis.
768         * @param dataset  the dataset.
769         * @param series  the series index (zero-based).
770         * @param item  the item index (zero-based).
771         * @param crosshairState  crosshair information for the plot 
772         *                        (<code>null</code> permitted).
773         * @param pass  the pass index.
774         */
775        public void drawItem(Graphics2D g2, 
776                             XYItemRendererState state,
777                             Rectangle2D dataArea, 
778                             PlotRenderingInfo info, 
779                             XYPlot plot,
780                             ValueAxis domainAxis, 
781                             ValueAxis rangeAxis, 
782                             XYDataset dataset,
783                             int series, 
784                             int item, 
785                             CrosshairState crosshairState, 
786                             int pass) {
787    
788            boolean itemVisible = getItemVisible(series, item);
789            
790            // setup for collecting optional entity info...
791            Shape entityArea = null;
792            EntityCollection entities = null;
793            if (info != null) {
794                entities = info.getOwner().getEntityCollection();
795            }
796    
797            PlotOrientation orientation = plot.getOrientation();
798            Paint paint = getItemPaint(series, item);
799            Stroke seriesStroke = getItemStroke(series, item);
800            g2.setPaint(paint);
801            g2.setStroke(seriesStroke);
802    
803            // get the data point...
804            double x1 = dataset.getXValue(series, item);
805            double y1 = dataset.getYValue(series, item);
806            if (Double.isNaN(x1) || Double.isNaN(y1)) {
807                itemVisible = false;
808            }
809    
810            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
811            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
812            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
813            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
814    
815            if (getPlotLines()) {
816                if (this.drawSeriesLineAsPath) {
817                    State s = (State) state;
818                    if (s.getSeriesIndex() != series) {
819                        // we are starting a new series path
820                        s.seriesPath.reset();
821                        s.lastPointGood = false;
822                        s.setSeriesIndex(series);
823                    }
824                    
825                    // update path to reflect latest point
826                    if (itemVisible && !Double.isNaN(transX1) 
827                            && !Double.isNaN(transY1)) {
828                        float x = (float) transX1;
829                        float y = (float) transY1;
830                        if (orientation == PlotOrientation.HORIZONTAL) {
831                            x = (float) transY1;
832                            y = (float) transX1;
833                        }
834                        if (s.isLastPointGood()) {
835                            // TODO: check threshold
836                            s.seriesPath.lineTo(x, y);
837                        }
838                        else {
839                            s.seriesPath.moveTo(x, y);
840                        }
841                        s.setLastPointGood(true);
842                    }
843                    else {
844                        s.setLastPointGood(false);
845                    }
846                    if (item == dataset.getItemCount(series) - 1) {
847                        if (s.seriesIndex == series) {
848                            // draw path
849                            g2.setStroke(lookupSeriesStroke(series));
850                            g2.setPaint(lookupSeriesPaint(series));
851                            g2.draw(s.seriesPath);
852                        }
853                    }
854                }
855    
856                else if (item != 0 && itemVisible) {
857                    // get the previous data point...
858                    double x0 = dataset.getXValue(series, item - 1);
859                    double y0 = dataset.getYValue(series, item - 1);
860                    if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
861                        boolean drawLine = true;
862                        if (getPlotDiscontinuous()) {
863                            // only draw a line if the gap between the current and 
864                            // previous data point is within the threshold
865                            int numX = dataset.getItemCount(series);
866                            double minX = dataset.getXValue(series, 0);
867                            double maxX = dataset.getXValue(series, numX - 1);
868                            if (this.gapThresholdType == UnitType.ABSOLUTE) {
869                                drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
870                            }
871                            else {
872                                drawLine = Math.abs(x1 - x0) <= ((maxX - minX) 
873                                    / numX * getGapThreshold());
874                            }
875                        }
876                        if (drawLine) {
877                            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
878                                    xAxisLocation);
879                            double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
880                                    yAxisLocation);
881    
882                            // only draw if we have good values
883                            if (Double.isNaN(transX0) || Double.isNaN(transY0) 
884                                || Double.isNaN(transX1) || Double.isNaN(transY1)) {
885                                return;
886                            }
887    
888                            if (orientation == PlotOrientation.HORIZONTAL) {
889                                state.workingLine.setLine(transY0, transX0, 
890                                        transY1, transX1);
891                            }
892                            else if (orientation == PlotOrientation.VERTICAL) {
893                                state.workingLine.setLine(transX0, transY0, 
894                                        transX1, transY1);
895                            }
896    
897                            if (state.workingLine.intersects(dataArea)) {
898                                g2.draw(state.workingLine);
899                            }
900                        }
901                    }
902                }
903            }
904            
905            // we needed to get this far even for invisible items, to ensure that
906            // seriesPath updates happened, but now there is nothing more we need
907            // to do for non-visible items...
908            if (!itemVisible) {
909                return;
910            }
911    
912            if (getBaseShapesVisible()) {
913    
914                Shape shape = getItemShape(series, item);
915                if (orientation == PlotOrientation.HORIZONTAL) {
916                    shape = ShapeUtilities.createTranslatedShape(shape, transY1, 
917                            transX1);
918                }
919                else if (orientation == PlotOrientation.VERTICAL) {
920                    shape = ShapeUtilities.createTranslatedShape(shape, transX1, 
921                            transY1);
922                }
923                if (shape.intersects(dataArea)) {
924                    if (getItemShapeFilled(series, item)) {
925                        g2.fill(shape);
926                    }
927                    else {
928                        g2.draw(shape);
929                    }
930                }
931                entityArea = shape;
932    
933            }
934    
935            if (getPlotImages()) {
936                Image image = getImage(plot, series, item, transX1, transY1);
937                if (image != null) {
938                    Point hotspot = getImageHotspot(plot, series, item, transX1, 
939                            transY1, image);
940                    g2.drawImage(image, (int) (transX1 - hotspot.getX()), 
941                            (int) (transY1 - hotspot.getY()), null);
942                    entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(), 
943                            transY1 - hotspot.getY(), image.getWidth(null), 
944                            image.getHeight(null));
945                }
946    
947            }
948    
949            double xx = transX1;
950            double yy = transY1;
951            if (orientation == PlotOrientation.HORIZONTAL) {
952                xx = transY1;
953                yy = transX1;
954            }          
955    
956            // draw the item label if there is one...
957            if (isItemLabelVisible(series, item)) {
958                drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 
959                        (y1 < 0.0));
960            }
961    
962            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
963            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
964            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 
965                    rangeAxisIndex, transX1, transY1, orientation);
966    
967            // add an entity for the item...
968            if (entities != null && dataArea.contains(xx, yy)) {
969                addEntity(entities, entityArea, dataset, series, item, xx, yy);
970            }
971    
972        }
973    
974        /**
975         * Tests this renderer for equality with another object.
976         *
977         * @param obj  the object (<code>null</code> permitted).
978         *
979         * @return A boolean.
980         */
981        public boolean equals(Object obj) {
982    
983            if (obj == this) {
984                return true;
985            }
986            if (!(obj instanceof StandardXYItemRenderer)) {
987                return false;
988            }
989            StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
990            if (this.baseShapesVisible != that.baseShapesVisible) {
991                return false;
992            }
993            if (this.plotLines != that.plotLines) {
994                return false;
995            }
996            if (this.plotImages != that.plotImages) {
997                return false;
998            }
999            if (this.plotDiscontinuous != that.plotDiscontinuous) {
1000                return false;
1001            }
1002            if (this.gapThresholdType != that.gapThresholdType) {
1003                return false;
1004            }
1005            if (this.gapThreshold != that.gapThreshold) {
1006                return false;
1007            }
1008            if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1009                return false;
1010            }
1011            if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) {
1012                return false;
1013            }
1014            if (this.baseShapesFilled != that.baseShapesFilled) {
1015                return false;
1016            }
1017            if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1018                return false;
1019            }
1020            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1021                return false;   
1022            }
1023            return super.equals(obj);
1024    
1025        }
1026    
1027        /**
1028         * Returns a clone of the renderer.
1029         *
1030         * @return A clone.
1031         *
1032         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
1033         */
1034        public Object clone() throws CloneNotSupportedException {
1035            StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone();
1036            clone.seriesShapesFilled 
1037                    = (BooleanList) this.seriesShapesFilled.clone();
1038            clone.legendLine = ShapeUtilities.clone(this.legendLine);
1039            return clone;
1040        }
1041    
1042        ////////////////////////////////////////////////////////////////////////////
1043        // PROTECTED METHODS
1044        // These provide the opportunity to subclass the standard renderer and 
1045        // create custom effects.
1046        ////////////////////////////////////////////////////////////////////////////
1047    
1048        /**
1049         * Returns the image used to draw a single data item.
1050         *
1051         * @param plot  the plot (can be used to obtain standard color information 
1052         *              etc).
1053         * @param series  the series index.
1054         * @param item  the item index.
1055         * @param x  the x value of the item.
1056         * @param y  the y value of the item.
1057         *
1058         * @return The image.
1059         * 
1060         * @see #getPlotImages()
1061         */
1062        protected Image getImage(Plot plot, int series, int item, 
1063                                 double x, double y) {
1064            // this method must be overridden if you want to display images
1065            return null;
1066        }
1067    
1068        /**
1069         * Returns the hotspot of the image used to draw a single data item.
1070         * The hotspot is the point relative to the top left of the image
1071         * that should indicate the data item. The default is the center of the
1072         * image.
1073         *
1074         * @param plot  the plot (can be used to obtain standard color information 
1075         *              etc).
1076         * @param image  the image (can be used to get size information about the 
1077         *               image)
1078         * @param series  the series index
1079         * @param item  the item index
1080         * @param x  the x value of the item
1081         * @param y  the y value of the item
1082         *
1083         * @return The hotspot used to draw the data item.
1084         */
1085        protected Point getImageHotspot(Plot plot, int series, int item,
1086                                        double x, double y, Image image) {
1087    
1088            int height = image.getHeight(null);
1089            int width = image.getWidth(null);
1090            return new Point(width / 2, height / 2);
1091    
1092        }
1093        
1094        /**
1095         * Provides serialization support.
1096         *
1097         * @param stream  the input stream.
1098         *
1099         * @throws IOException  if there is an I/O error.
1100         * @throws ClassNotFoundException  if there is a classpath problem.
1101         */
1102        private void readObject(ObjectInputStream stream) 
1103                throws IOException, ClassNotFoundException {
1104            stream.defaultReadObject();
1105            this.legendLine = SerialUtilities.readShape(stream);
1106        }
1107        
1108        /**
1109         * Provides serialization support.
1110         *
1111         * @param stream  the output stream.
1112         *
1113         * @throws IOException  if there is an I/O error.
1114         */
1115        private void writeObject(ObjectOutputStream stream) throws IOException {
1116            stream.defaultWriteObject();
1117            SerialUtilities.writeShape(this.legendLine, stream);
1118        }
1119    
1120    }