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