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