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