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