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     * XYLineAndShapeRenderer.java
029     * ---------------------------
030     * (C) Copyright 2004-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: XYLineAndShapeRenderer.java,v 1.20.2.9 2007/02/21 11:49:46 mungady Exp $
036     *
037     * Changes:
038     * --------
039     * 27-Jan-2004 : Version 1 (DG);
040     * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste 
041     *               overriding easier (DG);
042     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
043     * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG);
044     * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path 
045     *               (necessary when using a dashed stroke with many data 
046     *               items) (DG);
047     * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG);
048     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
049     * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG);
050     * 28-Jan-2005 : Added new constructor (DG);
051     * 09-Mar-2005 : Added fillPaint settings (DG);
052     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
053     * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible, 
054     *               defaultShapesVisible --> baseShapesVisible and
055     *               defaultShapesFilled --> baseShapesFilled (DG);
056     * 29-Jul-2005 : Added code to draw item labels (DG);
057     * ------------- JFREECHART 1.0.x ---------------------------------------------
058     * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
059     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
060     * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG);
061     *
062     */
063    
064    package org.jfree.chart.renderer.xy;
065    
066    import java.awt.Graphics2D;
067    import java.awt.Paint;
068    import java.awt.Shape;
069    import java.awt.Stroke;
070    import java.awt.geom.GeneralPath;
071    import java.awt.geom.Line2D;
072    import java.awt.geom.Rectangle2D;
073    import java.io.IOException;
074    import java.io.ObjectInputStream;
075    import java.io.ObjectOutputStream;
076    import java.io.Serializable;
077    
078    import org.jfree.chart.LegendItem;
079    import org.jfree.chart.axis.ValueAxis;
080    import org.jfree.chart.entity.EntityCollection;
081    import org.jfree.chart.event.RendererChangeEvent;
082    import org.jfree.chart.plot.CrosshairState;
083    import org.jfree.chart.plot.PlotOrientation;
084    import org.jfree.chart.plot.PlotRenderingInfo;
085    import org.jfree.chart.plot.XYPlot;
086    import org.jfree.data.xy.XYDataset;
087    import org.jfree.io.SerialUtilities;
088    import org.jfree.ui.RectangleEdge;
089    import org.jfree.util.BooleanList;
090    import org.jfree.util.BooleanUtilities;
091    import org.jfree.util.ObjectUtilities;
092    import org.jfree.util.PublicCloneable;
093    import org.jfree.util.ShapeUtilities;
094    
095    /**
096     * A renderer that can be used with the {@link XYPlot} class.
097     */
098    public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 
099                                        implements XYItemRenderer, 
100                                                   Cloneable,
101                                                   PublicCloneable,
102                                                   Serializable {
103    
104        /** For serialization. */
105        private static final long serialVersionUID = -7435246895986425885L;
106        
107        /** A flag that controls whether or not lines are visible for ALL series. */
108        private Boolean linesVisible;
109    
110        /** 
111         * A table of flags that control (per series) whether or not lines are 
112         * visible. 
113         */
114        private BooleanList seriesLinesVisible;
115    
116        /** The default value returned by the getLinesVisible() method. */
117        private boolean baseLinesVisible;
118    
119        /** The shape that is used to represent a line in the legend. */
120        private transient Shape legendLine;
121        
122        /** 
123         * A flag that controls whether or not shapes are visible for ALL series. 
124         */
125        private Boolean shapesVisible;
126    
127        /** 
128         * A table of flags that control (per series) whether or not shapes are 
129         * visible. 
130         */
131        private BooleanList seriesShapesVisible;
132    
133        /** The default value returned by the getShapeVisible() method. */
134        private boolean baseShapesVisible;
135    
136        /** A flag that controls whether or not shapes are filled for ALL series. */
137        private Boolean shapesFilled;
138    
139        /** 
140         * A table of flags that control (per series) whether or not shapes are 
141         * filled. 
142         */
143        private BooleanList seriesShapesFilled;
144    
145        /** The default value returned by the getShapeFilled() method. */
146        private boolean baseShapesFilled;
147        
148        /** A flag that controls whether outlines are drawn for shapes. */
149        private boolean drawOutlines;
150        
151        /** 
152         * A flag that controls whether the fill paint is used for filling 
153         * shapes. 
154         */
155        private boolean useFillPaint;
156        
157        /** 
158         * A flag that controls whether the outline paint is used for drawing shape 
159         * outlines. 
160         */
161        private boolean useOutlinePaint;
162        
163        /** 
164         * A flag that controls whether or not each series is drawn as a single 
165         * path. 
166         */
167        private boolean drawSeriesLineAsPath;
168    
169        /**
170         * Creates a new renderer with both lines and shapes visible.
171         */
172        public XYLineAndShapeRenderer() {
173            this(true, true);
174        }
175        
176        /**
177         * Creates a new renderer.
178         * 
179         * @param lines  lines visible?
180         * @param shapes  shapes visible?
181         */
182        public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
183            this.linesVisible = null;
184            this.seriesLinesVisible = new BooleanList();
185            this.baseLinesVisible = lines;
186            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
187            
188            this.shapesVisible = null;
189            this.seriesShapesVisible = new BooleanList();
190            this.baseShapesVisible = shapes;
191            
192            this.shapesFilled = null;
193            this.useFillPaint = false;     // use item paint for fills by default
194            this.seriesShapesFilled = new BooleanList();
195            this.baseShapesFilled = true;
196    
197            this.drawOutlines = true;     
198            this.useOutlinePaint = false;  // use item paint for outlines by 
199                                           // default, not outline paint
200            
201            this.drawSeriesLineAsPath = false;
202        }
203        
204        /**
205         * Returns a flag that controls whether or not each series is drawn as a 
206         * single path.
207         * 
208         * @return A boolean.
209         * 
210         * @see #setDrawSeriesLineAsPath(boolean)
211         */
212        public boolean getDrawSeriesLineAsPath() {
213            return this.drawSeriesLineAsPath;
214        }
215        
216        /**
217         * Sets the flag that controls whether or not each series is drawn as a 
218         * single path.
219         * 
220         * @param flag  the flag.
221         * 
222         * @see #getDrawSeriesLineAsPath()
223         */
224        public void setDrawSeriesLineAsPath(boolean flag) {
225            if (this.drawSeriesLineAsPath != flag) {
226                this.drawSeriesLineAsPath = flag;
227                notifyListeners(new RendererChangeEvent(this));
228            }
229        }
230        
231        /**
232         * Returns the number of passes through the data that the renderer requires 
233         * in order to draw the chart.  Most charts will require a single pass, but 
234         * some require two passes.
235         * 
236         * @return The pass count.
237         */
238        public int getPassCount() {
239            return 2;
240        }
241        
242        // LINES VISIBLE
243    
244        /**
245         * Returns the flag used to control whether or not the shape for an item is 
246         * visible.
247         *
248         * @param series  the series index (zero-based).
249         * @param item  the item index (zero-based).
250         *
251         * @return A boolean.
252         */
253        public boolean getItemLineVisible(int series, int item) {
254            Boolean flag = this.linesVisible;
255            if (flag == null) {
256                flag = getSeriesLinesVisible(series);
257            }
258            if (flag != null) {
259                return flag.booleanValue();
260            }
261            else {
262                return this.baseLinesVisible;   
263            }
264        }
265    
266        /**
267         * Returns a flag that controls whether or not lines are drawn for ALL 
268         * series.  If this flag is <code>null</code>, then the "per series" 
269         * settings will apply.
270         * 
271         * @return A flag (possibly <code>null</code>).
272         * 
273         * @see #setLinesVisible(Boolean) 
274         */
275        public Boolean getLinesVisible() {
276            return this.linesVisible;   
277        }
278        
279        /**
280         * Sets a flag that controls whether or not lines are drawn between the 
281         * items in ALL series, and sends a {@link RendererChangeEvent} to all 
282         * registered listeners.  You need to set this to <code>null</code> if you 
283         * want the "per series" settings to apply.
284         *
285         * @param visible  the flag (<code>null</code> permitted).
286         * 
287         * @see #getLinesVisible()
288         */
289        public void setLinesVisible(Boolean visible) {
290            this.linesVisible = visible;
291            notifyListeners(new RendererChangeEvent(this));
292        }
293    
294        /**
295         * Sets a flag that controls whether or not lines are drawn between the 
296         * items in ALL series, and sends a {@link RendererChangeEvent} to all 
297         * registered listeners.
298         *
299         * @param visible  the flag.
300         * 
301         * @see #getLinesVisible()
302         */
303        public void setLinesVisible(boolean visible) {
304            // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility
305            setLinesVisible(BooleanUtilities.valueOf(visible));
306        }
307    
308        /**
309         * Returns the flag used to control whether or not the lines for a series 
310         * are visible.
311         *
312         * @param series  the series index (zero-based).
313         *
314         * @return The flag (possibly <code>null</code>).
315         * 
316         * @see #setSeriesLinesVisible(int, Boolean)
317         */
318        public Boolean getSeriesLinesVisible(int series) {
319            return this.seriesLinesVisible.getBoolean(series);
320        }
321    
322        /**
323         * Sets the 'lines visible' flag for a series and sends a 
324         * {@link RendererChangeEvent} to all registered listeners.
325         *
326         * @param series  the series index (zero-based).
327         * @param flag  the flag (<code>null</code> permitted).
328         * 
329         * @see #getSeriesLinesVisible(int)
330         */
331        public void setSeriesLinesVisible(int series, Boolean flag) {
332            this.seriesLinesVisible.setBoolean(series, flag);
333            notifyListeners(new RendererChangeEvent(this));
334        }
335    
336        /**
337         * Sets the 'lines visible' flag for a series and sends a 
338         * {@link RendererChangeEvent} to all registered listeners.
339         * 
340         * @param series  the series index (zero-based).
341         * @param visible  the flag.
342         * 
343         * @see #getSeriesLinesVisible(int)
344         */
345        public void setSeriesLinesVisible(int series, boolean visible) {
346            setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
347        }
348        
349        /**
350         * Returns the base 'lines visible' attribute.
351         *
352         * @return The base flag.
353         * 
354         * @see #setBaseLinesVisible(boolean)
355         */
356        public boolean getBaseLinesVisible() {
357            return this.baseLinesVisible;
358        }
359    
360        /**
361         * Sets the base 'lines visible' flag and sends a 
362         * {@link RendererChangeEvent} to all registered listeners.
363         *
364         * @param flag  the flag.
365         * 
366         * @see #getBaseLinesVisible()
367         */
368        public void setBaseLinesVisible(boolean flag) {
369            this.baseLinesVisible = flag;
370            notifyListeners(new RendererChangeEvent(this));
371        }
372    
373        /**
374         * Returns the shape used to represent a line in the legend.
375         * 
376         * @return The legend line (never <code>null</code>).
377         * 
378         * @see #setLegendLine(Shape)
379         */
380        public Shape getLegendLine() {
381            return this.legendLine;   
382        }
383        
384        /**
385         * Sets the shape used as a line in each legend item and sends a 
386         * {@link RendererChangeEvent} to all registered listeners.
387         * 
388         * @param line  the line (<code>null</code> not permitted).
389         * 
390         * @see #getLegendLine()
391         */
392        public void setLegendLine(Shape line) {
393            if (line == null) {
394                throw new IllegalArgumentException("Null 'line' argument.");   
395            }
396            this.legendLine = line;
397            notifyListeners(new RendererChangeEvent(this));
398        }
399    
400        // SHAPES VISIBLE
401    
402        /**
403         * Returns the flag used to control whether or not the shape for an item is
404         * visible.
405         * <p>
406         * The default implementation passes control to the 
407         * <code>getSeriesShapesVisible</code> method. You can override this method
408         * if you require different behaviour.
409         *
410         * @param series  the series index (zero-based).
411         * @param item  the item index (zero-based).
412         *
413         * @return A boolean.
414         */
415        public boolean getItemShapeVisible(int series, int item) {
416            Boolean flag = this.shapesVisible;
417            if (flag == null) {
418                flag = getSeriesShapesVisible(series);
419            }
420            if (flag != null) {
421                return flag.booleanValue();   
422            }
423            else {
424                return this.baseShapesVisible;
425            }
426        }
427    
428        /**
429         * Returns the flag that controls whether the shapes are visible for the 
430         * items in ALL series.
431         * 
432         * @return The flag (possibly <code>null</code>).
433         * 
434         * @see #setShapesVisible(Boolean)
435         */
436        public Boolean getShapesVisible() {
437            return this.shapesVisible;    
438        }
439        
440        /**
441         * Sets the 'shapes visible' for ALL series and sends a 
442         * {@link RendererChangeEvent} to all registered listeners.
443         *
444         * @param visible  the flag (<code>null</code> permitted).
445         * 
446         * @see #getShapesVisible()
447         */
448        public void setShapesVisible(Boolean visible) {
449            this.shapesVisible = visible;
450            notifyListeners(new RendererChangeEvent(this));
451        }
452    
453        /**
454         * Sets the 'shapes visible' for ALL series and sends a 
455         * {@link RendererChangeEvent} to all registered listeners.
456         * 
457         * @param visible  the flag.
458         * 
459         * @see #getShapesVisible()
460         */
461        public void setShapesVisible(boolean visible) {
462            setShapesVisible(BooleanUtilities.valueOf(visible));
463        }
464    
465        /**
466         * Returns the flag used to control whether or not the shapes for a series
467         * are visible.
468         *
469         * @param series  the series index (zero-based).
470         *
471         * @return A boolean.
472         * 
473         * @see #setSeriesShapesVisible(int, Boolean)
474         */
475        public Boolean getSeriesShapesVisible(int series) {
476            return this.seriesShapesVisible.getBoolean(series);
477        }
478    
479        /**
480         * Sets the 'shapes visible' flag for a series and sends a 
481         * {@link RendererChangeEvent} to all registered listeners.
482         * 
483         * @param series  the series index (zero-based).
484         * @param visible  the flag.
485         * 
486         * @see #getSeriesShapesVisible(int)
487         */
488        public void setSeriesShapesVisible(int series, boolean visible) {
489            setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
490        }
491        
492        /**
493         * Sets the 'shapes visible' flag for a series and sends a 
494         * {@link RendererChangeEvent} to all registered listeners.
495         *
496         * @param series  the series index (zero-based).
497         * @param flag  the flag.
498         * 
499         * @see #getSeriesShapesVisible(int)
500         */
501        public void setSeriesShapesVisible(int series, Boolean flag) {
502            this.seriesShapesVisible.setBoolean(series, flag);
503            notifyListeners(new RendererChangeEvent(this));
504        }
505    
506        /**
507         * Returns the base 'shape visible' attribute.
508         *
509         * @return The base flag.
510         * 
511         * @see #setBaseShapesVisible(boolean)
512         */
513        public boolean getBaseShapesVisible() {
514            return this.baseShapesVisible;
515        }
516    
517        /**
518         * Sets the base 'shapes visible' flag and sends a 
519         * {@link RendererChangeEvent} to all registered listeners.
520         *
521         * @param flag  the flag.
522         * 
523         * @see #getBaseShapesVisible()
524         */
525        public void setBaseShapesVisible(boolean flag) {
526            this.baseShapesVisible = flag;
527            notifyListeners(new RendererChangeEvent(this));
528        }
529    
530        // SHAPES FILLED
531    
532        /**
533         * Returns the flag used to control whether or not the shape for an item 
534         * is filled.
535         * <p>
536         * The default implementation passes control to the 
537         * <code>getSeriesShapesFilled</code> method. You can override this method
538         * if you require different behaviour.
539         *
540         * @param series  the series index (zero-based).
541         * @param item  the item index (zero-based).
542         *
543         * @return A boolean.
544         */
545        public boolean getItemShapeFilled(int series, int item) {
546            Boolean flag = this.shapesFilled;
547            if (flag == null) {
548                flag = getSeriesShapesFilled(series);
549            }
550            if (flag != null) {
551                return flag.booleanValue();   
552            }
553            else {
554                return this.baseShapesFilled;   
555            }
556        }
557    
558        // FIXME: Why no getShapesFilled()?  An oversight probably
559        
560        /**
561         * Sets the 'shapes filled' for ALL series and sends a 
562         * {@link RendererChangeEvent} to all registered listeners.
563         *
564         * @param filled  the flag.
565         */
566        public void setShapesFilled(boolean filled) {
567            setShapesFilled(BooleanUtilities.valueOf(filled));
568        }
569    
570        /**
571         * Sets the 'shapes filled' for ALL series and sends a 
572         * {@link RendererChangeEvent} to all registered listeners.
573         *
574         * @param filled  the flag (<code>null</code> permitted).
575         */
576        public void setShapesFilled(Boolean filled) {
577            this.shapesFilled = filled;
578            notifyListeners(new RendererChangeEvent(this));
579        }
580        
581        /**
582         * Returns the flag used to control whether or not the shapes for a series
583         * are filled.
584         *
585         * @param series  the series index (zero-based).
586         *
587         * @return A boolean.
588         * 
589         * @see #setSeriesShapesFilled(int, Boolean)
590         */
591        public Boolean getSeriesShapesFilled(int series) {
592            return this.seriesShapesFilled.getBoolean(series);
593        }
594    
595        /**
596         * Sets the 'shapes filled' flag for a series and sends a 
597         * {@link RendererChangeEvent} to all registered listeners.
598         *
599         * @param series  the series index (zero-based).
600         * @param flag  the flag.
601         * 
602         * @see #getSeriesShapesFilled(int)
603         */
604        public void setSeriesShapesFilled(int series, boolean flag) {
605            setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag));
606        }
607    
608        /**
609         * Sets the 'shapes filled' flag for a series and sends a 
610         * {@link RendererChangeEvent} to all registered listeners.
611         *
612         * @param series  the series index (zero-based).
613         * @param flag  the flag.
614         * 
615         * @see #getSeriesShapesFilled(int)
616         */
617        public void setSeriesShapesFilled(int series, Boolean flag) {
618            this.seriesShapesFilled.setBoolean(series, flag);
619            notifyListeners(new RendererChangeEvent(this));
620        }
621    
622        /**
623         * Returns the base 'shape filled' attribute.
624         *
625         * @return The base flag.
626         * 
627         * @see #setBaseShapesFilled(boolean)
628         */
629        public boolean getBaseShapesFilled() {
630            return this.baseShapesFilled;
631        }
632    
633        /**
634         * Sets the base 'shapes filled' flag and sends a 
635         * {@link RendererChangeEvent} to all registered listeners.
636         *
637         * @param flag  the flag.
638         * 
639         * @see #getBaseShapesFilled()
640         */
641        public void setBaseShapesFilled(boolean flag) {
642            this.baseShapesFilled = flag;
643            notifyListeners(new RendererChangeEvent(this));
644        }
645    
646        /**
647         * Returns <code>true</code> if outlines should be drawn for shapes, and 
648         * <code>false</code> otherwise.
649         * 
650         * @return A boolean.
651         * 
652         * @see #setDrawOutlines(boolean)
653         */
654        public boolean getDrawOutlines() {
655            return this.drawOutlines;
656        }
657        
658        /**
659         * Sets the flag that controls whether outlines are drawn for 
660         * shapes, and sends a {@link RendererChangeEvent} to all registered 
661         * listeners. 
662         * <P>
663         * In some cases, shapes look better if they do NOT have an outline, but 
664         * this flag allows you to set your own preference.
665         * 
666         * @param flag  the flag.
667         * 
668         * @see #getDrawOutlines()
669         */
670        public void setDrawOutlines(boolean flag) {
671            this.drawOutlines = flag;
672            notifyListeners(new RendererChangeEvent(this));
673        }
674        
675        /**
676         * Returns <code>true</code> if the renderer should use the fill paint 
677         * setting to fill shapes, and <code>false</code> if it should just
678         * use the regular paint.
679         * 
680         * @return A boolean.
681         * 
682         * @see #setUseFillPaint(boolean)
683         * @see #getUseOutlinePaint()
684         */
685        public boolean getUseFillPaint() {
686            return this.useFillPaint;
687        }
688        
689        /**
690         * Sets the flag that controls whether the fill paint is used to fill 
691         * shapes, and sends a {@link RendererChangeEvent} to all 
692         * registered listeners.
693         * 
694         * @param flag  the flag.
695         * 
696         * @see #getUseFillPaint()
697         */
698        public void setUseFillPaint(boolean flag) {
699            this.useFillPaint = flag;
700            notifyListeners(new RendererChangeEvent(this));
701        }
702        
703        /**
704         * Returns <code>true</code> if the renderer should use the outline paint 
705         * setting to draw shape outlines, and <code>false</code> if it should just
706         * use the regular paint.
707         * 
708         * @return A boolean.
709         * 
710         * @see #setUseOutlinePaint(boolean)
711         * @see #getUseFillPaint()
712         */
713        public boolean getUseOutlinePaint() {
714            return this.useOutlinePaint;
715        }
716        
717        /**
718         * Sets the flag that controls whether the outline paint is used to draw 
719         * shape outlines, and sends a {@link RendererChangeEvent} to all 
720         * registered listeners.
721         * 
722         * @param flag  the flag.
723         * 
724         * @see #getUseOutlinePaint()
725         */
726        public void setUseOutlinePaint(boolean flag) {
727            this.useOutlinePaint = flag;
728            notifyListeners(new RendererChangeEvent(this));
729        }
730        
731        /**
732         * Records the state for the renderer.  This is used to preserve state 
733         * information between calls to the drawItem() method for a single chart 
734         * drawing.
735         */
736        public static class State extends XYItemRendererState {
737            
738            /** The path for the current series. */
739            public GeneralPath seriesPath;
740            
741            /** 
742             * A flag that indicates if the last (x, y) point was 'good' 
743             * (non-null). 
744             */
745            private boolean lastPointGood;
746            
747            /**
748             * Creates a new state instance.
749             * 
750             * @param info  the plot rendering info.
751             */
752            public State(PlotRenderingInfo info) {
753                super(info);
754            }
755            
756            /**
757             * Returns a flag that indicates if the last point drawn (in the 
758             * current series) was 'good' (non-null).
759             * 
760             * @return A boolean.
761             */
762            public boolean isLastPointGood() {
763                return this.lastPointGood;
764            }
765            
766            /**
767             * Sets a flag that indicates if the last point drawn (in the current 
768             * series) was 'good' (non-null).
769             * 
770             * @param good  the flag.
771             */
772            public void setLastPointGood(boolean good) {
773                this.lastPointGood = good;
774            }
775        }
776        
777        /**
778         * Initialises the renderer.
779         * <P>
780         * This method will be called before the first item is rendered, giving the
781         * renderer an opportunity to initialise any state information it wants to 
782         * maintain.  The renderer can do nothing if it chooses.
783         *
784         * @param g2  the graphics device.
785         * @param dataArea  the area inside the axes.
786         * @param plot  the plot.
787         * @param data  the data.
788         * @param info  an optional info collection object to return data back to 
789         *              the caller.
790         *
791         * @return The renderer state.
792         */
793        public XYItemRendererState initialise(Graphics2D g2,
794                                              Rectangle2D dataArea,
795                                              XYPlot plot,
796                                              XYDataset data,
797                                              PlotRenderingInfo info) {
798    
799            State state = new State(info);
800            state.seriesPath = new GeneralPath();
801            return state;
802    
803        }
804        
805        /**
806         * Draws the visual representation of a single data item.
807         *
808         * @param g2  the graphics device.
809         * @param state  the renderer state.
810         * @param dataArea  the area within which the data is being drawn.
811         * @param info  collects information about the drawing.
812         * @param plot  the plot (can be used to obtain standard color 
813         *              information etc).
814         * @param domainAxis  the domain axis.
815         * @param rangeAxis  the range axis.
816         * @param dataset  the dataset.
817         * @param series  the series index (zero-based).
818         * @param item  the item index (zero-based).
819         * @param crosshairState  crosshair information for the plot 
820         *                        (<code>null</code> permitted).
821         * @param pass  the pass index.
822         */
823        public void drawItem(Graphics2D g2,
824                             XYItemRendererState state,
825                             Rectangle2D dataArea,
826                             PlotRenderingInfo info,
827                             XYPlot plot,
828                             ValueAxis domainAxis,
829                             ValueAxis rangeAxis,
830                             XYDataset dataset,
831                             int series,
832                             int item,
833                             CrosshairState crosshairState,
834                             int pass) {
835    
836            // do nothing if item is not visible
837            if (!getItemVisible(series, item)) {
838                return;   
839            }
840    
841            // first pass draws the background (lines, for instance)
842            if (isLinePass(pass)) {
843                if (item == 0) {
844                    if (this.drawSeriesLineAsPath) {
845                        State s = (State) state;
846                        s.seriesPath.reset();
847                        s.lastPointGood = false;     
848                    }
849                }
850    
851                if (getItemLineVisible(series, item)) {
852                    if (this.drawSeriesLineAsPath) {
853                        drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 
854                                series, item, domainAxis, rangeAxis, dataArea);
855                    }
856                    else {
857                        drawPrimaryLine(state, g2, plot, dataset, pass, series, 
858                                item, domainAxis, rangeAxis, dataArea);
859                    }
860                }
861            }
862            // second pass adds shapes where the items are ..
863            else if (isItemPass(pass)) {
864    
865                // setup for collecting optional entity info...
866                EntityCollection entities = null;
867                if (info != null) {
868                    entities = info.getOwner().getEntityCollection();
869                }
870    
871                drawSecondaryPass(g2, plot, dataset, pass, series, item, 
872                        domainAxis, dataArea, rangeAxis, crosshairState, entities);
873            }
874        }
875    
876        /**
877         * Returns <code>true</code> if the specified pass is the one for drawing 
878         * lines.
879         * 
880         * @param pass  the pass.
881         * 
882         * @return A boolean.
883         */
884        protected boolean isLinePass(int pass) {
885            return pass == 0;
886        }
887    
888        /**
889         * Returns <code>true</code> if the specified pass is the one for drawing 
890         * items.
891         * 
892         * @param pass  the pass.
893         * 
894         * @return A boolean.
895         */
896        protected boolean isItemPass(int pass) {
897            return pass == 1;
898        }
899    
900        /**
901         * Draws the item (first pass). This method draws the lines
902         * connecting the items.
903         *
904         * @param g2  the graphics device.
905         * @param state  the renderer state.
906         * @param dataArea  the area within which the data is being drawn.
907         * @param plot  the plot (can be used to obtain standard color 
908         *              information etc).
909         * @param domainAxis  the domain axis.
910         * @param rangeAxis  the range axis.
911         * @param dataset  the dataset.
912         * @param pass  the pass.
913         * @param series  the series index (zero-based).
914         * @param item  the item index (zero-based).
915         */
916        protected void drawPrimaryLine(XYItemRendererState state,
917                                       Graphics2D g2,
918                                       XYPlot plot,
919                                       XYDataset dataset,
920                                       int pass,
921                                       int series,
922                                       int item,
923                                       ValueAxis domainAxis,
924                                       ValueAxis rangeAxis,
925                                       Rectangle2D dataArea) {
926            if (item == 0) {
927                return;
928            }
929    
930            // get the data point...
931            double x1 = dataset.getXValue(series, item);
932            double y1 = dataset.getYValue(series, item);
933            if (Double.isNaN(y1) || Double.isNaN(x1)) {
934                return;
935            }
936    
937            double x0 = dataset.getXValue(series, item - 1);
938            double y0 = dataset.getYValue(series, item - 1);
939            if (Double.isNaN(y0) || Double.isNaN(x0)) {
940                return;
941            }
942    
943            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
944            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
945    
946            double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
947            double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
948    
949            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
950            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
951    
952            // only draw if we have good values
953            if (Double.isNaN(transX0) || Double.isNaN(transY0)
954                || Double.isNaN(transX1) || Double.isNaN(transY1)) {
955                return;
956            }
957    
958            PlotOrientation orientation = plot.getOrientation();
959            if (orientation == PlotOrientation.HORIZONTAL) {
960                state.workingLine.setLine(transY0, transX0, transY1, transX1);
961            }
962            else if (orientation == PlotOrientation.VERTICAL) {
963                state.workingLine.setLine(transX0, transY0, transX1, transY1);
964            }
965    
966            if (state.workingLine.intersects(dataArea)) {
967                drawFirstPassShape(g2, pass, series, item, state.workingLine);
968            }
969        }
970    
971        /**
972         * Draws the first pass shape.
973         * 
974         * @param g2  the graphics device.
975         * @param pass  the pass.
976         * @param series  the series index.
977         * @param item  the item index.
978         * @param shape  the shape.
979         */
980        protected void drawFirstPassShape(Graphics2D g2, int pass, int series,
981                                          int item, Shape shape) {
982            g2.setStroke(getItemStroke(series, item));
983            g2.setPaint(getItemPaint(series, item));
984            g2.draw(shape);
985        }
986    
987    
988        /**
989         * Draws the item (first pass). This method draws the lines
990         * connecting the items. Instead of drawing separate lines,
991         * a GeneralPath is constructed and drawn at the end of
992         * the series painting.
993         *
994         * @param g2  the graphics device.
995         * @param state  the renderer state.
996         * @param plot  the plot (can be used to obtain standard color information 
997         *              etc).
998         * @param dataset  the dataset.
999         * @param pass  the pass.
1000         * @param series  the series index (zero-based).
1001         * @param item  the item index (zero-based).
1002         * @param domainAxis  the domain axis.
1003         * @param rangeAxis  the range axis.
1004         * @param dataArea  the area within which the data is being drawn.
1005         */
1006        protected void drawPrimaryLineAsPath(XYItemRendererState state,
1007                                             Graphics2D g2, XYPlot plot,
1008                                             XYDataset dataset,
1009                                             int pass,
1010                                             int series,
1011                                             int item,
1012                                             ValueAxis domainAxis,
1013                                             ValueAxis rangeAxis,
1014                                             Rectangle2D dataArea) {
1015    
1016    
1017            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1018            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1019    
1020            // get the data point...
1021            double x1 = dataset.getXValue(series, item);
1022            double y1 = dataset.getYValue(series, item);
1023            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1024            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1025    
1026            State s = (State) state;
1027            // update path to reflect latest point
1028            if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
1029                float x = (float) transX1;
1030                float y = (float) transY1;
1031                PlotOrientation orientation = plot.getOrientation();
1032                if (orientation == PlotOrientation.HORIZONTAL) {
1033                    x = (float) transY1;
1034                    y = (float) transX1;
1035                }
1036                if (s.isLastPointGood()) {
1037                    s.seriesPath.lineTo(x, y);
1038                }
1039                else {
1040                    s.seriesPath.moveTo(x, y);
1041                }
1042                s.setLastPointGood(true);
1043            }
1044            else {
1045                s.setLastPointGood(false);
1046            }
1047            // if this is the last item, draw the path ...
1048            if (item == dataset.getItemCount(series) - 1) {
1049                // draw path
1050                drawFirstPassShape(g2, pass, series, item, s.seriesPath);
1051            }
1052        }
1053    
1054        /**
1055         * Draws the item shapes and adds chart entities (second pass). This method 
1056         * draws the shapes which mark the item positions. If <code>entities</code> 
1057         * is not <code>null</code> it will be populated with entity information.
1058         *
1059         * @param g2  the graphics device.
1060         * @param dataArea  the area within which the data is being drawn.
1061         * @param plot  the plot (can be used to obtain standard color 
1062         *              information etc).
1063         * @param domainAxis  the domain axis.
1064         * @param rangeAxis  the range axis.
1065         * @param dataset  the dataset.
1066         * @param pass  the pass.
1067         * @param series  the series index (zero-based).
1068         * @param item  the item index (zero-based).
1069         * @param crosshairState  the crosshair state.
1070         * @param entities the entity collection.
1071         */
1072        protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 
1073                                         XYDataset dataset,
1074                                         int pass, int series, int item,
1075                                         ValueAxis domainAxis, 
1076                                         Rectangle2D dataArea,
1077                                         ValueAxis rangeAxis, 
1078                                         CrosshairState crosshairState,
1079                                         EntityCollection entities) {
1080    
1081            Shape entityArea = null;
1082    
1083            // get the data point...
1084            double x1 = dataset.getXValue(series, item);
1085            double y1 = dataset.getYValue(series, item);
1086            if (Double.isNaN(y1) || Double.isNaN(x1)) {
1087                return;
1088            }
1089    
1090            PlotOrientation orientation = plot.getOrientation();
1091            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1092            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1093            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1094            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1095    
1096            if (getItemShapeVisible(series, item)) {
1097                Shape shape = getItemShape(series, item);
1098                if (orientation == PlotOrientation.HORIZONTAL) {
1099                    shape = ShapeUtilities.createTranslatedShape(shape, transY1, 
1100                            transX1);
1101                }
1102                else if (orientation == PlotOrientation.VERTICAL) {
1103                    shape = ShapeUtilities.createTranslatedShape(shape, transX1, 
1104                            transY1);
1105                }
1106                entityArea = shape;
1107                if (shape.intersects(dataArea)) {
1108                    if (getItemShapeFilled(series, item)) {
1109                        if (this.useFillPaint) {
1110                            g2.setPaint(getItemFillPaint(series, item));
1111                        }
1112                        else {
1113                            g2.setPaint(getItemPaint(series, item));
1114                        }
1115                        g2.fill(shape);
1116                    }
1117                    if (this.drawOutlines) {
1118                        if (getUseOutlinePaint()) {
1119                            g2.setPaint(getItemOutlinePaint(series, item));
1120                        }
1121                        else {
1122                            g2.setPaint(getItemPaint(series, item));
1123                        }
1124                        g2.setStroke(getItemOutlineStroke(series, item));
1125                        g2.draw(shape);
1126                    }
1127                }
1128            }
1129    
1130            // draw the item label if there is one...
1131            if (isItemLabelVisible(series, item)) {
1132                double xx = transX1;
1133                double yy = transY1;
1134                if (orientation == PlotOrientation.HORIZONTAL) {
1135                    xx = transY1;
1136                    yy = transX1;
1137                }          
1138                drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 
1139                        (y1 < 0.0));
1140            }
1141    
1142            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
1143            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
1144            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 
1145                    rangeAxisIndex, transX1, transY1, plot.getOrientation());
1146    
1147            // add an entity for the item...
1148            if (entities != null) {
1149                addEntity(entities, entityArea, dataset, series, item, transX1, 
1150                        transY1);
1151            }
1152        }
1153    
1154    
1155        /**
1156         * Returns a legend item for the specified series.
1157         *
1158         * @param datasetIndex  the dataset index (zero-based).
1159         * @param series  the series index (zero-based).
1160         *
1161         * @return A legend item for the series.
1162         */
1163        public LegendItem getLegendItem(int datasetIndex, int series) {
1164    
1165            XYPlot plot = getPlot();
1166            if (plot == null) {
1167                return null;
1168            }
1169    
1170            LegendItem result = null;
1171            XYDataset dataset = plot.getDataset(datasetIndex);
1172            if (dataset != null) {
1173                if (getItemVisible(series, 0)) {
1174                    String label = getLegendItemLabelGenerator().generateLabel(
1175                            dataset, series);
1176                    String description = label;
1177                    String toolTipText = null;
1178                    if (getLegendItemToolTipGenerator() != null) {
1179                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
1180                                dataset, series);
1181                    }
1182                    String urlText = null;
1183                    if (getLegendItemURLGenerator() != null) {
1184                        urlText = getLegendItemURLGenerator().generateLabel(
1185                                dataset, series);
1186                    }
1187                    boolean shapeIsVisible = getItemShapeVisible(series, 0);
1188                    Shape shape = getSeriesShape(series);
1189                    boolean shapeIsFilled = getItemShapeFilled(series, 0);
1190                    Paint fillPaint = (this.useFillPaint 
1191                        ? getSeriesFillPaint(series) : getSeriesPaint(series));
1192                    boolean shapeOutlineVisible = this.drawOutlines;  
1193                    Paint outlinePaint = (this.useOutlinePaint 
1194                        ? getSeriesOutlinePaint(series) 
1195                        : getSeriesPaint(series));
1196                    Stroke outlineStroke = getSeriesOutlineStroke(series);
1197                    boolean lineVisible = getItemLineVisible(series, 0);
1198                    Stroke lineStroke = getSeriesStroke(series);
1199                    Paint linePaint = getSeriesPaint(series);
1200                    result = new LegendItem(label, description, toolTipText, 
1201                            urlText, shapeIsVisible, shape, shapeIsFilled, 
1202                            fillPaint, shapeOutlineVisible, outlinePaint, 
1203                            outlineStroke, lineVisible, this.legendLine, 
1204                            lineStroke, linePaint);
1205                    result.setSeriesIndex(series);
1206                    result.setDatasetIndex(datasetIndex);
1207                }
1208            }
1209    
1210            return result;
1211    
1212        }
1213        
1214        /**
1215         * Returns a clone of the renderer.
1216         * 
1217         * @return A clone.
1218         * 
1219         * @throws CloneNotSupportedException if the clone cannot be created.
1220         */
1221        public Object clone() throws CloneNotSupportedException {
1222            XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone();
1223            clone.seriesLinesVisible 
1224                    = (BooleanList) this.seriesLinesVisible.clone();
1225            if (this.legendLine != null) {
1226                clone.legendLine = ShapeUtilities.clone(this.legendLine);
1227            }
1228            clone.seriesShapesVisible 
1229                    = (BooleanList) this.seriesShapesVisible.clone();
1230            clone.seriesShapesFilled 
1231                    = (BooleanList) this.seriesShapesFilled.clone();
1232            return clone;
1233        }
1234        
1235        /**
1236         * Tests this renderer for equality with an arbitrary object.
1237         *
1238         * @param obj  the object (<code>null</code> permitted).
1239         *
1240         * @return <code>true</code> or <code>false</code>.
1241         */
1242        public boolean equals(Object obj) {
1243    
1244            if (obj == this) {
1245                return true;
1246            }
1247            if (!(obj instanceof XYLineAndShapeRenderer)) {
1248                return false;
1249            }
1250            if (!super.equals(obj)) {
1251                return false;
1252            }
1253            XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
1254            if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
1255                return false;
1256            }
1257            if (!ObjectUtilities.equal(
1258                this.seriesLinesVisible, that.seriesLinesVisible)
1259            ) {
1260                return false;
1261            }
1262            if (this.baseLinesVisible != that.baseLinesVisible) {
1263                return false;
1264            }
1265            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1266                return false;   
1267            }
1268            if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
1269                return false;
1270            }
1271            if (!ObjectUtilities.equal(
1272                this.seriesShapesVisible, that.seriesShapesVisible)
1273            ) {
1274                return false;
1275            }
1276            if (this.baseShapesVisible != that.baseShapesVisible) {
1277                return false;
1278            }
1279            if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1280                return false;
1281            }
1282            if (!ObjectUtilities.equal(
1283                this.seriesShapesFilled, that.seriesShapesFilled)
1284            ) {
1285                return false;
1286            }
1287            if (this.baseShapesFilled != that.baseShapesFilled) {
1288                return false;
1289            }
1290            if (this.drawOutlines != that.drawOutlines) {
1291                return false;
1292            }
1293            if (this.useOutlinePaint != that.useOutlinePaint) {
1294                return false;
1295            }
1296            if (this.useFillPaint != that.useFillPaint) {
1297                return false;
1298            }
1299            if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1300                return false;
1301            }
1302            return true;
1303    
1304        }
1305        
1306        /**
1307         * Provides serialization support.
1308         *
1309         * @param stream  the input stream.
1310         *
1311         * @throws IOException  if there is an I/O error.
1312         * @throws ClassNotFoundException  if there is a classpath problem.
1313         */
1314        private void readObject(ObjectInputStream stream) 
1315                throws IOException, ClassNotFoundException {
1316            stream.defaultReadObject();
1317            this.legendLine = SerialUtilities.readShape(stream);
1318        }
1319        
1320        /**
1321         * Provides serialization support.
1322         *
1323         * @param stream  the output stream.
1324         *
1325         * @throws IOException  if there is an I/O error.
1326         */
1327        private void writeObject(ObjectOutputStream stream) throws IOException {
1328            stream.defaultWriteObject();
1329            SerialUtilities.writeShape(this.legendLine, stream);
1330        }
1331      
1332    }