    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
    095package org.jfree.chart.renderer.category;
    096
    097import java.awt.Graphics2D;
    098import java.awt.Paint;
    099import java.awt.Shape;
    100import java.awt.Stroke;
    101import java.awt.geom.Line2D;
    102import java.awt.geom.Rectangle2D;
    103import java.io.Serializable;
    104
    105import org.jfree.chart.LegendItem;
    106import org.jfree.chart.axis.CategoryAxis;
    107import org.jfree.chart.axis.ValueAxis;
    108import org.jfree.chart.entity.EntityCollection;
    109import org.jfree.chart.event.RendererChangeEvent;
    110import org.jfree.chart.plot.CategoryPlot;
    111import org.jfree.chart.plot.PlotOrientation;
    112import org.jfree.data.category.CategoryDataset;
    113import org.jfree.util.BooleanList;
    114import org.jfree.util.BooleanUtilities;
    115import org.jfree.util.ObjectUtilities;
    116import org.jfree.util.PublicCloneable;
    117import 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 */
    128public 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}