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