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