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