    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     * BarRenderer.java
    029     * ----------------
    030     * (C) Copyright 2002-2009, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *                   Peter Kolb (patches 2497611, 2791407);
    035     *
    036     * Changes
    037     * -------
    038     * 14-Mar-2002 : Version 1 (DG);
    039     * 23-May-2002 : Added tooltip generator to renderer (DG);
    040     * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
    041     * 25-Jun-2002 : Changed constructor to protected and removed redundant
    042     *               code (DG);
    043     * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
    044     *               clip values (DG);
    045     * 24-Sep-2002 : Added getLegendItem() method (DG);
    046     * 09-Oct-2002 : Modified constructor to include URL generator (DG);
    047     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    048     * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
    049     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
    050     * 25-Mar-2003 : Implemented Serializable (DG);
    051     * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
    052     * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
    053     * 12-Jun-2003 : Updates for item labels (DG);
    054     * 30-Jul-2003 : Modified entity constructor (CZ);
    055     * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
    056     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    057     * 07-Oct-2003 : Added renderer state (DG);
    058     * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
    059     *               methods (DG);
    060     * 28-Oct-2003 : Added support for gradient paint on bars (DG);
    061     * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
    062     * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
    063     *               overriding (DG);
    064     * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
    065     *               label generators.  Fixed equals() method (DG);
    066     * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
    067     * 05-Nov-2004 : Modified drawItem() signature (DG);
    068     * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
    069     * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
    070     * 18-May-2005 : Added configurable base value (DG);
    071     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
    072     * 01-Dec-2005 : Update legend item to use/not use outline (DG);
    073     * ------------: JFreeChart 1.0.x ---------------------------------------------
    074     * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
    075     * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
    076     * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
    077     *               bars) (DG);
    078     * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
    079     * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
    080     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    081     * 11-May-2007 : Check for visibility in getLegendItem() (DG);
    082     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    083     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    084     * 07-May-2008 : If minimumBarLength is > 0.0, extend the non-base end of the
    085     *               bar (DG);
    086     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    087     * 24-Jun-2008 : Added barPainter mechanism (DG);
    088     * 26-Jun-2008 : Added crosshair support (DG);
    089     * 13-Aug-2008 : Added shadowPaint attribute (DG);
    090     * 14-Jan-2009 : Added support for seriesVisible flags (PK);
    091     * 03-Feb-2009 : Added defaultShadowsVisible flag - see patch 2511330 (PK);
    092     *
    093     */
    094
    095    package org.jfree.chart.renderer.category;
    096
    097    import java.awt.BasicStroke;
    098    import java.awt.Color;
    099    import java.awt.Font;
    100    import java.awt.Graphics2D;
    101    import java.awt.Paint;
    102    import java.awt.Shape;
    103    import java.awt.Stroke;
    104    import java.awt.geom.Line2D;
    105    import java.awt.geom.Point2D;
    106    import java.awt.geom.Rectangle2D;
    107    import java.io.IOException;
    108    import java.io.ObjectInputStream;
    109    import java.io.ObjectOutputStream;
    110    import java.io.Serializable;
    111
    112    import org.jfree.chart.LegendItem;
    113    import org.jfree.chart.axis.CategoryAxis;
    114    import org.jfree.chart.axis.ValueAxis;
    115    import org.jfree.chart.entity.EntityCollection;
    116    import org.jfree.chart.event.RendererChangeEvent;
    117    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    118    import org.jfree.chart.labels.ItemLabelAnchor;
    119    import org.jfree.chart.labels.ItemLabelPosition;
    120    import org.jfree.chart.plot.CategoryPlot;
    121    import org.jfree.chart.plot.PlotOrientation;
    122    import org.jfree.chart.plot.PlotRenderingInfo;
    123    import org.jfree.data.Range;
    124    import org.jfree.data.category.CategoryDataset;
    125    import org.jfree.io.SerialUtilities;
    126    import org.jfree.text.TextUtilities;
    127    import org.jfree.ui.GradientPaintTransformer;
    128    import org.jfree.ui.RectangleEdge;
    129    import org.jfree.ui.StandardGradientPaintTransformer;
    130    import org.jfree.util.ObjectUtilities;
    131    import org.jfree.util.PaintUtilities;
    132    import org.jfree.util.PublicCloneable;
    133
    134    /**
    135     * A {@link CategoryItemRenderer} that draws individual data items as bars.
    136     * The example shown here is generated by the <code>BarChartDemo1.java</code>
    137     * program included in the JFreeChart Demo Collection:
    138     * <br><br>
    139     * <img src="../../../../../images/BarRendererSample.png"
    140     * alt="BarRendererSample.png" />
    141     */
    142    public class BarRenderer extends AbstractCategoryItemRenderer
    143            implements Cloneable, PublicCloneable, Serializable {
    144
    145        /** For serialization. */
    146        private static final long serialVersionUID = 6000649414965887481L;
    147
    148        /** The default item margin percentage. */
    149        public static final double DEFAULT_ITEM_MARGIN = 0.20;
    150
    151        /**
    152         * Constant that controls the minimum width before a bar has an outline
    153         * drawn.
    154         */
    155        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
    156
    157        /**
    158         * The default bar painter assigned to each new instance of this renderer.
    159         *
    160         * @since 1.0.11
    161         */
    162        private static BarPainter defaultBarPainter = new GradientBarPainter();
    163
    164        /**
    165         * Returns the default bar painter.
    166         *
    167         * @return The default bar painter.
    168         *
    169         * @since 1.0.11
    170         */
    171        public static BarPainter getDefaultBarPainter() {
    172            return BarRenderer.defaultBarPainter;
    173        }
    174
    175        /**
    176         * Sets the default bar painter.
    177         *
    178         * @param painter  the painter (<code>null</code> not permitted).
    179         *
    180         * @since 1.0.11
    181         */
    182        public static void setDefaultBarPainter(BarPainter painter) {
    183            if (painter == null) {
    184                throw new IllegalArgumentException("Null 'painter' argument.");
    185            }
    186            BarRenderer.defaultBarPainter = painter;
    187        }
    188
    189        /**
    190         * The default value for the initialisation of the shadowsVisible flag.
    191         */
    192        private static boolean defaultShadowsVisible = true;
    193
    194        /**
    195         * Returns the default value for the <code>shadowsVisible</code> flag.
    196         *
    197         * @return A boolean.
    198         *
    199         * @see #setDefaultShadowsVisible(boolean)
    200         *
    201         * @since 1.0.13
    202         */
    203        public static boolean getDefaultShadowsVisible() {
    204            return BarRenderer.defaultShadowsVisible;
    205        }
    206
    207        /**
    208         * Sets the default value for the shadows visible flag.
    209         *
    210         * @param visible  the new value for the default.
    211         *
    212         * @see #getDefaultShadowsVisible()
    213         *
    214         * @since 1.0.13
    215         */
    216        public static void setDefaultShadowsVisible(boolean visible) {
    217            BarRenderer.defaultShadowsVisible = visible;
    218        }
    219
    220        /** The margin between items (bars) within a category. */
    221        private double itemMargin;
    222
    223        /** A flag that controls whether or not bar outlines are drawn. */
    224        private boolean drawBarOutline;
    225
    226        /** The maximum bar width as a percentage of the available space. */
    227        private double maximumBarWidth;
    228
    229        /** The minimum bar length (in Java2D units). */
    230        private double minimumBarLength;
    231
    232        /**
    233         * An optional class used to transform gradient paint objects to fit each
    234         * bar.
    235         */
    236        private GradientPaintTransformer gradientPaintTransformer;
    237
    238        /**
    239         * The fallback position if a positive item label doesn't fit inside the
    240         * bar.
    241         */
    242        private ItemLabelPosition positiveItemLabelPositionFallback;
    243
    244        /**
    245         * The fallback position if a negative item label doesn't fit inside the
    246         * bar.
    247         */
    248        private ItemLabelPosition negativeItemLabelPositionFallback;
    249
    250        /** The upper clip (axis) value for the axis. */
    251        private double upperClip;
    252        // TODO:  this needs to move into the renderer state
    253
    254        /** The lower clip (axis) value for the axis. */
    255        private double lowerClip;
    256        // TODO:  this needs to move into the renderer state
    257
    258        /** The base value for the bars (defaults to 0.0). */
    259        private double base;
    260
    261        /**
    262         * A flag that controls whether the base value is included in the range
    263         * returned by the findRangeBounds() method.
    264         */
    265        private boolean includeBaseInRange;
    266
    267        /**
    268         * The bar painter (never <code>null</code>).
    269         *
    270         * @since 1.0.11
    271         */
    272        private BarPainter barPainter;
    273
    274        /**
    275         * The flag that controls whether or not shadows are drawn for the bars.
    276         *
    277         * @since 1.0.11
    278         */
    279        private boolean shadowsVisible;
    280
    281        /**
    282         * The shadow paint.
    283         *
    284         * @since 1.0.11
    285         */
    286        private transient Paint shadowPaint;
    287
    288        /**
    289         * The x-offset for the shadow effect.
    290         *
    291         * @since 1.0.11
    292         */
    293        private double shadowXOffset;
    294
    295        /**
    296         * The y-offset for the shadow effect.
    297         *
    298         * @since 1.0.11
    299         */
    300        private double shadowYOffset;
    301
    302        /**
    303         * Creates a new bar renderer with default settings.
    304         */
    305        public BarRenderer() {
    306            super();
    307            this.base = 0.0;
    308            this.includeBaseInRange = true;
    309            this.itemMargin = DEFAULT_ITEM_MARGIN;
    310            this.drawBarOutline = false;
    311            this.maximumBarWidth = 1.0;
    312                // 100 percent, so it will not apply unless changed
    313            this.positiveItemLabelPositionFallback = null;
    314            this.negativeItemLabelPositionFallback = null;
    315            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    316            this.minimumBarLength = 0.0;
    317            setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
    318            this.barPainter = getDefaultBarPainter();
    319            this.shadowsVisible = getDefaultShadowsVisible();
    320            this.shadowPaint = Color.gray;
    321            this.shadowXOffset = 4.0;
    322            this.shadowYOffset = 4.0;
    323        }
    324
    325        /**
    326         * Returns the base value for the bars.  The default value is
    327         * <code>0.0</code>.
    328         *
    329         * @return The base value for the bars.
    330         *
    331         * @see #setBase(double)
    332         */
    333        public double getBase() {
    334            return this.base;
    335        }
    336
    337        /**
    338         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    339         * to all registered listeners.
    340         *
    341         * @param base  the new base value.
    342         *
    343         * @see #getBase()
    344         */
    345        public void setBase(double base) {
    346            this.base = base;
    347            fireChangeEvent();
    348        }
    349
    350        /**
    351         * Returns the item margin as a percentage of the available space for all
    352         * bars.
    353         *
    354         * @return The margin percentage (where 0.10 is ten percent).
    355         *
    356         * @see #setItemMargin(double)
    357         */
    358        public double getItemMargin() {
    359            return this.itemMargin;
    360        }
    361
    362        /**
    363         * Sets the item margin and sends a {@link RendererChangeEvent} to all
    364         * registered listeners.  The value is expressed as a percentage of the
    365         * available width for plotting all the bars, with the resulting amount to
    366         * be distributed between all the bars evenly.
    367         *
    368         * @param percent  the margin (where 0.10 is ten percent).
    369         *
    370         * @see #getItemMargin()
    371         */
    372        public void setItemMargin(double percent) {
    373            this.itemMargin = percent;
    374            fireChangeEvent();
    375        }
    376
    377        /**
    378         * Returns a flag that controls whether or not bar outlines are drawn.
    379         *
    380         * @return A boolean.
    381         *
    382         * @see #setDrawBarOutline(boolean)
    383         */
    384        public boolean isDrawBarOutline() {
    385            return this.drawBarOutline;
    386        }
    387
    388        /**
    389         * Sets the flag that controls whether or not bar outlines are drawn and
    390         * sends a {@link RendererChangeEvent} to all registered listeners.
    391         *
    392         * @param draw  the flag.
    393         *
    394         * @see #isDrawBarOutline()
    395         */
    396        public void setDrawBarOutline(boolean draw) {
    397            this.drawBarOutline = draw;
    398            fireChangeEvent();
    399        }
    400
    401        /**
    402         * Returns the maximum bar width, as a percentage of the available drawing
    403         * space.
    404         *
    405         * @return The maximum bar width.
    406         *
    407         * @see #setMaximumBarWidth(double)
    408         */
    409        public double getMaximumBarWidth() {
    410            return this.maximumBarWidth;
    411        }
    412
    413        /**
    414         * Sets the maximum bar width, which is specified as a percentage of the
    415         * available space for all bars, and sends a {@link RendererChangeEvent} to
    416         * all registered listeners.
    417         *
    418         * @param percent  the percent (where 0.05 is five percent).
    419         *
    420         * @see #getMaximumBarWidth()
    421         */
    422        public void setMaximumBarWidth(double percent) {
    423            this.maximumBarWidth = percent;
    424            fireChangeEvent();
    425        }
    426
    427        /**
    428         * Returns the minimum bar length (in Java2D units).  The default value is
    429         * 0.0.
    430         *
    431         * @return The minimum bar length.
    432         *
    433         * @see #setMinimumBarLength(double)
    434         */
    435        public double getMinimumBarLength() {
    436            return this.minimumBarLength;
    437        }
    438
    439        /**
    440         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
    441         * all registered listeners.  The minimum bar length is specified in Java2D
    442         * units, and can be used to prevent bars that represent very small data
    443         * values from disappearing when drawn on the screen.  Typically you would
    444         * set this to (say) 0.5 or 1.0 Java 2D units.  Use this attribute with
    445         * caution, however, because setting it to a non-zero value will
    446         * artificially increase the length of bars representing small values,
    447         * which may misrepresent your data.
    448         *
    449         * @param min  the minimum bar length (in Java2D units, must be >= 0.0).
    450         *
    451         * @see #getMinimumBarLength()
    452         */
    453        public void setMinimumBarLength(double min) {
    454            if (min < 0.0) {
    455                throw new IllegalArgumentException("Requires 'min' >= 0.0");
    456            }
    457            this.minimumBarLength = min;
    458            fireChangeEvent();
    459        }
    460
    461        /**
    462         * Returns the gradient paint transformer (an object used to transform
    463         * gradient paint objects to fit each bar).
    464         *
    465         * @return A transformer (<code>null</code> possible).
    466         *
    467         * @see #setGradientPaintTransformer(GradientPaintTransformer)
    468         */
    469        public GradientPaintTransformer getGradientPaintTransformer() {
    470            return this.gradientPaintTransformer;
    471        }
    472
    473        /**
    474         * Sets the gradient paint transformer and sends a
    475         * {@link RendererChangeEvent} to all registered listeners.
    476         *
    477         * @param transformer  the transformer (<code>null</code> permitted).
    478         *
    479         * @see #getGradientPaintTransformer()
    480         */
    481        public void setGradientPaintTransformer(
    482                GradientPaintTransformer transformer) {
    483            this.gradientPaintTransformer = transformer;
    484            fireChangeEvent();
    485        }
    486
    487        /**
    488         * Returns the fallback position for positive item labels that don't fit
    489         * within a bar.
    490         *
    491         * @return The fallback position (<code>null</code> possible).
    492         *
    493         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    494         */
    495        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    496            return this.positiveItemLabelPositionFallback;
    497        }
    498
    499        /**
    500         * Sets the fallback position for positive item labels that don't fit
    501         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    502         * listeners.
    503         *
    504         * @param position  the position (<code>null</code> permitted).
    505         *
    506         * @see #getPositiveItemLabelPositionFallback()
    507         */
    508        public void setPositiveItemLabelPositionFallback(
    509                ItemLabelPosition position) {
    510            this.positiveItemLabelPositionFallback = position;
    511            fireChangeEvent();
    512        }
    513
    514        /**
    515         * Returns the fallback position for negative item labels that don't fit
    516         * within a bar.
    517         *
    518         * @return The fallback position (<code>null</code> possible).
    519         *
    520         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    521         */
    522        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    523            return this.negativeItemLabelPositionFallback;
    524        }
    525
    526        /**
    527         * Sets the fallback position for negative item labels that don't fit
    528         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    529         * listeners.
    530         *
    531         * @param position  the position (<code>null</code> permitted).
    532         *
    533         * @see #getNegativeItemLabelPositionFallback()
    534         */
    535        public void setNegativeItemLabelPositionFallback(
    536                ItemLabelPosition position) {
    537            this.negativeItemLabelPositionFallback = position;
    538            fireChangeEvent();
    539        }
    540
    541        /**
    542         * Returns the flag that controls whether or not the base value for the
    543         * bars is included in the range calculated by
    544         * {@link #findRangeBounds(CategoryDataset)}.
    545         *
    546         * @return <code>true</code> if the base is included in the range, and
    547         *         <code>false</code> otherwise.
    548         *
    549         * @since 1.0.1
    550         *
    551         * @see #setIncludeBaseInRange(boolean)
    552         */
    553        public boolean getIncludeBaseInRange() {
    554            return this.includeBaseInRange;
    555        }
    556
    557        /**
    558         * Sets the flag that controls whether or not the base value for the bars
    559         * is included in the range calculated by
    560         * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
    561         * a {@link RendererChangeEvent} is sent to all registered listeners.
    562         *
    563         * @param include  the new value for the flag.
    564         *
    565         * @since 1.0.1
    566         *
    567         * @see #getIncludeBaseInRange()
    568         */
    569        public void setIncludeBaseInRange(boolean include) {
    570            if (this.includeBaseInRange != include) {
    571                this.includeBaseInRange = include;
    572                fireChangeEvent();
    573            }
    574        }
    575
    576        /**
    577         * Returns the bar painter.
    578         *
    579         * @return The bar painter (never <code>null</code>).
    580         *
    581         * @see #setBarPainter(BarPainter)
    582         *
    583         * @since 1.0.11
    584         */
    585        public BarPainter getBarPainter() {
    586            return this.barPainter;
    587        }
    588
    589        /**
    590         * Sets the bar painter for this renderer and sends a
    591         * {@link RendererChangeEvent} to all registered listeners.
    592         *
    593         * @param painter  the painter (<code>null</code> not permitted).
    594         *
    595         * @see #getBarPainter()
    596         *
    597         * @since 1.0.11
    598         */
    599        public void setBarPainter(BarPainter painter) {
    600            if (painter == null) {
    601                throw new IllegalArgumentException("Null 'painter' argument.");
    602            }
    603            this.barPainter = painter;
    604            fireChangeEvent();
    605        }
    606
    607        /**
    608         * Returns the flag that controls whether or not shadows are drawn for
    609         * the bars.
    610         *
    611         * @return A boolean.
    612         *
    613         * @since 1.0.11
    614         */
    615        public boolean getShadowsVisible() {
    616            return this.shadowsVisible;
    617        }
    618
    619        /**
    620         * Sets the flag that controls whether or not shadows are
    621         * drawn by the renderer.
    622         *
    623         * @param visible  the new flag value.
    624         *
    625         * @since 1.0.11
    626         */
    627        public void setShadowVisible(boolean visible) {
    628            this.shadowsVisible = visible;
    629            fireChangeEvent();
    630        }
    631
    632        /**
    633         * Returns the shadow paint.
    634         *
    635         * @return The shadow paint.
    636         *
    637         * @see #setShadowPaint(Paint)
    638         *
    639         * @since 1.0.11
    640         */
    641        public Paint getShadowPaint() {
    642            return this.shadowPaint;
    643        }
    644
    645        /**
    646         * Sets the shadow paint and sends a {@link RendererChangeEvent} to all
    647         * registered listeners.
    648         *
    649         * @param paint  the paint (<code>null</code> not permitted).
    650         *
    651         * @see #getShadowPaint()
    652         *
    653         * @since 1.0.11
    654         */
    655        public void setShadowPaint(Paint paint) {
    656            if (paint == null) {
    657                throw new IllegalArgumentException("Null 'paint' argument.");
    658            }
    659            this.shadowPaint = paint;
    660            fireChangeEvent();
    661        }
    662
    663        /**
    664         * Returns the shadow x-offset.
    665         *
    666         * @return The shadow x-offset.
    667         *
    668         * @since 1.0.11
    669         */
    670        public double getShadowXOffset() {
    671            return this.shadowXOffset;
    672        }
    673
    674        /**
    675         * Sets the x-offset for the bar shadow and sends a
    676         * {@link RendererChangeEvent} to all registered listeners.
    677         *
    678         * @param offset  the offset.
    679         *
    680         * @since 1.0.11
    681         */
    682        public void setShadowXOffset(double offset) {
    683            this.shadowXOffset = offset;
    684            fireChangeEvent();
    685        }
    686
    687        /**
    688         * Returns the shadow y-offset.
    689         *
    690         * @return The shadow y-offset.
    691         *
    692         * @since 1.0.11
    693         */
    694        public double getShadowYOffset() {
    695            return this.shadowYOffset;
    696        }
    697
    698        /**
    699         * Sets the y-offset for the bar shadow and sends a
    700         * {@link RendererChangeEvent} to all registered listeners.
    701         *
    702         * @param offset  the offset.
    703         *
    704         * @since 1.0.11
    705         */
    706        public void setShadowYOffset(double offset) {
    707            this.shadowYOffset = offset;
    708            fireChangeEvent();
    709        }
    710
    711        /**
    712         * Returns the lower clip value.  This value is recalculated in the
    713         * initialise() method.
    714         *
    715         * @return The value.
    716         */
    717        public double getLowerClip() {
    718            // TODO:  this attribute should be transferred to the renderer state.
    719            return this.lowerClip;
    720        }
    721
    722        /**
    723         * Returns the upper clip value.  This value is recalculated in the
    724         * initialise() method.
    725         *
    726         * @return The value.
    727         */
    728        public double getUpperClip() {
    729            // TODO:  this attribute should be transferred to the renderer state.
    730            return this.upperClip;
    731        }
    732
    733        /**
    734         * Initialises the renderer and returns a state object that will be passed
    735         * to subsequent calls to the drawItem method.  This method gets called
    736         * once at the start of the process of drawing a chart.
    737         *
    738         * @param g2  the graphics device.
    739         * @param dataArea  the area in which the data is to be plotted.
    740         * @param plot  the plot.
    741         * @param rendererIndex  the renderer index.
    742         * @param info  collects chart rendering information for return to caller.
    743         *
    744         * @return The renderer state.
    745         */
    746        public CategoryItemRendererState initialise(Graphics2D g2,
    747                                                    Rectangle2D dataArea,
    748                                                    CategoryPlot plot,
    749                                                    int rendererIndex,
    750                                                    PlotRenderingInfo info) {
    751
    752            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
    753                    rendererIndex, info);
    754
    755            // get the clipping values...
    756            ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
    757            this.lowerClip = rangeAxis.getRange().getLowerBound();
    758            this.upperClip = rangeAxis.getRange().getUpperBound();
    759
    760            // calculate the bar width
    761            calculateBarWidth(plot, dataArea, rendererIndex, state);
    762
    763            return state;
    764
    765        }
    766
    767        /**
    768         * Calculates the bar width and stores it in the renderer state.
    769         *
    770         * @param plot  the plot.
    771         * @param dataArea  the data area.
    772         * @param rendererIndex  the renderer index.
    773         * @param state  the renderer state.
    774         */
    775        protected void calculateBarWidth(CategoryPlot plot,
    776                                         Rectangle2D dataArea,
    777                                         int rendererIndex,
    778                                         CategoryItemRendererState state) {
    779
    780            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    781            CategoryDataset dataset = plot.getDataset(rendererIndex);
    782            if (dataset != null) {
    783                int columns = dataset.getColumnCount();
    784                int rows = state.getVisibleSeriesCount() >= 0
    785                        ? state.getVisibleSeriesCount() : dataset.getRowCount();
    786                double space = 0.0;
    787                PlotOrientation orientation = plot.getOrientation();
    788                if (orientation == PlotOrientation.HORIZONTAL) {
    789                    space = dataArea.getHeight();
    790                }
    791                else if (orientation == PlotOrientation.VERTICAL) {
    792                    space = dataArea.getWidth();
    793                }
    794                double maxWidth = space * getMaximumBarWidth();
    795                double categoryMargin = 0.0;
    796                double currentItemMargin = 0.0;
    797                if (columns > 1) {
    798                    categoryMargin = domainAxis.getCategoryMargin();
    799                }
    800                if (rows > 1) {
    801                    currentItemMargin = getItemMargin();
    802                }
    803                double used = space * (1 - domainAxis.getLowerMargin()
    804                                         - domainAxis.getUpperMargin()
    805                                         - categoryMargin - currentItemMargin);
    806                if ((rows * columns) > 0) {
    807                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
    808                }
    809                else {
    810                    state.setBarWidth(Math.min(used, maxWidth));
    811                }
    812            }
    813        }
    814
    815        /**
    816         * Calculates the coordinate of the first "side" of a bar.  This will be
    817         * the minimum x-coordinate for a vertical bar, and the minimum
    818         * y-coordinate for a horizontal bar.
    819         *
    820         * @param plot  the plot.
    821         * @param orientation  the plot orientation.
    822         * @param dataArea  the data area.
    823         * @param domainAxis  the domain axis.
    824         * @param state  the renderer state (has the bar width precalculated).
    825         * @param row  the row index.
    826         * @param column  the column index.
    827         *
    828         * @return The coordinate.
    829         */
    830        protected double calculateBarW0(CategoryPlot plot,
    831                                        PlotOrientation orientation,
    832                                        Rectangle2D dataArea,
    833                                        CategoryAxis domainAxis,
    834                                        CategoryItemRendererState state,
    835                                        int row,
    836                                        int column) {
    837            // calculate bar width...
    838            double space = 0.0;
    839            if (orientation == PlotOrientation.HORIZONTAL) {
    840                space = dataArea.getHeight();
    841            }
    842            else {
    843                space = dataArea.getWidth();
    844            }
    845            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
    846                    dataArea, plot.getDomainAxisEdge());
    847            int seriesCount = state.getVisibleSeriesCount() >= 0
    848                    ? state.getVisibleSeriesCount() : getRowCount();
    849            int categoryCount = getColumnCount();
    850            if (seriesCount > 1) {
    851                double seriesGap = space * getItemMargin()
    852                                   / (categoryCount * (seriesCount - 1));
    853                double seriesW = calculateSeriesWidth(space, domainAxis,
    854                        categoryCount, seriesCount);
    855                barW0 = barW0 + row * (seriesW + seriesGap)
    856                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
    857            }
    858            else {
    859                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    860                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
    861                        / 2.0;
    862            }
    863            return barW0;
    864        }
    865
    866        /**
    867         * Calculates the coordinates for the length of a single bar.
    868         *
    869         * @param value  the value represented by the bar.
    870         *
    871         * @return The coordinates for each end of the bar (or <code>null</code> if
    872         *         the bar is not visible for the current axis range).
    873         */
    874        protected double[] calculateBarL0L1(double value) {
    875            double lclip = getLowerClip();
    876            double uclip = getUpperClip();
    877            double barLow = Math.min(this.base, value);
    878            double barHigh = Math.max(this.base, value);
    879            if (barHigh < lclip) {  // bar is not visible
    880                return null;
    881            }
    882            if (barLow > uclip) {   // bar is not visible
    883                return null;
    884            }
    885            barLow = Math.max(barLow, lclip);
    886            barHigh = Math.min(barHigh, uclip);
    887            return new double[] {barLow, barHigh};
    888        }
    889
    890        /**
    891         * Returns the range of values the renderer requires to display all the
    892         * items from the specified dataset.  This takes into account the range
    893         * of values in the dataset, plus the flag that determines whether or not
    894         * the base value for the bars should be included in the range.
    895         *
    896         * @param dataset  the dataset (<code>null</code> permitted).
    897         * @param includeInterval  include the interval if the dataset has one?
    898         *
    899         * @return The range (or <code>null</code> if the dataset is
    900         *         <code>null</code> or empty).
    901         */
    902        public Range findRangeBounds(CategoryDataset dataset,
    903                boolean includeInterval) {
    904            if (dataset == null) {
    905                return null;
    906            }
    907            Range result = super.findRangeBounds(dataset, includeInterval);
    908            if (result != null) {
    909                if (this.includeBaseInRange) {
    910                    result = Range.expandToInclude(result, this.base);
    911                }
    912            }
    913            return result;
    914        }
    915
    916        /**
    917         * Returns a legend item for a series.
    918         *
    919         * @param datasetIndex  the dataset index (zero-based).
    920         * @param series  the series index (zero-based).
    921         *
    922         * @return The legend item (possibly <code>null</code>).
    923         */
    924        public LegendItem getLegendItem(int datasetIndex, int series) {
    925
    926            CategoryPlot cp = getPlot();
    927            if (cp == null) {
    928                return null;
    929            }
    930
    931            // check that a legend item needs to be displayed...
    932            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
    933                return null;
    934            }
    935
    936            CategoryDataset dataset = cp.getDataset(datasetIndex);
    937            String label = getLegendItemLabelGenerator().generateLabel(dataset,
    938                    series);
    939            String description = label;
    940            String toolTipText = null;
    941            if (getLegendItemToolTipGenerator() != null) {
    942                toolTipText = getLegendItemToolTipGenerator().generateLabel(
    943                        dataset, series);
    944            }
    945            String urlText = null;
    946            if (getLegendItemURLGenerator() != null) {
    947                urlText = getLegendItemURLGenerator().generateLabel(dataset,
    948                        series);
    949            }
    950            Shape shape = lookupLegendShape(series);
    951            Paint paint = lookupSeriesPaint(series);
    952            Paint outlinePaint = lookupSeriesOutlinePaint(series);
    953            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    954
    955            LegendItem result = new LegendItem(label, description, toolTipText,
    956                    urlText, true, shape, true, paint, isDrawBarOutline(),
    957                    outlinePaint, outlineStroke, false, new Line2D.Float(),
    958                    new BasicStroke(1.0f), Color.black);
    959            result.setLabelFont(lookupLegendTextFont(series));
    960            Paint labelPaint = lookupLegendTextPaint(series);
    961            if (labelPaint != null) {
    962                result.setLabelPaint(labelPaint);
    963            }
    964            result.setDataset(dataset);
    965            result.setDatasetIndex(datasetIndex);
    966            result.setSeriesKey(dataset.getRowKey(series));
    967            result.setSeriesIndex(series);
    968            if (this.gradientPaintTransformer != null) {
    969                result.setFillPaintTransformer(this.gradientPaintTransformer);
    970            }
    971            return result;
    972        }
    973
    974        /**
    975         * Draws the bar for a single (series, category) data item.
    976         *
    977         * @param g2  the graphics device.
    978         * @param state  the renderer state.
    979         * @param dataArea  the data area.
    980         * @param plot  the plot.
    981         * @param domainAxis  the domain axis.
    982         * @param rangeAxis  the range axis.
    983         * @param dataset  the dataset.
    984         * @param row  the row index (zero-based).
    985         * @param column  the column index (zero-based).
    986         * @param pass  the pass index.
    987         */
    988        public void drawItem(Graphics2D g2,
    989                             CategoryItemRendererState state,
    990                             Rectangle2D dataArea,
    991                             CategoryPlot plot,
    992                             CategoryAxis domainAxis,
    993                             ValueAxis rangeAxis,
    994                             CategoryDataset dataset,
    995                             int row,
    996                             int column,
    997                             int pass) {
    998
    999            // nothing is drawn if the row index is not included in the list with
    1000            // the indices of the visible rows...
    1001            int visibleRow = state.getVisibleSeriesIndex(row);
    1002            if (visibleRow < 0) {
    1003                return;
    1004            }
    1005            // nothing is drawn for null values...
    1006            Number dataValue = dataset.getValue(row, column);
    1007            if (dataValue == null) {
    1008                return;
    1009            }
    1010
    1011            final double value = dataValue.doubleValue();
    1012            PlotOrientation orientation = plot.getOrientation();
    1013            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
    1014                    state, visibleRow, column);
    1015            double[] barL0L1 = calculateBarL0L1(value);
    1016            if (barL0L1 == null) {
    1017                return;  // the bar is not visible
    1018            }
    1019
    1020            RectangleEdge edge = plot.getRangeAxisEdge();
    1021            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
    1022            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
    1023
    1024            // in the following code, barL0 is (in Java2D coordinates) the LEFT
    1025            // end of the bar for a horizontal bar chart, and the TOP end of the
    1026            // bar for a vertical bar chart.  Whether this is the BASE of the bar
    1027            // or not depends also on (a) whether the data value is 'negative'
    1028            // relative to the base value and (b) whether or not the range axis is
    1029            // inverted.  This only matters if/when we apply the minimumBarLength
    1030            // attribute, because we should extend the non-base end of the bar
    1031            boolean positive = (value >= this.base);
    1032            boolean inverted = rangeAxis.isInverted();
    1033            double barL0 = Math.min(transL0, transL1);
    1034            double barLength = Math.abs(transL1 - transL0);
    1035            double barLengthAdj = 0.0;
    1036            if (barLength > 0.0 && barLength < getMinimumBarLength()) {
    1037                barLengthAdj = getMinimumBarLength() - barLength;
    1038            }
    1039            double barL0Adj = 0.0;
    1040            RectangleEdge barBase;
    1041            if (orientation == PlotOrientation.HORIZONTAL) {
    1042                if (positive && inverted || !positive && !inverted) {
    1043                    barL0Adj = barLengthAdj;
    1044                    barBase = RectangleEdge.RIGHT;
    1045                }
    1046                else {
    1047                    barBase = RectangleEdge.LEFT;
    1048                }
    1049            }
    1050            else {
    1051                if (positive && !inverted || !positive && inverted) {
    1052                    barL0Adj = barLengthAdj;
    1053                    barBase = RectangleEdge.BOTTOM;
    1054                }
    1055                else {
    1056                    barBase = RectangleEdge.TOP;
    1057                }
    1058            }
    1059
    1060            // draw the bar...
    1061            Rectangle2D bar = null;
    1062            if (orientation == PlotOrientation.HORIZONTAL) {
    1063                bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
    1064                        barLength + barLengthAdj, state.getBarWidth());
    1065            }
    1066            else {
    1067                bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
    1068                        state.getBarWidth(), barLength + barLengthAdj);
    1069            }
    1070            if (getShadowsVisible()) {
    1071                this.barPainter.paintBarShadow(g2, this, row, column, bar, barBase,
    1072                    true);
    1073            }
    1074            this.barPainter.paintBar(g2, this, row, column, bar, barBase);
    1075
    1076            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
    1077                    column);
    1078            if (generator != null && isItemLabelVisible(row, column)) {
    1079                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    1080                        (value < 0.0));
    1081            }
    1082
    1083            // submit the current data point as a crosshair candidate
    1084            int datasetIndex = plot.indexOf(dataset);
    1085            updateCrosshairValues(state.getCrosshairState(),
    1086                    dataset.getRowKey(row), dataset.getColumnKey(column), value,
    1087                    datasetIndex, barW0, barL0, orientation);
    1088
    1089            // add an item entity, if this information is being collected
    1090            EntityCollection entities = state.getEntityCollection();
    1091            if (entities != null) {
    1092                addItemEntity(entities, dataset, row, column, bar);
    1093            }
    1094
    1095        }
    1096
    1097        /**
    1098         * Calculates the available space for each series.
    1099         *
    1100         * @param space  the space along the entire axis (in Java2D units).
    1101         * @param axis  the category axis.
    1102         * @param categories  the number of categories.
    1103         * @param series  the number of series.
    1104         *
    1105         * @return The width of one series.
    1106         */
    1107        protected double calculateSeriesWidth(double space, CategoryAxis axis,
    1108                                              int categories, int series) {
    1109            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
    1110                                - axis.getUpperMargin();
    1111            if (categories > 1) {
    1112                factor = factor - axis.getCategoryMargin();
    1113            }
    1114            return (space * factor) / (categories * series);
    1115        }
    1116
    1117        /**
    1118         * Draws an item label.  This method is overridden so that the bar can be
    1119         * used to calculate the label anchor point.
    1120         *
    1121         * @param g2  the graphics device.
    1122         * @param data  the dataset.
    1123         * @param row  the row.
    1124         * @param column  the column.
    1125         * @param plot  the plot.
    1126         * @param generator  the label generator.
    1127         * @param bar  the bar.
    1128         * @param negative  a flag indicating a negative value.
    1129         */
    1130        protected void drawItemLabel(Graphics2D g2,
    1131                                     CategoryDataset data,
    1132                                     int row,
    1133                                     int column,
    1134                                     CategoryPlot plot,
    1135                                     CategoryItemLabelGenerator generator,
    1136                                     Rectangle2D bar,
    1137                                     boolean negative) {
    1138
    1139            String label = generator.generateLabel(data, row, column);
    1140            if (label == null) {
    1141                return;  // nothing to do
    1142            }
    1143
    1144            Font labelFont = getItemLabelFont(row, column);
    1145            g2.setFont(labelFont);
    1146            Paint paint = getItemLabelPaint(row, column);
    1147            g2.setPaint(paint);
    1148
    1149            // find out where to place the label...
    1150            ItemLabelPosition position = null;
    1151            if (!negative) {
    1152                position = getPositiveItemLabelPosition(row, column);
    1153            }
    1154            else {
    1155                position = getNegativeItemLabelPosition(row, column);
    1156            }
    1157
    1158            // work out the label anchor point...
    1159            Point2D anchorPoint = calculateLabelAnchorPoint(
    1160                    position.getItemLabelAnchor(), bar, plot.getOrientation());
    1161
    1162            if (isInternalAnchor(position.getItemLabelAnchor())) {
    1163                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
    1164                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    1165                        position.getTextAnchor(), position.getAngle(),
    1166                        position.getRotationAnchor());
    1167
    1168                if (bounds != null) {
    1169                    if (!bar.contains(bounds.getBounds2D())) {
    1170                        if (!negative) {
    1171                            position = getPositiveItemLabelPositionFallback();
    1172                        }
    1173                        else {
    1174                            position = getNegativeItemLabelPositionFallback();
    1175                        }
    1176                        if (position != null) {
    1177                            anchorPoint = calculateLabelAnchorPoint(
    1178                                    position.getItemLabelAnchor(), bar,
    1179                                    plot.getOrientation());
    1180                        }
    1181                    }
    1182                }
    1183
    1184            }
    1185
    1186            if (position != null) {
    1187                TextUtilities.drawRotatedString(label, g2,
    1188                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    1189                        position.getTextAnchor(), position.getAngle(),
    1190                        position.getRotationAnchor());
    1191            }
    1192        }
    1193
    1194        /**
    1195         * Calculates the item label anchor point.
    1196         *
    1197         * @param anchor  the anchor.
    1198         * @param bar  the bar.
    1199         * @param orientation  the plot orientation.
    1200         *
    1201         * @return The anchor point.
    1202         */
    1203        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    1204                                                  Rectangle2D bar,
    1205                                                  PlotOrientation orientation) {
    1206
    1207            Point2D result = null;
    1208            double offset = getItemLabelAnchorOffset();
    1209            double x0 = bar.getX() - offset;
    1210            double x1 = bar.getX();
    1211            double x2 = bar.getX() + offset;
    1212            double x3 = bar.getCenterX();
    1213            double x4 = bar.getMaxX() - offset;
    1214            double x5 = bar.getMaxX();
    1215            double x6 = bar.getMaxX() + offset;
    1216
    1217            double y0 = bar.getMaxY() + offset;
    1218            double y1 = bar.getMaxY();
    1219            double y2 = bar.getMaxY() - offset;
    1220            double y3 = bar.getCenterY();
    1221            double y4 = bar.getMinY() + offset;
    1222            double y5 = bar.getMinY();
    1223            double y6 = bar.getMinY() - offset;
    1224
    1225            if (anchor == ItemLabelAnchor.CENTER) {
    1226                result = new Point2D.Double(x3, y3);
    1227            }
    1228            else if (anchor == ItemLabelAnchor.INSIDE1) {
    1229                result = new Point2D.Double(x4, y4);
    1230            }
    1231            else if (anchor == ItemLabelAnchor.INSIDE2) {
    1232                result = new Point2D.Double(x4, y4);
    1233            }
    1234            else if (anchor == ItemLabelAnchor.INSIDE3) {
    1235                result = new Point2D.Double(x4, y3);
    1236            }
    1237            else if (anchor == ItemLabelAnchor.INSIDE4) {
    1238                result = new Point2D.Double(x4, y2);
    1239            }
    1240            else if (anchor == ItemLabelAnchor.INSIDE5) {
    1241                result = new Point2D.Double(x4, y2);
    1242            }
    1243            else if (anchor == ItemLabelAnchor.INSIDE6) {
    1244                result = new Point2D.Double(x3, y2);
    1245            }
    1246            else if (anchor == ItemLabelAnchor.INSIDE7) {
    1247                result = new Point2D.Double(x2, y2);
    1248            }
    1249            else if (anchor == ItemLabelAnchor.INSIDE8) {
    1250                result = new Point2D.Double(x2, y2);
    1251            }
    1252            else if (anchor == ItemLabelAnchor.INSIDE9) {
    1253                result = new Point2D.Double(x2, y3);
    1254            }
    1255            else if (anchor == ItemLabelAnchor.INSIDE10) {
    1256                result = new Point2D.Double(x2, y4);
    1257            }
    1258            else if (anchor == ItemLabelAnchor.INSIDE11) {
    1259                result = new Point2D.Double(x2, y4);
    1260            }
    1261            else if (anchor == ItemLabelAnchor.INSIDE12) {
    1262                result = new Point2D.Double(x3, y4);
    1263            }
    1264            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    1265                result = new Point2D.Double(x5, y6);
    1266            }
    1267            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    1268                result = new Point2D.Double(x6, y5);
    1269            }
    1270            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    1271                result = new Point2D.Double(x6, y3);
    1272            }
    1273            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    1274                result = new Point2D.Double(x6, y1);
    1275            }
    1276            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    1277                result = new Point2D.Double(x5, y0);
    1278            }
    1279            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    1280                result = new Point2D.Double(x3, y0);
    1281            }
    1282            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    1283                result = new Point2D.Double(x1, y0);
    1284            }
    1285            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    1286                result = new Point2D.Double(x0, y1);
    1287            }
    1288            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    1289                result = new Point2D.Double(x0, y3);
    1290            }
    1291            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    1292                result = new Point2D.Double(x0, y5);
    1293            }
    1294            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    1295                result = new Point2D.Double(x1, y6);
    1296            }
    1297            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    1298                result = new Point2D.Double(x3, y6);
    1299            }
    1300
    1301            return result;
    1302
    1303        }
    1304
    1305        /**
    1306         * Returns <code>true</code> if the specified anchor point is inside a bar.
    1307         *
    1308         * @param anchor  the anchor point.
    1309         *
    1310         * @return A boolean.
    1311         */
    1312        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    1313            return anchor == ItemLabelAnchor.CENTER
    1314                   || anchor == ItemLabelAnchor.INSIDE1
    1315                   || anchor == ItemLabelAnchor.INSIDE2
    1316                   || anchor == ItemLabelAnchor.INSIDE3
    1317                   || anchor == ItemLabelAnchor.INSIDE4
    1318                   || anchor == ItemLabelAnchor.INSIDE5
    1319                   || anchor == ItemLabelAnchor.INSIDE6
    1320                   || anchor == ItemLabelAnchor.INSIDE7
    1321                   || anchor == ItemLabelAnchor.INSIDE8
    1322                   || anchor == ItemLabelAnchor.INSIDE9
    1323                   || anchor == ItemLabelAnchor.INSIDE10
    1324                   || anchor == ItemLabelAnchor.INSIDE11
    1325                   || anchor == ItemLabelAnchor.INSIDE12;
    1326        }
    1327
    1328        /**
    1329         * Tests this instance for equality with an arbitrary object.
    1330         *
    1331         * @param obj  the object (<code>null</code> permitted).
    1332         *
    1333         * @return A boolean.
    1334         */
    1335        public boolean equals(Object obj) {
    1336            if (obj == this) {
    1337                return true;
    1338            }
    1339            if (!(obj instanceof BarRenderer)) {
    1340                return false;
    1341            }
    1342            BarRenderer that = (BarRenderer) obj;
    1343            if (this.base != that.base) {
    1344                return false;
    1345            }
    1346            if (this.itemMargin != that.itemMargin) {
    1347                return false;
    1348            }
    1349            if (this.drawBarOutline != that.drawBarOutline) {
    1350                return false;
    1351            }
    1352            if (this.maximumBarWidth != that.maximumBarWidth) {
    1353                return false;
    1354            }
    1355            if (this.minimumBarLength != that.minimumBarLength) {
    1356                return false;
    1357            }
    1358            if (!ObjectUtilities.equal(this.gradientPaintTransformer,
    1359                    that.gradientPaintTransformer)) {
    1360                return false;
    1361            }
    1362            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    1363                that.positiveItemLabelPositionFallback)) {
    1364                return false;
    1365            }
    1366            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    1367                that.negativeItemLabelPositionFallback)) {
    1368                return false;
    1369            }
    1370            if (!this.barPainter.equals(that.barPainter)) {
    1371                return false;
    1372            }
    1373            if (this.shadowsVisible != that.shadowsVisible) {
    1374                return false;
    1375            }
    1376            if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
    1377                return false;
    1378            }
    1379            if (this.shadowXOffset != that.shadowXOffset) {
    1380                return false;
    1381            }
    1382            if (this.shadowYOffset != that.shadowYOffset) {
    1383                return false;
    1384            }
    1385            return super.equals(obj);
    1386        }
    1387
    1388        /**
    1389         * Provides serialization support.
    1390         *
    1391         * @param stream  the output stream.
    1392         *
    1393         * @throws IOException  if there is an I/O error.
    1394         */
    1395        private void writeObject(ObjectOutputStream stream) throws IOException {
    1396            stream.defaultWriteObject();
    1397            SerialUtilities.writePaint(this.shadowPaint, stream);
    1398        }
    1399
    1400        /**
    1401         * Provides serialization support.
    1402         *
    1403         * @param stream  the input stream.
    1404         *
    1405         * @throws IOException  if there is an I/O error.
    1406         * @throws ClassNotFoundException  if there is a classpath problem.
    1407         */
    1408        private void readObject(ObjectInputStream stream)
    1409                throws IOException, ClassNotFoundException {
    1410            stream.defaultReadObject();
    1411            this.shadowPaint = SerialUtilities.readPaint(stream);
    1412        }
    1413
    1414    }