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