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