    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025     * Other names may be trademarks of their respective owners.]
    026     *
    027     * -------------------------
    028     * LineAndShapeRenderer.java
    029     * -------------------------
    030     * (C) Copyright 2001-2009, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Mark Watson (www.markwatson.com);
    034     *                   Jeremy Bowman;
    035     *                   Richard Atkinson;
    036     *                   Christian W. Zuckschwerdt;
    037     *                   Peter Kolb (patch 2497611);
    038     *
    039     * Changes
    040     * -------
    041     * 23-Oct-2001 : Version 1 (DG);
    042     * 15-Nov-2001 : Modified to allow for null data values (DG);
    043     * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java
    044     *               --> CategoryItemRenderer.java (DG);
    045     * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void
    046     *               to Shape, as part of the tooltips implementation (DG);
    047     * 11-May-2002 : Support for value label drawing (JB);
    048     * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
    049     * 25-Jun-2002 : Removed redundant import (DG);
    050     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
    051     *               for HTML image maps (RA);
    052     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    053     * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL
    054     *               generators (DG);
    055     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
    056     *               CategoryToolTipGenerator interface (DG);
    057     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    058     * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis
    059     *               for category spacing (DG);
    060     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
    061     * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
    062     *               method (DG);
    063     * 12-May-2003 : Modified to take into account the plot orientation (DG);
    064     * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
    065     * 30-Jul-2003 : Modified entity constructor (CZ);
    066     * 22-Sep-2003 : Fixed cloning (DG);
    067     * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste
    068     *               override easier (DG);
    069     * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal
    070     *               charts (DG);
    071     * 15-Oct-2004 : Updated equals() method (DG);
    072     * 05-Nov-2004 : Modified drawItem() signature (DG);
    073     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
    074     * 27-Jan-2005 : Changed attribute names, modified constructor and removed
    075     *               constants (DG);
    076     * 01-Feb-2005 : Removed unnecessary constants (DG);
    077     * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG);
    078     * 13-Apr-2005 : Check flags that control series visibility (DG);
    079     * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG);
    080     * 09-Jun-2005 : Use addItemEntity() method (DG);
    081     * ------------- JFREECHART 1.0.x ---------------------------------------------
    082     * 25-May-2006 : Added check to drawItem() to detect when both the line and
    083     *               the shape are not visible (DG);
    084     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    085     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    086     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    087     * 24-Sep-2007 : Deprecated redundant fields/methods (DG);
    088     * 27-Sep-2007 : Added option to offset series x-position within category (DG);
    089     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    090     * 26-Jun-2008 : Added crosshair support (DG);
    091     * 14-Jan-2009 : Added support for seriesVisible flags (PK);
    092     *
    093     */
    094
    095    package org.jfree.chart.renderer.category;
    096
    097    import java.awt.Graphics2D;
    098    import java.awt.Paint;
    099    import java.awt.Shape;
    100    import java.awt.Stroke;
    101    import java.awt.geom.Line2D;
    102    import java.awt.geom.Rectangle2D;
    103    import java.io.Serializable;
    104
    105    import org.jfree.chart.LegendItem;
    106    import org.jfree.chart.axis.CategoryAxis;
    107    import org.jfree.chart.axis.ValueAxis;
    108    import org.jfree.chart.entity.EntityCollection;
    109    import org.jfree.chart.event.RendererChangeEvent;
    110    import org.jfree.chart.plot.CategoryPlot;
    111    import org.jfree.chart.plot.PlotOrientation;
    112    import org.jfree.data.category.CategoryDataset;
    113    import org.jfree.util.BooleanList;
    114    import org.jfree.util.BooleanUtilities;
    115    import org.jfree.util.ObjectUtilities;
    116    import org.jfree.util.PublicCloneable;
    117    import org.jfree.util.ShapeUtilities;
    118
    119    /**
    120     * A renderer that draws shapes for each data item, and lines between data
    121     * items (for use with the {@link CategoryPlot} class).
    122     * The example shown here is generated by the <code>LineChartDemo1.java</code>
    123     * program included in the JFreeChart Demo Collection:
    124     * <br><br>
    125     * <img src="../../../../../images/LineAndShapeRendererSample.png"
    126     * alt="LineAndShapeRendererSample.png" />
    127     */
    128    public class LineAndShapeRenderer extends AbstractCategoryItemRenderer
    129            implements Cloneable, PublicCloneable, Serializable {
    130
    131        /** For serialization. */
    132        private static final long serialVersionUID = -197749519869226398L;
    133
    134        /**
    135         * A flag that controls whether or not lines are visible for ALL series.
    136         *
    137         * @deprecated As of 1.0.7 (this override flag is unnecessary).
    138         */
    139        private Boolean linesVisible;
    140
    141        /**
    142         * A table of flags that control (per series) whether or not lines are
    143         * visible.
    144         */
    145        private BooleanList seriesLinesVisible;
    146
    147        /**
    148         * A flag indicating whether or not lines are drawn between non-null
    149         * points.
    150         */
    151        private boolean baseLinesVisible;
    152
    153        /**
    154         * A flag that controls whether or not shapes are visible for ALL series.
    155         *
    156         * @deprecated As of 1.0.7 (this override flag is unnecessary).
    157         */
    158        private Boolean shapesVisible;
    159
    160        /**
    161         * A table of flags that control (per series) whether or not shapes are
    162         * visible.
    163         */
    164        private BooleanList seriesShapesVisible;
    165
    166        /** The default value returned by the getShapeVisible() method. */
    167        private boolean baseShapesVisible;
    168
    169        /**
    170         * A flag that controls whether or not shapes are filled for ALL series.
    171         *
    172         * @deprecated As of 1.0.7 (this override flag is unnecessary).
    173         */
    174        private Boolean shapesFilled;
    175
    176        /**
    177         * A table of flags that control (per series) whether or not shapes are
    178         * filled.
    179         */
    180        private BooleanList seriesShapesFilled;
    181
    182        /** The default value returned by the getShapeFilled() method. */
    183        private boolean baseShapesFilled;
    184
    185        /**
    186         * A flag that controls whether the fill paint is used for filling
    187         * shapes.
    188         */
    189        private boolean useFillPaint;
    190
    191        /** A flag that controls whether outlines are drawn for shapes. */
    192        private boolean drawOutlines;
    193
    194        /**
    195         * A flag that controls whether the outline paint is used for drawing shape
    196         * outlines - if not, the regular series paint is used.
    197         */
    198        private boolean useOutlinePaint;
    199
    200        /**
    201         * A flag that controls whether or not the x-position for each item is
    202         * offset within the category according to the series.
    203         *
    204         * @since 1.0.7
    205         */
    206        private boolean useSeriesOffset;
    207
    208        /**
    209         * The item margin used for series offsetting - this allows the positioning
    210         * to match the bar positions of the {@link BarRenderer} class.
    211         *
    212         * @since 1.0.7
    213         */
    214        private double itemMargin;
    215
    216        /**
    217         * Creates a renderer with both lines and shapes visible by default.
    218         */
    219        public LineAndShapeRenderer() {
    220            this(true, true);
    221        }
    222
    223        /**
    224         * Creates a new renderer with lines and/or shapes visible.
    225         *
    226         * @param lines  draw lines?
    227         * @param shapes  draw shapes?
    228         */
    229        public LineAndShapeRenderer(boolean lines, boolean shapes) {
    230            super();
    231            this.linesVisible = null;
    232            this.seriesLinesVisible = new BooleanList();
    233            this.baseLinesVisible = lines;
    234            this.shapesVisible = null;
    235            this.seriesShapesVisible = new BooleanList();
    236            this.baseShapesVisible = shapes;
    237            this.shapesFilled = null;
    238            this.seriesShapesFilled = new BooleanList();
    239            this.baseShapesFilled = true;
    240            this.useFillPaint = false;
    241            this.drawOutlines = true;
    242            this.useOutlinePaint = false;
    243            this.useSeriesOffset = false;  // preserves old behaviour
    244            this.itemMargin = 0.0;
    245        }
    246
    247        // LINES VISIBLE
    248
    249        /**
    250         * Returns the flag used to control whether or not the line for an item is
    251         * visible.
    252         *
    253         * @param series  the series index (zero-based).
    254         * @param item  the item index (zero-based).
    255         *
    256         * @return A boolean.
    257         */
    258        public boolean getItemLineVisible(int series, int item) {
    259            Boolean flag = this.linesVisible;
    260            if (flag == null) {
    261                flag = getSeriesLinesVisible(series);
    262            }
    263            if (flag != null) {
    264                return flag.booleanValue();
    265            }
    266            else {
    267                return this.baseLinesVisible;
    268            }
    269        }
    270
    271        /**
    272         * Returns a flag that controls whether or not lines are drawn for ALL
    273         * series.  If this flag is <code>null</code>, then the "per series"
    274         * settings will apply.
    275         *
    276         * @return A flag (possibly <code>null</code>).
    277         *
    278         * @see #setLinesVisible(Boolean)
    279         *
    280         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    281         *     use the per-series and base (default) settings).
    282         */
    283        public Boolean getLinesVisible() {
    284            return this.linesVisible;
    285        }
    286
    287        /**
    288         * Sets a flag that controls whether or not lines are drawn between the
    289         * items in ALL series, and sends a {@link RendererChangeEvent} to all
    290         * registered listeners.  You need to set this to <code>null</code> if you
    291         * want the "per series" settings to apply.
    292         *
    293         * @param visible  the flag (<code>null</code> permitted).
    294         *
    295         * @see #getLinesVisible()
    296         *
    297         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    298         *     use the per-series and base (default) settings).
    299         */
    300        public void setLinesVisible(Boolean visible) {
    301            this.linesVisible = visible;
    302            fireChangeEvent();
    303        }
    304
    305        /**
    306         * Sets a flag that controls whether or not lines are drawn between the
    307         * items in ALL series, and sends a {@link RendererChangeEvent} to all
    308         * registered listeners.
    309         *
    310         * @param visible  the flag.
    311         *
    312         * @see #getLinesVisible()
    313         *
    314         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    315         *     use the per-series and base (default) settings).
    316         */
    317        public void setLinesVisible(boolean visible) {
    318            setLinesVisible(BooleanUtilities.valueOf(visible));
    319        }
    320
    321        /**
    322         * Returns the flag used to control whether or not the lines for a series
    323         * are visible.
    324         *
    325         * @param series  the series index (zero-based).
    326         *
    327         * @return The flag (possibly <code>null</code>).
    328         *
    329         * @see #setSeriesLinesVisible(int, Boolean)
    330         */
    331        public Boolean getSeriesLinesVisible(int series) {
    332            return this.seriesLinesVisible.getBoolean(series);
    333        }
    334
    335        /**
    336         * Sets the 'lines visible' flag for a series and sends a
    337         * {@link RendererChangeEvent} to all registered listeners.
    338         *
    339         * @param series  the series index (zero-based).
    340         * @param flag  the flag (<code>null</code> permitted).
    341         *
    342         * @see #getSeriesLinesVisible(int)
    343         */
    344        public void setSeriesLinesVisible(int series, Boolean flag) {
    345            this.seriesLinesVisible.setBoolean(series, flag);
    346            fireChangeEvent();
    347        }
    348
    349        /**
    350         * Sets the 'lines visible' flag for a series and sends a
    351         * {@link RendererChangeEvent} to all registered listeners.
    352         *
    353         * @param series  the series index (zero-based).
    354         * @param visible  the flag.
    355         *
    356         * @see #getSeriesLinesVisible(int)
    357         */
    358        public void setSeriesLinesVisible(int series, boolean visible) {
    359            setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
    360        }
    361
    362        /**
    363         * Returns the base 'lines visible' attribute.
    364         *
    365         * @return The base flag.
    366         *
    367         * @see #getBaseLinesVisible()
    368         */
    369        public boolean getBaseLinesVisible() {
    370            return this.baseLinesVisible;
    371        }
    372
    373        /**
    374         * Sets the base 'lines visible' flag and sends a
    375         * {@link RendererChangeEvent} to all registered listeners.
    376         *
    377         * @param flag  the flag.
    378         *
    379         * @see #getBaseLinesVisible()
    380         */
    381        public void setBaseLinesVisible(boolean flag) {
    382            this.baseLinesVisible = flag;
    383            fireChangeEvent();
    384        }
    385
    386        // SHAPES VISIBLE
    387
    388        /**
    389         * Returns the flag used to control whether or not the shape for an item is
    390         * visible.
    391         *
    392         * @param series  the series index (zero-based).
    393         * @param item  the item index (zero-based).
    394         *
    395         * @return A boolean.
    396         */
    397        public boolean getItemShapeVisible(int series, int item) {
    398            Boolean flag = this.shapesVisible;
    399            if (flag == null) {
    400                flag = getSeriesShapesVisible(series);
    401            }
    402            if (flag != null) {
    403                return flag.booleanValue();
    404            }
    405            else {
    406                return this.baseShapesVisible;
    407            }
    408        }
    409
    410        /**
    411         * Returns the flag that controls whether the shapes are visible for the
    412         * items in ALL series.
    413         *
    414         * @return The flag (possibly <code>null</code>).
    415         *
    416         * @see #setShapesVisible(Boolean)
    417         *
    418         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    419         *     use the per-series and base (default) settings).
    420         */
    421        public Boolean getShapesVisible() {
    422            return this.shapesVisible;
    423        }
    424
    425        /**
    426         * Sets the 'shapes visible' for ALL series and sends a
    427         * {@link RendererChangeEvent} to all registered listeners.
    428         *
    429         * @param visible  the flag (<code>null</code> permitted).
    430         *
    431         * @see #getShapesVisible()
    432         *
    433         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    434         *     use the per-series and base (default) settings).
    435         */
    436        public void setShapesVisible(Boolean visible) {
    437            this.shapesVisible = visible;
    438            fireChangeEvent();
    439        }
    440
    441        /**
    442         * Sets the 'shapes visible' for ALL series and sends a
    443         * {@link RendererChangeEvent} to all registered listeners.
    444         *
    445         * @param visible  the flag.
    446         *
    447         * @see #getShapesVisible()
    448         *
    449         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    450         *     use the per-series and base (default) settings).
    451         */
    452        public void setShapesVisible(boolean visible) {
    453            setShapesVisible(BooleanUtilities.valueOf(visible));
    454        }
    455
    456        /**
    457         * Returns the flag used to control whether or not the shapes for a series
    458         * are visible.
    459         *
    460         * @param series  the series index (zero-based).
    461         *
    462         * @return A boolean.
    463         *
    464         * @see #setSeriesShapesVisible(int, Boolean)
    465         */
    466        public Boolean getSeriesShapesVisible(int series) {
    467            return this.seriesShapesVisible.getBoolean(series);
    468        }
    469
    470        /**
    471         * Sets the 'shapes visible' flag for a series and sends a
    472         * {@link RendererChangeEvent} to all registered listeners.
    473         *
    474         * @param series  the series index (zero-based).
    475         * @param visible  the flag.
    476         *
    477         * @see #getSeriesShapesVisible(int)
    478         */
    479        public void setSeriesShapesVisible(int series, boolean visible) {
    480            setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
    481        }
    482
    483        /**
    484         * Sets the 'shapes visible' flag for a series and sends a
    485         * {@link RendererChangeEvent} to all registered listeners.
    486         *
    487         * @param series  the series index (zero-based).
    488         * @param flag  the flag.
    489         *
    490         * @see #getSeriesShapesVisible(int)
    491         */
    492        public void setSeriesShapesVisible(int series, Boolean flag) {
    493            this.seriesShapesVisible.setBoolean(series, flag);
    494            fireChangeEvent();
    495        }
    496
    497        /**
    498         * Returns the base 'shape visible' attribute.
    499         *
    500         * @return The base flag.
    501         *
    502         * @see #setBaseShapesVisible(boolean)
    503         */
    504        public boolean getBaseShapesVisible() {
    505            return this.baseShapesVisible;
    506        }
    507
    508        /**
    509         * Sets the base 'shapes visible' flag and sends a
    510         * {@link RendererChangeEvent} to all registered listeners.
    511         *
    512         * @param flag  the flag.
    513         *
    514         * @see #getBaseShapesVisible()
    515         */
    516        public void setBaseShapesVisible(boolean flag) {
    517            this.baseShapesVisible = flag;
    518            fireChangeEvent();
    519        }
    520
    521        /**
    522         * Returns <code>true</code> if outlines should be drawn for shapes, and
    523         * <code>false</code> otherwise.
    524         *
    525         * @return A boolean.
    526         *
    527         * @see #setDrawOutlines(boolean)
    528         */
    529        public boolean getDrawOutlines() {
    530            return this.drawOutlines;
    531        }
    532
    533        /**
    534         * Sets the flag that controls whether outlines are drawn for
    535         * shapes, and sends a {@link RendererChangeEvent} to all registered
    536         * listeners.
    537         * <P>
    538         * In some cases, shapes look better if they do NOT have an outline, but
    539         * this flag allows you to set your own preference.
    540         *
    541         * @param flag  the flag.
    542         *
    543         * @see #getDrawOutlines()
    544         */
    545        public void setDrawOutlines(boolean flag) {
    546            this.drawOutlines = flag;
    547            fireChangeEvent();
    548        }
    549
    550        /**
    551         * Returns the flag that controls whether the outline paint is used for
    552         * shape outlines.  If not, the regular series paint is used.
    553         *
    554         * @return A boolean.
    555         *
    556         * @see #setUseOutlinePaint(boolean)
    557         */
    558        public boolean getUseOutlinePaint() {
    559            return this.useOutlinePaint;
    560        }
    561
    562        /**
    563         * Sets the flag that controls whether the outline paint is used for shape
    564         * outlines, and sends a {@link RendererChangeEvent} to all registered
    565         * listeners.
    566         *
    567         * @param use  the flag.
    568         *
    569         * @see #getUseOutlinePaint()
    570         */
    571        public void setUseOutlinePaint(boolean use) {
    572            this.useOutlinePaint = use;
    573            fireChangeEvent();
    574        }
    575
    576        // SHAPES FILLED
    577
    578        /**
    579         * Returns the flag used to control whether or not the shape for an item
    580         * is filled. The default implementation passes control to the
    581         * <code>getSeriesShapesFilled</code> method. You can override this method
    582         * if you require different behaviour.
    583         *
    584         * @param series  the series index (zero-based).
    585         * @param item  the item index (zero-based).
    586         *
    587         * @return A boolean.
    588         */
    589        public boolean getItemShapeFilled(int series, int item) {
    590            return getSeriesShapesFilled(series);
    591        }
    592
    593        /**
    594         * Returns the flag used to control whether or not the shapes for a series
    595         * are filled.
    596         *
    597         * @param series  the series index (zero-based).
    598         *
    599         * @return A boolean.
    600         */
    601        public boolean getSeriesShapesFilled(int series) {
    602
    603            // return the overall setting, if there is one...
    604            if (this.shapesFilled != null) {
    605                return this.shapesFilled.booleanValue();
    606            }
    607
    608            // otherwise look up the paint table
    609            Boolean flag = this.seriesShapesFilled.getBoolean(series);
    610            if (flag != null) {
    611                return flag.booleanValue();
    612            }
    613            else {
    614                return this.baseShapesFilled;
    615            }
    616
    617        }
    618
    619        /**
    620         * Returns the flag that controls whether or not shapes are filled for
    621         * ALL series.
    622         *
    623         * @return A Boolean.
    624         *
    625         * @see #setShapesFilled(Boolean)
    626         *
    627         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    628         *     use the per-series and base (default) settings).
    629         */
    630        public Boolean getShapesFilled() {
    631            return this.shapesFilled;
    632        }
    633
    634        /**
    635         * Sets the 'shapes filled' for ALL series and sends a
    636         * {@link RendererChangeEvent} to all registered listeners.
    637         *
    638         * @param filled  the flag.
    639         *
    640         * @see #getShapesFilled()
    641         *
    642         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    643         *     use the per-series and base (default) settings).
    644         */
    645        public void setShapesFilled(boolean filled) {
    646            if (filled) {
    647                setShapesFilled(Boolean.TRUE);
    648            }
    649            else {
    650                setShapesFilled(Boolean.FALSE);
    651            }
    652        }
    653
    654        /**
    655         * Sets the 'shapes filled' for ALL series and sends a
    656         * {@link RendererChangeEvent} to all registered listeners.
    657         *
    658         * @param filled  the flag (<code>null</code> permitted).
    659         *
    660         * @see #getShapesFilled()
    661         *
    662         * @deprecated As of 1.0.7 (the override facility is unnecessary, just
    663         *     use the per-series and base (default) settings).
    664         */
    665        public void setShapesFilled(Boolean filled) {
    666            this.shapesFilled = filled;
    667            fireChangeEvent();
    668        }
    669
    670        /**
    671         * Sets the 'shapes filled' flag for a series and sends a
    672         * {@link RendererChangeEvent} to all registered listeners.
    673         *
    674         * @param series  the series index (zero-based).
    675         * @param filled  the flag.
    676         *
    677         * @see #getSeriesShapesFilled(int)
    678         */
    679        public void setSeriesShapesFilled(int series, Boolean filled) {
    680            this.seriesShapesFilled.setBoolean(series, filled);
    681            fireChangeEvent();
    682        }
    683
    684        /**
    685         * Sets the 'shapes filled' flag for a series and sends a
    686         * {@link RendererChangeEvent} to all registered listeners.
    687         *
    688         * @param series  the series index (zero-based).
    689         * @param filled  the flag.
    690         *
    691         * @see #getSeriesShapesFilled(int)
    692         */
    693        public void setSeriesShapesFilled(int series, boolean filled) {
    694            // delegate
    695            setSeriesShapesFilled(series, BooleanUtilities.valueOf(filled));
    696        }
    697
    698        /**
    699         * Returns the base 'shape filled' attribute.
    700         *
    701         * @return The base flag.
    702         *
    703         * @see #setBaseShapesFilled(boolean)
    704         */
    705        public boolean getBaseShapesFilled() {
    706            return this.baseShapesFilled;
    707        }
    708
    709        /**
    710         * Sets the base 'shapes filled' flag and sends a
    711         * {@link RendererChangeEvent} to all registered listeners.
    712         *
    713         * @param flag  the flag.
    714         *
    715         * @see #getBaseShapesFilled()
    716         */
    717        public void setBaseShapesFilled(boolean flag) {
    718            this.baseShapesFilled = flag;
    719            fireChangeEvent();
    720        }
    721
    722        /**
    723         * Returns <code>true</code> if the renderer should use the fill paint
    724         * setting to fill shapes, and <code>false</code> if it should just
    725         * use the regular paint.
    726         *
    727         * @return A boolean.
    728         *
    729         * @see #setUseFillPaint(boolean)
    730         */
    731        public boolean getUseFillPaint() {
    732            return this.useFillPaint;
    733        }
    734
    735        /**
    736         * Sets the flag that controls whether the fill paint is used to fill
    737         * shapes, and sends a {@link RendererChangeEvent} to all
    738         * registered listeners.
    739         *
    740         * @param flag  the flag.
    741         *
    742         * @see #getUseFillPaint()
    743         */
    744        public void setUseFillPaint(boolean flag) {
    745            this.useFillPaint = flag;
    746            fireChangeEvent();
    747        }
    748
    749        /**
    750         * Returns the flag that controls whether or not the x-position for each
    751         * data item is offset within the category according to the series.
    752         *
    753         * @return A boolean.
    754         *
    755         * @see #setUseSeriesOffset(boolean)
    756         *
    757         * @since 1.0.7
    758         */
    759        public boolean getUseSeriesOffset() {
    760            return this.useSeriesOffset;
    761        }
    762
    763        /**
    764         * Sets the flag that controls whether or not the x-position for each
    765         * data item is offset within its category according to the series, and
    766         * sends a {@link RendererChangeEvent} to all registered listeners.
    767         *
    768         * @param offset  the offset.
    769         *
    770         * @see #getUseSeriesOffset()
    771         *
    772         * @since 1.0.7
    773         */
    774        public void setUseSeriesOffset(boolean offset) {
    775            this.useSeriesOffset = offset;
    776            fireChangeEvent();
    777        }
    778
    779        /**
    780         * Returns the item margin, which is the gap between items within a
    781         * category (expressed as a percentage of the overall category width).
    782         * This can be used to match the offset alignment with the bars drawn by
    783         * a {@link BarRenderer}).
    784         *
    785         * @return The item margin.
    786         *
    787         * @see #setItemMargin(double)
    788         * @see #getUseSeriesOffset()
    789         *
    790         * @since 1.0.7
    791         */
    792        public double getItemMargin() {
    793            return this.itemMargin;
    794        }
    795
    796        /**
    797         * Sets the item margin, which is the gap between items within a category
    798         * (expressed as a percentage of the overall category width), and sends
    799         * a {@link RendererChangeEvent} to all registered listeners.
    800         *
    801         * @param margin  the margin (0.0 <= margin < 1.0).
    802         *
    803         * @see #getItemMargin()
    804         * @see #getUseSeriesOffset()
    805         *
    806         * @since 1.0.7
    807         */
    808        public void setItemMargin(double margin) {
    809            if (margin < 0.0 || margin >= 1.0) {
    810                throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0.");
    811            }
    812            this.itemMargin = margin;
    813            fireChangeEvent();
    814        }
    815
    816        /**
    817         * Returns a legend item for a series.
    818         *
    819         * @param datasetIndex  the dataset index (zero-based).
    820         * @param series  the series index (zero-based).
    821         *
    822         * @return The legend item.
    823         */
    824        public LegendItem getLegendItem(int datasetIndex, int series) {
    825
    826            CategoryPlot cp = getPlot();
    827            if (cp == null) {
    828                return null;
    829            }
    830
    831            if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
    832                CategoryDataset dataset = cp.getDataset(datasetIndex);
    833                String label = getLegendItemLabelGenerator().generateLabel(
    834                        dataset, series);
    835                String description = label;
    836                String toolTipText = null;
    837                if (getLegendItemToolTipGenerator() != null) {
    838                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
    839                            dataset, series);
    840                }
    841                String urlText = null;
    842                if (getLegendItemURLGenerator() != null) {
    843                    urlText = getLegendItemURLGenerator().generateLabel(
    844                            dataset, series);
    845                }
    846                Shape shape = lookupLegendShape(series);
    847                Paint paint = lookupSeriesPaint(series);
    848                Paint fillPaint = (this.useFillPaint
    849                        ? getItemFillPaint(series, 0) : paint);
    850                boolean shapeOutlineVisible = this.drawOutlines;
    851                Paint outlinePaint = (this.useOutlinePaint
    852                        ? getItemOutlinePaint(series, 0) : paint);
    853                Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    854                boolean lineVisible = getItemLineVisible(series, 0);
    855                boolean shapeVisible = getItemShapeVisible(series, 0);
    856                LegendItem result = new LegendItem(label, description, toolTipText,
    857                        urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
    858                        fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
    859                        lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
    860                        getItemStroke(series, 0), getItemPaint(series, 0));
    861                result.setLabelFont(lookupLegendTextFont(series));
    862                Paint labelPaint = lookupLegendTextPaint(series);
    863                if (labelPaint != null) {
    864                    result.setLabelPaint(labelPaint);
    865                }
    866                result.setDataset(dataset);
    867                result.setDatasetIndex(datasetIndex);
    868                result.setSeriesKey(dataset.getRowKey(series));
    869                result.setSeriesIndex(series);
    870                return result;
    871            }
    872            return null;
    873
    874        }
    875
    876        /**
    877         * This renderer uses two passes to draw the data.
    878         *
    879         * @return The pass count (<code>2</code> for this renderer).
    880         */
    881        public int getPassCount() {
    882            return 2;
    883        }
    884
    885        /**
    886         * Draw a single data item.
    887         *
    888         * @param g2  the graphics device.
    889         * @param state  the renderer state.
    890         * @param dataArea  the area in which the data is drawn.
    891         * @param plot  the plot.
    892         * @param domainAxis  the domain axis.
    893         * @param rangeAxis  the range axis.
    894         * @param dataset  the dataset.
    895         * @param row  the row index (zero-based).
    896         * @param column  the column index (zero-based).
    897         * @param pass  the pass index.
    898         */
    899        public void drawItem(Graphics2D g2, CategoryItemRendererState state,
    900                Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
    901                ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
    902                int pass) {
    903
    904            // do nothing if item is not visible
    905            if (!getItemVisible(row, column)) {
    906                return;
    907            }
    908
    909            // do nothing if both the line and shape are not visible
    910            if (!getItemLineVisible(row, column)
    911                    && !getItemShapeVisible(row, column)) {
    912                return;
    913            }
    914
    915            // nothing is drawn for null...
    916            Number v = dataset.getValue(row, column);
    917            if (v == null) {
    918                return;
    919            }
    920
    921            int visibleRow = state.getVisibleSeriesIndex(row);
    922            if (visibleRow < 0) {
    923                return;
    924            }
    925            int visibleRowCount = state.getVisibleSeriesCount();
    926
    927            PlotOrientation orientation = plot.getOrientation();
    928
    929            // current data point...
    930            double x1;
    931            if (this.useSeriesOffset) {
    932                x1 = domainAxis.getCategorySeriesMiddle(column,
    933                        dataset.getColumnCount(), visibleRow, visibleRowCount,
    934                        this.itemMargin, dataArea, plot.getDomainAxisEdge());
    935            }
    936            else {
    937                x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    938                        dataArea, plot.getDomainAxisEdge());
    939            }
    940            double value = v.doubleValue();
    941            double y1 = rangeAxis.valueToJava2D(value, dataArea,
    942                    plot.getRangeAxisEdge());
    943
    944            if (pass == 0 && getItemLineVisible(row, column)) {
    945                if (column != 0) {
    946                    Number previousValue = dataset.getValue(row, column - 1);
    947                    if (previousValue != null) {
    948                        // previous data point...
    949                        double previous = previousValue.doubleValue();
    950                        double x0;
    951                        if (this.useSeriesOffset) {
    952                            x0 = domainAxis.getCategorySeriesMiddle(
    953                                    column - 1, dataset.getColumnCount(),
    954                                    visibleRow, visibleRowCount,
    955                                    this.itemMargin, dataArea,
    956                                    plot.getDomainAxisEdge());
    957                        }
    958                        else {
    959                            x0 = domainAxis.getCategoryMiddle(column - 1,
    960                                    getColumnCount(), dataArea,
    961                                    plot.getDomainAxisEdge());
    962                        }
    963                        double y0 = rangeAxis.valueToJava2D(previous, dataArea,
    964                                plot.getRangeAxisEdge());
    965
    966                        Line2D line = null;
    967                        if (orientation == PlotOrientation.HORIZONTAL) {
    968                            line = new Line2D.Double(y0, x0, y1, x1);
    969                        }
    970                        else if (orientation == PlotOrientation.VERTICAL) {
    971                            line = new Line2D.Double(x0, y0, x1, y1);
    972                        }
    973                        g2.setPaint(getItemPaint(row, column));
    974                        g2.setStroke(getItemStroke(row, column));
    975                        g2.draw(line);
    976                    }
    977                }
    978            }
    979
    980            if (pass == 1) {
    981                Shape shape = getItemShape(row, column);
    982                if (orientation == PlotOrientation.HORIZONTAL) {
    983                    shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    984                }
    985                else if (orientation == PlotOrientation.VERTICAL) {
    986                    shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    987                }
    988
    989                if (getItemShapeVisible(row, column)) {
    990                    if (getItemShapeFilled(row, column)) {
    991                        if (this.useFillPaint) {
    992                            g2.setPaint(getItemFillPaint(row, column));
    993                        }
    994                        else {
    995                            g2.setPaint(getItemPaint(row, column));
    996                        }
    997                        g2.fill(shape);
    998                    }
    999                    if (this.drawOutlines) {
    1000                        if (this.useOutlinePaint) {
    1001                            g2.setPaint(getItemOutlinePaint(row, column));
    1002                        }
    1003                        else {
    1004                            g2.setPaint(getItemPaint(row, column));
    1005                        }
    1006                        g2.setStroke(getItemOutlineStroke(row, column));
    1007                        g2.draw(shape);
    1008                    }
    1009                }
    1010
    1011                // draw the item label if there is one...
    1012                if (isItemLabelVisible(row, column)) {
    1013                    if (orientation == PlotOrientation.HORIZONTAL) {
    1014                        drawItemLabel(g2, orientation, dataset, row, column, y1,
    1015                                x1, (value < 0.0));
    1016                    }
    1017                    else if (orientation == PlotOrientation.VERTICAL) {
    1018                        drawItemLabel(g2, orientation, dataset, row, column, x1,
    1019                                y1, (value < 0.0));
    1020                    }
    1021                }
    1022
    1023                // submit the current data point as a crosshair candidate
    1024                int datasetIndex = plot.indexOf(dataset);
    1025                updateCrosshairValues(state.getCrosshairState(),
    1026                        dataset.getRowKey(row), dataset.getColumnKey(column),
    1027                        value, datasetIndex, x1, y1, orientation);
    1028
    1029                // add an item entity, if this information is being collected
    1030                EntityCollection entities = state.getEntityCollection();
    1031                if (entities != null) {
    1032                    addItemEntity(entities, dataset, row, column, shape);
    1033                }
    1034            }
    1035
    1036        }
    1037
    1038        /**
    1039         * Tests this renderer for equality with an arbitrary object.
    1040         *
    1041         * @param obj  the object (<code>null</code> permitted).
    1042         *
    1043         * @return A boolean.
    1044         */
    1045        public boolean equals(Object obj) {
    1046
    1047            if (obj == this) {
    1048                return true;
    1049            }
    1050            if (!(obj instanceof LineAndShapeRenderer)) {
    1051                return false;
    1052            }
    1053
    1054            LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
    1055            if (this.baseLinesVisible != that.baseLinesVisible) {
    1056                return false;
    1057            }
    1058            if (!ObjectUtilities.equal(this.seriesLinesVisible,
    1059                    that.seriesLinesVisible)) {
    1060                return false;
    1061            }
    1062            if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
    1063                return false;
    1064            }
    1065            if (this.baseShapesVisible != that.baseShapesVisible) {
    1066                return false;
    1067            }
    1068            if (!ObjectUtilities.equal(this.seriesShapesVisible,
    1069                    that.seriesShapesVisible)) {
    1070                return false;
    1071            }
    1072            if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
    1073                return false;
    1074            }
    1075            if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
    1076                return false;
    1077            }
    1078            if (!ObjectUtilities.equal(this.seriesShapesFilled,
    1079                    that.seriesShapesFilled)) {
    1080                return false;
    1081            }
    1082            if (this.baseShapesFilled != that.baseShapesFilled) {
    1083                return false;
    1084            }
    1085            if (this.useOutlinePaint != that.useOutlinePaint) {
    1086                return false;
    1087            }
    1088            if (this.useSeriesOffset != that.useSeriesOffset) {
    1089                return false;
    1090            }
    1091            if (this.itemMargin != that.itemMargin) {
    1092                return false;
    1093            }
    1094            return super.equals(obj);
    1095        }
    1096
    1097        /**
    1098         * Returns an independent copy of the renderer.
    1099         *
    1100         * @return A clone.
    1101         *
    1102         * @throws CloneNotSupportedException  should not happen.
    1103         */
    1104        public Object clone() throws CloneNotSupportedException {
    1105            LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
    1106            clone.seriesLinesVisible
    1107                    = (BooleanList) this.seriesLinesVisible.clone();
    1108            clone.seriesShapesVisible
    1109                    = (BooleanList) this.seriesShapesVisible.clone();
    1110            clone.seriesShapesFilled
    1111                    = (BooleanList) this.seriesShapesFilled.clone();
    1112            return clone;
    1113        }
    1114
    1115    }