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