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