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