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