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