001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
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    public void setPlotDiscontinuous(boolean flag) {
443        if (this.plotDiscontinuous != flag) {
444            this.plotDiscontinuous = flag;
445            fireChangeEvent();
446        }
447    }
448
449    /**
450     * Returns a flag that controls whether or not each series is drawn as a
451     * single path.
452     *
453     * @return A boolean.
454     *
455     * @see #setDrawSeriesLineAsPath(boolean)
456     */
457    public boolean getDrawSeriesLineAsPath() {
458        return this.drawSeriesLineAsPath;
459    }
460
461    /**
462     * Sets the flag that controls whether or not each series is drawn as a
463     * single path.
464     *
465     * @param flag  the flag.
466     *
467     * @see #getDrawSeriesLineAsPath()
468     */
469    public void setDrawSeriesLineAsPath(boolean flag) {
470        this.drawSeriesLineAsPath = flag;
471    }
472
473    /**
474     * Returns the shape used to represent a line in the legend.
475     *
476     * @return The legend line (never {@code null}).
477     *
478     * @see #setLegendLine(Shape)
479     */
480    public Shape getLegendLine() {
481        return this.legendLine;
482    }
483
484    /**
485     * Sets the shape used as a line in each legend item and sends a
486     * {@link RendererChangeEvent} to all registered listeners.
487     *
488     * @param line  the line ({@code null} not permitted).
489     *
490     * @see #getLegendLine()
491     */
492    public void setLegendLine(Shape line) {
493        Args.nullNotPermitted(line, "line");
494        this.legendLine = line;
495        fireChangeEvent();
496    }
497
498    /**
499     * Returns a legend item for a series.
500     *
501     * @param datasetIndex  the dataset index (zero-based).
502     * @param series  the series index (zero-based).
503     *
504     * @return A legend item for the series.
505     */
506    @Override
507    public LegendItem getLegendItem(int datasetIndex, int series) {
508        XYPlot plot = getPlot();
509        if (plot == null) {
510            return null;
511        }
512        LegendItem result = null;
513        XYDataset dataset = plot.getDataset(datasetIndex);
514        if (dataset != null) {
515            if (getItemVisible(series, 0)) {
516                String label = getLegendItemLabelGenerator().generateLabel(
517                        dataset, series);
518                String description = label;
519                String toolTipText = null;
520                if (getLegendItemToolTipGenerator() != null) {
521                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
522                            dataset, series);
523                }
524                String urlText = null;
525                if (getLegendItemURLGenerator() != null) {
526                    urlText = getLegendItemURLGenerator().generateLabel(
527                            dataset, series);
528                }
529                Shape shape = lookupLegendShape(series);
530                boolean shapeFilled = getItemShapeFilled(series, 0);
531                Paint paint = lookupSeriesPaint(series);
532                Paint linePaint = paint;
533                Stroke lineStroke = lookupSeriesStroke(series);
534                result = new LegendItem(label, description, toolTipText,
535                        urlText, this.baseShapesVisible, shape, shapeFilled,
536                        paint, !shapeFilled, paint, lineStroke,
537                        this.plotLines, this.legendLine, lineStroke, linePaint);
538                result.setLabelFont(lookupLegendTextFont(series));
539                Paint labelPaint = lookupLegendTextPaint(series);
540                if (labelPaint != null) {
541                    result.setLabelPaint(labelPaint);
542                }
543                result.setDataset(dataset);
544                result.setDatasetIndex(datasetIndex);
545                result.setSeriesKey(dataset.getSeriesKey(series));
546                result.setSeriesIndex(series);
547            }
548        }
549        return result;
550    }
551
552    /**
553     * Records the state for the renderer.  This is used to preserve state
554     * information between calls to the drawItem() method for a single chart
555     * drawing.
556     */
557    public static class State extends XYItemRendererState {
558
559        /** The path for the current series. */
560        public GeneralPath seriesPath;
561
562        /** The series index. */
563        private int seriesIndex;
564
565        /**
566         * A flag that indicates if the last (x, y) point was 'good'
567         * (non-null).
568         */
569        private boolean lastPointGood;
570
571        /**
572         * Creates a new state instance.
573         *
574         * @param info  the plot rendering info.
575         */
576        public State(PlotRenderingInfo info) {
577            super(info);
578        }
579
580        /**
581         * Returns a flag that indicates if the last point drawn (in the
582         * current series) was 'good' (non-null).
583         *
584         * @return A boolean.
585         */
586        public boolean isLastPointGood() {
587            return this.lastPointGood;
588        }
589
590        /**
591         * Sets a flag that indicates if the last point drawn (in the current
592         * series) was 'good' (non-null).
593         *
594         * @param good  the flag.
595         */
596        public void setLastPointGood(boolean good) {
597            this.lastPointGood = good;
598        }
599
600        /**
601         * Returns the series index for the current path.
602         *
603         * @return The series index for the current path.
604         */
605        public int getSeriesIndex() {
606            return this.seriesIndex;
607        }
608
609        /**
610         * Sets the series index for the current path.
611         *
612         * @param index  the index.
613         */
614        public void setSeriesIndex(int index) {
615            this.seriesIndex = index;
616        }
617    }
618
619    /**
620     * Initialises the renderer.
621     * <P>
622     * This method will be called before the first item is rendered, giving the
623     * renderer an opportunity to initialise any state information it wants to
624     * maintain. The renderer can do nothing if it chooses.
625     *
626     * @param g2  the graphics device.
627     * @param dataArea  the area inside the axes.
628     * @param plot  the plot.
629     * @param data  the data.
630     * @param info  an optional info collection object to return data back to
631     *              the caller.
632     *
633     * @return The renderer state.
634     */
635    @Override
636    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
637            XYPlot plot, XYDataset data, PlotRenderingInfo info) {
638
639        State state = new State(info);
640        state.seriesPath = new GeneralPath();
641        state.seriesIndex = -1;
642        return state;
643
644    }
645
646    /**
647     * Draws the visual representation of a single data item.
648     *
649     * @param g2  the graphics device.
650     * @param state  the renderer state.
651     * @param dataArea  the area within which the data is being drawn.
652     * @param info  collects information about the drawing.
653     * @param plot  the plot (can be used to obtain standard color information
654     *              etc).
655     * @param domainAxis  the domain axis.
656     * @param rangeAxis  the range axis.
657     * @param dataset  the dataset.
658     * @param series  the series index (zero-based).
659     * @param item  the item index (zero-based).
660     * @param crosshairState  crosshair information for the plot
661     *                        ({@code null} permitted).
662     * @param pass  the pass index.
663     */
664    @Override
665    public void drawItem(Graphics2D g2, XYItemRendererState state,
666            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
667            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
668            int series, int item, CrosshairState crosshairState, int pass) {
669
670        boolean itemVisible = getItemVisible(series, item);
671
672        // setup for collecting optional entity info...
673        Shape entityArea = null;
674        EntityCollection entities = null;
675        if (info != null) {
676            entities = info.getOwner().getEntityCollection();
677        }
678
679        PlotOrientation orientation = plot.getOrientation();
680        Paint paint = getItemPaint(series, item);
681        Stroke seriesStroke = getItemStroke(series, item);
682        g2.setPaint(paint);
683        g2.setStroke(seriesStroke);
684
685        // get the data point...
686        double x1 = dataset.getXValue(series, item);
687        double y1 = dataset.getYValue(series, item);
688        if (Double.isNaN(x1) || Double.isNaN(y1)) {
689            itemVisible = false;
690        }
691
692        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
693        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
694        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
695        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
696
697        if (getPlotLines()) {
698            if (this.drawSeriesLineAsPath) {
699                State s = (State) state;
700                if (s.getSeriesIndex() != series) {
701                    // we are starting a new series path
702                    s.seriesPath.reset();
703                    s.lastPointGood = false;
704                    s.setSeriesIndex(series);
705                }
706
707                // update path to reflect latest point
708                if (itemVisible && !Double.isNaN(transX1)
709                        && !Double.isNaN(transY1)) {
710                    float x = (float) transX1;
711                    float y = (float) transY1;
712                    if (orientation == PlotOrientation.HORIZONTAL) {
713                        x = (float) transY1;
714                        y = (float) transX1;
715                    }
716                    if (s.isLastPointGood()) {
717                        // TODO: check threshold
718                        s.seriesPath.lineTo(x, y);
719                    }
720                    else {
721                        s.seriesPath.moveTo(x, y);
722                    }
723                    s.setLastPointGood(true);
724                }
725                else {
726                    s.setLastPointGood(false);
727                }
728                if (item == dataset.getItemCount(series) - 1) {
729                    if (s.seriesIndex == series) {
730                        // draw path
731                        g2.setStroke(lookupSeriesStroke(series));
732                        g2.setPaint(lookupSeriesPaint(series));
733                        g2.draw(s.seriesPath);
734                    }
735                }
736            }
737
738            else if (item != 0 && itemVisible) {
739                // get the previous data point...
740                double x0 = dataset.getXValue(series, item - 1);
741                double y0 = dataset.getYValue(series, item - 1);
742                if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
743                    boolean drawLine = true;
744                    if (getPlotDiscontinuous()) {
745                        // only draw a line if the gap between the current and
746                        // previous data point is within the threshold
747                        int numX = dataset.getItemCount(series);
748                        double minX = dataset.getXValue(series, 0);
749                        double maxX = dataset.getXValue(series, numX - 1);
750                        if (this.gapThresholdType == UnitType.ABSOLUTE) {
751                            drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
752                        }
753                        else {
754                            drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
755                                / numX * getGapThreshold());
756                        }
757                    }
758                    if (drawLine) {
759                        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
760                                xAxisLocation);
761                        double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
762                                yAxisLocation);
763
764                        // only draw if we have good values
765                        if (Double.isNaN(transX0) || Double.isNaN(transY0)
766                            || Double.isNaN(transX1) || Double.isNaN(transY1)) {
767                            return;
768                        }
769
770                        if (orientation == PlotOrientation.HORIZONTAL) {
771                            state.workingLine.setLine(transY0, transX0,
772                                    transY1, transX1);
773                        }
774                        else if (orientation == PlotOrientation.VERTICAL) {
775                            state.workingLine.setLine(transX0, transY0,
776                                    transX1, transY1);
777                        }
778
779                        if (state.workingLine.intersects(dataArea)) {
780                            g2.draw(state.workingLine);
781                        }
782                    }
783                }
784            }
785        }
786
787        // we needed to get this far even for invisible items, to ensure that
788        // seriesPath updates happened, but now there is nothing more we need
789        // to do for non-visible items...
790        if (!itemVisible) {
791            return;
792        }
793
794        if (getBaseShapesVisible()) {
795
796            Shape shape = getItemShape(series, item);
797            if (orientation == PlotOrientation.HORIZONTAL) {
798                shape = ShapeUtils.createTranslatedShape(shape, transY1,
799                        transX1);
800            }
801            else if (orientation == PlotOrientation.VERTICAL) {
802                shape = ShapeUtils.createTranslatedShape(shape, transX1,
803                        transY1);
804            }
805            if (shape.intersects(dataArea)) {
806                if (getItemShapeFilled(series, item)) {
807                    g2.fill(shape);
808                }
809                else {
810                    g2.draw(shape);
811                }
812            }
813            entityArea = shape;
814
815        }
816
817        if (getPlotImages()) {
818            Image image = getImage(plot, series, item, transX1, transY1);
819            if (image != null) {
820                Point hotspot = getImageHotspot(plot, series, item, transX1,
821                        transY1, image);
822                g2.drawImage(image, (int) (transX1 - hotspot.getX()),
823                        (int) (transY1 - hotspot.getY()), null);
824                entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(),
825                        transY1 - hotspot.getY(), image.getWidth(null),
826                        image.getHeight(null));
827            }
828
829        }
830
831        double xx = transX1;
832        double yy = transY1;
833        if (orientation == PlotOrientation.HORIZONTAL) {
834            xx = transY1;
835            yy = transX1;
836        }
837
838        // draw the item label if there is one...
839        if (isItemLabelVisible(series, item)) {
840            drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
841                    (y1 < 0.0));
842        }
843
844        int datasetIndex = plot.indexOf(dataset);
845        updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
846                transX1, transY1, orientation);
847
848        // add an entity for the item...
849        if (entities != null && ShapeUtils.isPointInRect(dataArea, xx, yy)) {
850            addEntity(entities, entityArea, dataset, series, item, xx, yy);
851        }
852
853    }
854
855    /**
856     * Tests this renderer for equality with another object.
857     *
858     * @param obj  the object ({@code null} permitted).
859     *
860     * @return A boolean.
861     */
862    @Override
863    public boolean equals(Object obj) {
864
865        if (obj == this) {
866            return true;
867        }
868        if (!(obj instanceof StandardXYItemRenderer)) {
869            return false;
870        }
871        StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
872        if (this.baseShapesVisible != that.baseShapesVisible) {
873            return false;
874        }
875        if (this.plotLines != that.plotLines) {
876            return false;
877        }
878        if (this.plotImages != that.plotImages) {
879            return false;
880        }
881        if (this.plotDiscontinuous != that.plotDiscontinuous) {
882            return false;
883        }
884        if (this.gapThresholdType != that.gapThresholdType) {
885            return false;
886        }
887        if (this.gapThreshold != that.gapThreshold) {
888            return false;
889        }
890        if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) {
891            return false;
892        }
893        if (this.baseShapesFilled != that.baseShapesFilled) {
894            return false;
895        }
896        if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
897            return false;
898        }
899        if (!ShapeUtils.equal(this.legendLine, that.legendLine)) {
900            return false;
901        }
902        return super.equals(obj);
903
904    }
905
906    /**
907     * Returns a clone of the renderer.
908     *
909     * @return A clone.
910     *
911     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
912     */
913    @Override
914    public Object clone() throws CloneNotSupportedException {
915        StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone();
916        clone.seriesShapesFilled
917                = (BooleanList) this.seriesShapesFilled.clone();
918        clone.legendLine = ShapeUtils.clone(this.legendLine);
919        return clone;
920    }
921
922    ////////////////////////////////////////////////////////////////////////////
923    // PROTECTED METHODS
924    // These provide the opportunity to subclass the standard renderer and
925    // create custom effects.
926    ////////////////////////////////////////////////////////////////////////////
927
928    /**
929     * Returns the image used to draw a single data item.
930     *
931     * @param plot  the plot (can be used to obtain standard color information
932     *              etc).
933     * @param series  the series index.
934     * @param item  the item index.
935     * @param x  the x value of the item.
936     * @param y  the y value of the item.
937     *
938     * @return The image.
939     *
940     * @see #getPlotImages()
941     */
942    protected Image getImage(Plot plot, int series, int item,
943                             double x, double y) {
944        // this method must be overridden if you want to display images
945        return null;
946    }
947
948    /**
949     * Returns the hotspot of the image used to draw a single data item.
950     * The hotspot is the point relative to the top left of the image
951     * that should indicate the data item. The default is the center of the
952     * image.
953     *
954     * @param plot  the plot (can be used to obtain standard color information
955     *              etc).
956     * @param image  the image (can be used to get size information about the
957     *               image)
958     * @param series  the series index
959     * @param item  the item index
960     * @param x  the x value of the item
961     * @param y  the y value of the item
962     *
963     * @return The hotspot used to draw the data item.
964     */
965    protected Point getImageHotspot(Plot plot, int series, int item,
966                                    double x, double y, Image image) {
967
968        int height = image.getHeight(null);
969        int width = image.getWidth(null);
970        return new Point(width / 2, height / 2);
971
972    }
973
974    /**
975     * Provides serialization support.
976     *
977     * @param stream  the input stream.
978     *
979     * @throws IOException  if there is an I/O error.
980     * @throws ClassNotFoundException  if there is a classpath problem.
981     */
982    private void readObject(ObjectInputStream stream)
983            throws IOException, ClassNotFoundException {
984        stream.defaultReadObject();
985        this.legendLine = SerialUtils.readShape(stream);
986    }
987
988    /**
989     * Provides serialization support.
990     *
991     * @param stream  the output stream.
992     *
993     * @throws IOException  if there is an I/O error.
994     */
995    private void writeObject(ObjectOutputStream stream) throws IOException {
996        stream.defaultWriteObject();
997        SerialUtils.writeShape(this.legendLine, stream);
998    }
999
1000}