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