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