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