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