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