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