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