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