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