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    
070    package org.jfree.chart.renderer.xy;
071    
072    import java.awt.Graphics2D;
073    import java.awt.Paint;
074    import java.awt.Shape;
075    import java.awt.Stroke;
076    import java.awt.geom.GeneralPath;
077    import java.awt.geom.Line2D;
078    import java.awt.geom.Rectangle2D;
079    import java.io.IOException;
080    import java.io.ObjectInputStream;
081    import java.io.ObjectOutputStream;
082    import java.io.Serializable;
083    
084    import org.jfree.chart.LegendItem;
085    import org.jfree.chart.axis.ValueAxis;
086    import org.jfree.chart.entity.EntityCollection;
087    import org.jfree.chart.event.RendererChangeEvent;
088    import org.jfree.chart.plot.CrosshairState;
089    import org.jfree.chart.plot.PlotOrientation;
090    import org.jfree.chart.plot.PlotRenderingInfo;
091    import org.jfree.chart.plot.XYPlot;
092    import org.jfree.data.xy.XYDataset;
093    import org.jfree.io.SerialUtilities;
094    import org.jfree.ui.RectangleEdge;
095    import org.jfree.util.BooleanList;
096    import org.jfree.util.BooleanUtilities;
097    import org.jfree.util.ObjectUtilities;
098    import org.jfree.util.PublicCloneable;
099    import 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     */
112    public 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    }