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