    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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-2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *
    035     * Changes
    036     * -------
    037     * 14-Mar-2002 : Version 1 (DG);
    038     * 23-May-2002 : Added tooltip generator to renderer (DG);
    039     * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
    040     * 25-Jun-2002 : Changed constructor to protected and removed redundant
    041     *               code (DG);
    042     * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
    043     *               clip values (DG);
    044     * 24-Sep-2002 : Added getLegendItem() method (DG);
    045     * 09-Oct-2002 : Modified constructor to include URL generator (DG);
    046     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    047     * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
    048     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
    049     * 25-Mar-2003 : Implemented Serializable (DG);
    050     * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
    051     * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
    052     * 12-Jun-2003 : Updates for item labels (DG);
    053     * 30-Jul-2003 : Modified entity constructor (CZ);
    054     * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
    055     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    056     * 07-Oct-2003 : Added renderer state (DG);
    057     * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
    058     *               methods (DG);
    059     * 28-Oct-2003 : Added support for gradient paint on bars (DG);
    060     * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
    061     * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
    062     *               overriding (DG);
    063     * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
    064     *               label generators.  Fixed equals() method (DG);
    065     * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
    066     * 05-Nov-2004 : Modified drawItem() signature (DG);
    067     * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
    068     * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
    069     * 18-May-2005 : Added configurable base value (DG);
    070     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
    071     * 01-Dec-2005 : Update legend item to use/not use outline (DG);
    072     * ------------: JFreeChart 1.0.x ---------------------------------------------
    073     * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
    074     * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
    075     * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
    076     *               bars) (DG);
    077     * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
    078     * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
    079     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    080     * 11-May-2007 : Check for visibility in getLegendItem() (DG);
    081     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    082     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    083     *
    084     */
    085
    086    package org.jfree.chart.renderer.category;
    087
    088    import java.awt.BasicStroke;
    089    import java.awt.Color;
    090    import java.awt.Font;
    091    import java.awt.GradientPaint;
    092    import java.awt.Graphics2D;
    093    import java.awt.Paint;
    094    import java.awt.Shape;
    095    import java.awt.Stroke;
    096    import java.awt.geom.Line2D;
    097    import java.awt.geom.Point2D;
    098    import java.awt.geom.Rectangle2D;
    099    import java.io.Serializable;
    100
    101    import org.jfree.chart.LegendItem;
    102    import org.jfree.chart.axis.CategoryAxis;
    103    import org.jfree.chart.axis.ValueAxis;
    104    import org.jfree.chart.entity.EntityCollection;
    105    import org.jfree.chart.event.RendererChangeEvent;
    106    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    107    import org.jfree.chart.labels.ItemLabelAnchor;
    108    import org.jfree.chart.labels.ItemLabelPosition;
    109    import org.jfree.chart.plot.CategoryPlot;
    110    import org.jfree.chart.plot.PlotOrientation;
    111    import org.jfree.chart.plot.PlotRenderingInfo;
    112    import org.jfree.data.Range;
    113    import org.jfree.data.category.CategoryDataset;
    114    import org.jfree.data.general.DatasetUtilities;
    115    import org.jfree.text.TextUtilities;
    116    import org.jfree.ui.GradientPaintTransformer;
    117    import org.jfree.ui.RectangleEdge;
    118    import org.jfree.ui.StandardGradientPaintTransformer;
    119    import org.jfree.util.ObjectUtilities;
    120    import org.jfree.util.PublicCloneable;
    121
    122    /**
    123     * A {@link CategoryItemRenderer} that draws individual data items as bars.
    124     */
    125    public class BarRenderer extends AbstractCategoryItemRenderer
    126                             implements Cloneable, PublicCloneable, Serializable {
    127
    128        /** For serialization. */
    129        private static final long serialVersionUID = 6000649414965887481L;
    130
    131        /** The default item margin percentage. */
    132        public static final double DEFAULT_ITEM_MARGIN = 0.20;
    133
    134        /**
    135         * Constant that controls the minimum width before a bar has an outline
    136         * drawn.
    137         */
    138        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
    139
    140        /** The margin between items (bars) within a category. */
    141        private double itemMargin;
    142
    143        /** A flag that controls whether or not bar outlines are drawn. */
    144        private boolean drawBarOutline;
    145
    146        /** The maximum bar width as a percentage of the available space. */
    147        private double maximumBarWidth;
    148
    149        /** The minimum bar length (in Java2D units). */
    150        private double minimumBarLength;
    151
    152        /**
    153         * An optional class used to transform gradient paint objects to fit each
    154         * bar.
    155         */
    156        private GradientPaintTransformer gradientPaintTransformer;
    157
    158        /**
    159         * The fallback position if a positive item label doesn't fit inside the
    160         * bar.
    161         */
    162        private ItemLabelPosition positiveItemLabelPositionFallback;
    163
    164        /**
    165         * The fallback position if a negative item label doesn't fit inside the
    166         * bar.
    167         */
    168        private ItemLabelPosition negativeItemLabelPositionFallback;
    169
    170        /** The upper clip (axis) value for the axis. */
    171        private double upperClip;
    172        // TODO:  this needs to move into the renderer state
    173
    174        /** The lower clip (axis) value for the axis. */
    175        private double lowerClip;
    176        // TODO:  this needs to move into the renderer state
    177
    178        /** The base value for the bars (defaults to 0.0). */
    179        private double base;
    180
    181        /**
    182         * A flag that controls whether the base value is included in the range
    183         * returned by the findRangeBounds() method.
    184         */
    185        private boolean includeBaseInRange;
    186
    187        /**
    188         * Creates a new bar renderer with default settings.
    189         */
    190        public BarRenderer() {
    191            super();
    192            this.base = 0.0;
    193            this.includeBaseInRange = true;
    194            this.itemMargin = DEFAULT_ITEM_MARGIN;
    195            this.drawBarOutline = false;
    196            this.maximumBarWidth = 1.0;
    197                // 100 percent, so it will not apply unless changed
    198            this.positiveItemLabelPositionFallback = null;
    199            this.negativeItemLabelPositionFallback = null;
    200            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    201            this.minimumBarLength = 0.0;
    202        }
    203
    204        /**
    205         * Returns the base value for the bars.  The default value is
    206         * <code>0.0</code>.
    207         *
    208         * @return The base value for the bars.
    209         *
    210         * @see #setBase(double)
    211         */
    212        public double getBase() {
    213            return this.base;
    214        }
    215
    216        /**
    217         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    218         * to all registered listeners.
    219         *
    220         * @param base  the new base value.
    221         *
    222         * @see #getBase()
    223         */
    224        public void setBase(double base) {
    225            this.base = base;
    226            fireChangeEvent();
    227        }
    228
    229        /**
    230         * Returns the item margin as a percentage of the available space for all
    231         * bars.
    232         *
    233         * @return The margin percentage (where 0.10 is ten percent).
    234         *
    235         * @see #setItemMargin(double)
    236         */
    237        public double getItemMargin() {
    238            return this.itemMargin;
    239        }
    240
    241        /**
    242         * Sets the item margin and sends a {@link RendererChangeEvent} to all
    243         * registered listeners.  The value is expressed as a percentage of the
    244         * available width for plotting all the bars, with the resulting amount to
    245         * be distributed between all the bars evenly.
    246         *
    247         * @param percent  the margin (where 0.10 is ten percent).
    248         *
    249         * @see #getItemMargin()
    250         */
    251        public void setItemMargin(double percent) {
    252            this.itemMargin = percent;
    253            fireChangeEvent();
    254        }
    255
    256        /**
    257         * Returns a flag that controls whether or not bar outlines are drawn.
    258         *
    259         * @return A boolean.
    260         *
    261         * @see #setDrawBarOutline(boolean)
    262         */
    263        public boolean isDrawBarOutline() {
    264            return this.drawBarOutline;
    265        }
    266
    267        /**
    268         * Sets the flag that controls whether or not bar outlines are drawn and
    269         * sends a {@link RendererChangeEvent} to all registered listeners.
    270         *
    271         * @param draw  the flag.
    272         *
    273         * @see #isDrawBarOutline()
    274         */
    275        public void setDrawBarOutline(boolean draw) {
    276            this.drawBarOutline = draw;
    277            fireChangeEvent();
    278        }
    279
    280        /**
    281         * Returns the maximum bar width, as a percentage of the available drawing
    282         * space.
    283         *
    284         * @return The maximum bar width.
    285         *
    286         * @see #setMaximumBarWidth(double)
    287         */
    288        public double getMaximumBarWidth() {
    289            return this.maximumBarWidth;
    290        }
    291
    292        /**
    293         * Sets the maximum bar width, which is specified as a percentage of the
    294         * available space for all bars, and sends a {@link RendererChangeEvent} to
    295         * all registered listeners.
    296         *
    297         * @param percent  the percent (where 0.05 is five percent).
    298         *
    299         * @see #getMaximumBarWidth()
    300         */
    301        public void setMaximumBarWidth(double percent) {
    302            this.maximumBarWidth = percent;
    303            fireChangeEvent();
    304        }
    305
    306        /**
    307         * Returns the minimum bar length (in Java2D units).
    308         *
    309         * @return The minimum bar length.
    310         *
    311         * @see #setMinimumBarLength(double)
    312         */
    313        public double getMinimumBarLength() {
    314            return this.minimumBarLength;
    315        }
    316
    317        /**
    318         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
    319         * all registered listeners.  The minimum bar length is specified in Java2D
    320         * units, and can be used to prevent bars that represent very small data
    321         * values from disappearing when drawn on the screen.
    322         *
    323         * @param min  the minimum bar length (in Java2D units).
    324         *
    325         * @see #getMinimumBarLength()
    326         */
    327        public void setMinimumBarLength(double min) {
    328            this.minimumBarLength = min;
    329            fireChangeEvent();
    330        }
    331
    332        /**
    333         * Returns the gradient paint transformer (an object used to transform
    334         * gradient paint objects to fit each bar).
    335         *
    336         * @return A transformer (<code>null</code> possible).
    337         *
    338         * @see #setGradientPaintTransformer(GradientPaintTransformer)
    339         */
    340        public GradientPaintTransformer getGradientPaintTransformer() {
    341            return this.gradientPaintTransformer;
    342        }
    343
    344        /**
    345         * Sets the gradient paint transformer and sends a
    346         * {@link RendererChangeEvent} to all registered listeners.
    347         *
    348         * @param transformer  the transformer (<code>null</code> permitted).
    349         *
    350         * @see #getGradientPaintTransformer()
    351         */
    352        public void setGradientPaintTransformer(
    353                GradientPaintTransformer transformer) {
    354            this.gradientPaintTransformer = transformer;
    355            fireChangeEvent();
    356        }
    357
    358        /**
    359         * Returns the fallback position for positive item labels that don't fit
    360         * within a bar.
    361         *
    362         * @return The fallback position (<code>null</code> possible).
    363         *
    364         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    365         */
    366        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    367            return this.positiveItemLabelPositionFallback;
    368        }
    369
    370        /**
    371         * Sets the fallback position for positive item labels that don't fit
    372         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    373         * listeners.
    374         *
    375         * @param position  the position (<code>null</code> permitted).
    376         *
    377         * @see #getPositiveItemLabelPositionFallback()
    378         */
    379        public void setPositiveItemLabelPositionFallback(
    380                ItemLabelPosition position) {
    381            this.positiveItemLabelPositionFallback = position;
    382            fireChangeEvent();
    383        }
    384
    385        /**
    386         * Returns the fallback position for negative item labels that don't fit
    387         * within a bar.
    388         *
    389         * @return The fallback position (<code>null</code> possible).
    390         *
    391         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    392         */
    393        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    394            return this.negativeItemLabelPositionFallback;
    395        }
    396
    397        /**
    398         * Sets the fallback position for negative item labels that don't fit
    399         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    400         * listeners.
    401         *
    402         * @param position  the position (<code>null</code> permitted).
    403         *
    404         * @see #getNegativeItemLabelPositionFallback()
    405         */
    406        public void setNegativeItemLabelPositionFallback(
    407                ItemLabelPosition position) {
    408            this.negativeItemLabelPositionFallback = position;
    409            fireChangeEvent();
    410        }
    411
    412        /**
    413         * Returns the flag that controls whether or not the base value for the
    414         * bars is included in the range calculated by
    415         * {@link #findRangeBounds(CategoryDataset)}.
    416         *
    417         * @return <code>true</code> if the base is included in the range, and
    418         *         <code>false</code> otherwise.
    419         *
    420         * @since 1.0.1
    421         *
    422         * @see #setIncludeBaseInRange(boolean)
    423         */
    424        public boolean getIncludeBaseInRange() {
    425            return this.includeBaseInRange;
    426        }
    427
    428        /**
    429         * Sets the flag that controls whether or not the base value for the bars
    430         * is included in the range calculated by
    431         * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
    432         * a {@link RendererChangeEvent} is sent to all registered listeners.
    433         *
    434         * @param include  the new value for the flag.
    435         *
    436         * @since 1.0.1
    437         *
    438         * @see #getIncludeBaseInRange()
    439         */
    440        public void setIncludeBaseInRange(boolean include) {
    441            if (this.includeBaseInRange != include) {
    442                this.includeBaseInRange = include;
    443                fireChangeEvent();
    444            }
    445        }
    446
    447        /**
    448         * Returns the lower clip value.  This value is recalculated in the
    449         * initialise() method.
    450         *
    451         * @return The value.
    452         */
    453        public double getLowerClip() {
    454            // TODO:  this attribute should be transferred to the renderer state.
    455            return this.lowerClip;
    456        }
    457
    458        /**
    459         * Returns the upper clip value.  This value is recalculated in the
    460         * initialise() method.
    461         *
    462         * @return The value.
    463         */
    464        public double getUpperClip() {
    465            // TODO:  this attribute should be transferred to the renderer state.
    466            return this.upperClip;
    467        }
    468
    469        /**
    470         * Initialises the renderer and returns a state object that will be passed
    471         * to subsequent calls to the drawItem method.  This method gets called
    472         * once at the start of the process of drawing a chart.
    473         *
    474         * @param g2  the graphics device.
    475         * @param dataArea  the area in which the data is to be plotted.
    476         * @param plot  the plot.
    477         * @param rendererIndex  the renderer index.
    478         * @param info  collects chart rendering information for return to caller.
    479         *
    480         * @return The renderer state.
    481         */
    482        public CategoryItemRendererState initialise(Graphics2D g2,
    483                                                    Rectangle2D dataArea,
    484                                                    CategoryPlot plot,
    485                                                    int rendererIndex,
    486                                                    PlotRenderingInfo info) {
    487
    488            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
    489                    rendererIndex, info);
    490
    491            // get the clipping values...
    492            ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
    493            this.lowerClip = rangeAxis.getRange().getLowerBound();
    494            this.upperClip = rangeAxis.getRange().getUpperBound();
    495
    496            // calculate the bar width
    497            calculateBarWidth(plot, dataArea, rendererIndex, state);
    498
    499            return state;
    500
    501        }
    502
    503        /**
    504         * Calculates the bar width and stores it in the renderer state.
    505         *
    506         * @param plot  the plot.
    507         * @param dataArea  the data area.
    508         * @param rendererIndex  the renderer index.
    509         * @param state  the renderer state.
    510         */
    511        protected void calculateBarWidth(CategoryPlot plot,
    512                                         Rectangle2D dataArea,
    513                                         int rendererIndex,
    514                                         CategoryItemRendererState state) {
    515
    516            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    517            CategoryDataset dataset = plot.getDataset(rendererIndex);
    518            if (dataset != null) {
    519                int columns = dataset.getColumnCount();
    520                int rows = dataset.getRowCount();
    521                double space = 0.0;
    522                PlotOrientation orientation = plot.getOrientation();
    523                if (orientation == PlotOrientation.HORIZONTAL) {
    524                    space = dataArea.getHeight();
    525                }
    526                else if (orientation == PlotOrientation.VERTICAL) {
    527                    space = dataArea.getWidth();
    528                }
    529                double maxWidth = space * getMaximumBarWidth();
    530                double categoryMargin = 0.0;
    531                double currentItemMargin = 0.0;
    532                if (columns > 1) {
    533                    categoryMargin = domainAxis.getCategoryMargin();
    534                }
    535                if (rows > 1) {
    536                    currentItemMargin = getItemMargin();
    537                }
    538                double used = space * (1 - domainAxis.getLowerMargin()
    539                                         - domainAxis.getUpperMargin()
    540                                         - categoryMargin - currentItemMargin);
    541                if ((rows * columns) > 0) {
    542                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
    543                }
    544                else {
    545                    state.setBarWidth(Math.min(used, maxWidth));
    546                }
    547            }
    548        }
    549
    550        /**
    551         * Calculates the coordinate of the first "side" of a bar.  This will be
    552         * the minimum x-coordinate for a vertical bar, and the minimum
    553         * y-coordinate for a horizontal bar.
    554         *
    555         * @param plot  the plot.
    556         * @param orientation  the plot orientation.
    557         * @param dataArea  the data area.
    558         * @param domainAxis  the domain axis.
    559         * @param state  the renderer state (has the bar width precalculated).
    560         * @param row  the row index.
    561         * @param column  the column index.
    562         *
    563         * @return The coordinate.
    564         */
    565        protected double calculateBarW0(CategoryPlot plot,
    566                                        PlotOrientation orientation,
    567                                        Rectangle2D dataArea,
    568                                        CategoryAxis domainAxis,
    569                                        CategoryItemRendererState state,
    570                                        int row,
    571                                        int column) {
    572            // calculate bar width...
    573            double space = 0.0;
    574            if (orientation == PlotOrientation.HORIZONTAL) {
    575                space = dataArea.getHeight();
    576            }
    577            else {
    578                space = dataArea.getWidth();
    579            }
    580            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
    581                    dataArea, plot.getDomainAxisEdge());
    582            int seriesCount = getRowCount();
    583            int categoryCount = getColumnCount();
    584            if (seriesCount > 1) {
    585                double seriesGap = space * getItemMargin()
    586                                   / (categoryCount * (seriesCount - 1));
    587                double seriesW = calculateSeriesWidth(space, domainAxis,
    588                        categoryCount, seriesCount);
    589                barW0 = barW0 + row * (seriesW + seriesGap)
    590                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
    591            }
    592            else {
    593                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    594                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
    595                        / 2.0;
    596            }
    597            return barW0;
    598        }
    599
    600        /**
    601         * Calculates the coordinates for the length of a single bar.
    602         *
    603         * @param value  the value represented by the bar.
    604         *
    605         * @return The coordinates for each end of the bar (or <code>null</code> if
    606         *         the bar is not visible for the current axis range).
    607         */
    608        protected double[] calculateBarL0L1(double value) {
    609            double lclip = getLowerClip();
    610            double uclip = getUpperClip();
    611            double barLow = Math.min(this.base, value);
    612            double barHigh = Math.max(this.base, value);
    613            if (barHigh < lclip) {  // bar is not visible
    614                return null;
    615            }
    616            if (barLow > uclip) {   // bar is not visible
    617                return null;
    618            }
    619            barLow = Math.max(barLow, lclip);
    620            barHigh = Math.min(barHigh, uclip);
    621            return new double[] {barLow, barHigh};
    622        }
    623
    624        /**
    625         * Returns the range of values the renderer requires to display all the
    626         * items from the specified dataset.  This takes into account the range
    627         * of values in the dataset, plus the flag that determines whether or not
    628         * the base value for the bars should be included in the range.
    629         *
    630         * @param dataset  the dataset (<code>null</code> permitted).
    631         *
    632         * @return The range (or <code>null</code> if the dataset is
    633         *         <code>null</code> or empty).
    634         */
    635        public Range findRangeBounds(CategoryDataset dataset) {
    636            Range result = DatasetUtilities.findRangeBounds(dataset);
    637            if (result != null) {
    638                if (this.includeBaseInRange) {
    639                    result = Range.expandToInclude(result, this.base);
    640                }
    641            }
    642            return result;
    643        }
    644
    645        /**
    646         * Returns a legend item for a series.
    647         *
    648         * @param datasetIndex  the dataset index (zero-based).
    649         * @param series  the series index (zero-based).
    650         *
    651         * @return The legend item (possibly <code>null</code>).
    652         */
    653        public LegendItem getLegendItem(int datasetIndex, int series) {
    654
    655            CategoryPlot cp = getPlot();
    656            if (cp == null) {
    657                return null;
    658            }
    659
    660            // check that a legend item needs to be displayed...
    661            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
    662                return null;
    663            }
    664
    665            CategoryDataset dataset = cp.getDataset(datasetIndex);
    666            String label = getLegendItemLabelGenerator().generateLabel(dataset,
    667                    series);
    668            String description = label;
    669            String toolTipText = null;
    670            if (getLegendItemToolTipGenerator() != null) {
    671                toolTipText = getLegendItemToolTipGenerator().generateLabel(
    672                        dataset, series);
    673            }
    674            String urlText = null;
    675            if (getLegendItemURLGenerator() != null) {
    676                urlText = getLegendItemURLGenerator().generateLabel(dataset,
    677                        series);
    678            }
    679            Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
    680            Paint paint = lookupSeriesPaint(series);
    681            Paint outlinePaint = lookupSeriesOutlinePaint(series);
    682            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    683
    684            LegendItem result = new LegendItem(label, description, toolTipText,
    685                    urlText, true, shape, true, paint, isDrawBarOutline(),
    686                    outlinePaint, outlineStroke, false, new Line2D.Float(),
    687                    new BasicStroke(1.0f), Color.black);
    688            result.setDataset(dataset);
    689            result.setDatasetIndex(datasetIndex);
    690            result.setSeriesKey(dataset.getRowKey(series));
    691            result.setSeriesIndex(series);
    692            if (this.gradientPaintTransformer != null) {
    693                result.setFillPaintTransformer(this.gradientPaintTransformer);
    694            }
    695            return result;
    696        }
    697
    698        /**
    699         * Draws the bar for a single (series, category) data item.
    700         *
    701         * @param g2  the graphics device.
    702         * @param state  the renderer state.
    703         * @param dataArea  the data area.
    704         * @param plot  the plot.
    705         * @param domainAxis  the domain axis.
    706         * @param rangeAxis  the range axis.
    707         * @param dataset  the dataset.
    708         * @param row  the row index (zero-based).
    709         * @param column  the column index (zero-based).
    710         * @param pass  the pass index.
    711         */
    712        public void drawItem(Graphics2D g2,
    713                             CategoryItemRendererState state,
    714                             Rectangle2D dataArea,
    715                             CategoryPlot plot,
    716                             CategoryAxis domainAxis,
    717                             ValueAxis rangeAxis,
    718                             CategoryDataset dataset,
    719                             int row,
    720                             int column,
    721                             int pass) {
    722
    723            // nothing is drawn for null values...
    724            Number dataValue = dataset.getValue(row, column);
    725            if (dataValue == null) {
    726                return;
    727            }
    728
    729            double value = dataValue.doubleValue();
    730
    731            PlotOrientation orientation = plot.getOrientation();
    732            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
    733                    state, row, column);
    734            double[] barL0L1 = calculateBarL0L1(value);
    735            if (barL0L1 == null) {
    736                return;  // the bar is not visible
    737            }
    738
    739            RectangleEdge edge = plot.getRangeAxisEdge();
    740            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
    741            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
    742            double barL0 = Math.min(transL0, transL1);
    743            double barLength = Math.max(Math.abs(transL1 - transL0),
    744                    getMinimumBarLength());
    745
    746            // draw the bar...
    747            Rectangle2D bar = null;
    748            if (orientation == PlotOrientation.HORIZONTAL) {
    749                bar = new Rectangle2D.Double(barL0, barW0, barLength,
    750                        state.getBarWidth());
    751            }
    752            else {
    753                bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(),
    754                        barLength);
    755            }
    756            Paint itemPaint = getItemPaint(row, column);
    757            GradientPaintTransformer t = getGradientPaintTransformer();
    758            if (t != null && itemPaint instanceof GradientPaint) {
    759                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    760            }
    761            g2.setPaint(itemPaint);
    762            g2.fill(bar);
    763
    764            // draw the outline...
    765            if (isDrawBarOutline()
    766                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    767                Stroke stroke = getItemOutlineStroke(row, column);
    768                Paint paint = getItemOutlinePaint(row, column);
    769                if (stroke != null && paint != null) {
    770                    g2.setStroke(stroke);
    771                    g2.setPaint(paint);
    772                    g2.draw(bar);
    773                }
    774            }
    775
    776            CategoryItemLabelGenerator generator
    777                = getItemLabelGenerator(row, column);
    778            if (generator != null && isItemLabelVisible(row, column)) {
    779                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    780                        (value < 0.0));
    781            }
    782
    783            // add an item entity, if this information is being collected
    784            EntityCollection entities = state.getEntityCollection();
    785            if (entities != null) {
    786                addItemEntity(entities, dataset, row, column, bar);
    787            }
    788
    789        }
    790
    791        /**
    792         * Calculates the available space for each series.
    793         *
    794         * @param space  the space along the entire axis (in Java2D units).
    795         * @param axis  the category axis.
    796         * @param categories  the number of categories.
    797         * @param series  the number of series.
    798         *
    799         * @return The width of one series.
    800         */
    801        protected double calculateSeriesWidth(double space, CategoryAxis axis,
    802                                              int categories, int series) {
    803            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
    804                                - axis.getUpperMargin();
    805            if (categories > 1) {
    806                factor = factor - axis.getCategoryMargin();
    807            }
    808            return (space * factor) / (categories * series);
    809        }
    810
    811        /**
    812         * Draws an item label.  This method is overridden so that the bar can be
    813         * used to calculate the label anchor point.
    814         *
    815         * @param g2  the graphics device.
    816         * @param data  the dataset.
    817         * @param row  the row.
    818         * @param column  the column.
    819         * @param plot  the plot.
    820         * @param generator  the label generator.
    821         * @param bar  the bar.
    822         * @param negative  a flag indicating a negative value.
    823         */
    824        protected void drawItemLabel(Graphics2D g2,
    825                                     CategoryDataset data,
    826                                     int row,
    827                                     int column,
    828                                     CategoryPlot plot,
    829                                     CategoryItemLabelGenerator generator,
    830                                     Rectangle2D bar,
    831                                     boolean negative) {
    832
    833            String label = generator.generateLabel(data, row, column);
    834            if (label == null) {
    835                return;  // nothing to do
    836            }
    837
    838            Font labelFont = getItemLabelFont(row, column);
    839            g2.setFont(labelFont);
    840            Paint paint = getItemLabelPaint(row, column);
    841            g2.setPaint(paint);
    842
    843            // find out where to place the label...
    844            ItemLabelPosition position = null;
    845            if (!negative) {
    846                position = getPositiveItemLabelPosition(row, column);
    847            }
    848            else {
    849                position = getNegativeItemLabelPosition(row, column);
    850            }
    851
    852            // work out the label anchor point...
    853            Point2D anchorPoint = calculateLabelAnchorPoint(
    854                    position.getItemLabelAnchor(), bar, plot.getOrientation());
    855
    856            if (isInternalAnchor(position.getItemLabelAnchor())) {
    857                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
    858                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    859                        position.getTextAnchor(), position.getAngle(),
    860                        position.getRotationAnchor());
    861
    862                if (bounds != null) {
    863                    if (!bar.contains(bounds.getBounds2D())) {
    864                        if (!negative) {
    865                            position = getPositiveItemLabelPositionFallback();
    866                        }
    867                        else {
    868                            position = getNegativeItemLabelPositionFallback();
    869                        }
    870                        if (position != null) {
    871                            anchorPoint = calculateLabelAnchorPoint(
    872                                    position.getItemLabelAnchor(), bar,
    873                                    plot.getOrientation());
    874                        }
    875                    }
    876                }
    877
    878            }
    879
    880            if (position != null) {
    881                TextUtilities.drawRotatedString(label, g2,
    882                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    883                        position.getTextAnchor(), position.getAngle(),
    884                        position.getRotationAnchor());
    885            }
    886        }
    887
    888        /**
    889         * Calculates the item label anchor point.
    890         *
    891         * @param anchor  the anchor.
    892         * @param bar  the bar.
    893         * @param orientation  the plot orientation.
    894         *
    895         * @return The anchor point.
    896         */
    897        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    898                                                  Rectangle2D bar,
    899                                                  PlotOrientation orientation) {
    900
    901            Point2D result = null;
    902            double offset = getItemLabelAnchorOffset();
    903            double x0 = bar.getX() - offset;
    904            double x1 = bar.getX();
    905            double x2 = bar.getX() + offset;
    906            double x3 = bar.getCenterX();
    907            double x4 = bar.getMaxX() - offset;
    908            double x5 = bar.getMaxX();
    909            double x6 = bar.getMaxX() + offset;
    910
    911            double y0 = bar.getMaxY() + offset;
    912            double y1 = bar.getMaxY();
    913            double y2 = bar.getMaxY() - offset;
    914            double y3 = bar.getCenterY();
    915            double y4 = bar.getMinY() + offset;
    916            double y5 = bar.getMinY();
    917            double y6 = bar.getMinY() - offset;
    918
    919            if (anchor == ItemLabelAnchor.CENTER) {
    920                result = new Point2D.Double(x3, y3);
    921            }
    922            else if (anchor == ItemLabelAnchor.INSIDE1) {
    923                result = new Point2D.Double(x4, y4);
    924            }
    925            else if (anchor == ItemLabelAnchor.INSIDE2) {
    926                result = new Point2D.Double(x4, y4);
    927            }
    928            else if (anchor == ItemLabelAnchor.INSIDE3) {
    929                result = new Point2D.Double(x4, y3);
    930            }
    931            else if (anchor == ItemLabelAnchor.INSIDE4) {
    932                result = new Point2D.Double(x4, y2);
    933            }
    934            else if (anchor == ItemLabelAnchor.INSIDE5) {
    935                result = new Point2D.Double(x4, y2);
    936            }
    937            else if (anchor == ItemLabelAnchor.INSIDE6) {
    938                result = new Point2D.Double(x3, y2);
    939            }
    940            else if (anchor == ItemLabelAnchor.INSIDE7) {
    941                result = new Point2D.Double(x2, y2);
    942            }
    943            else if (anchor == ItemLabelAnchor.INSIDE8) {
    944                result = new Point2D.Double(x2, y2);
    945            }
    946            else if (anchor == ItemLabelAnchor.INSIDE9) {
    947                result = new Point2D.Double(x2, y3);
    948            }
    949            else if (anchor == ItemLabelAnchor.INSIDE10) {
    950                result = new Point2D.Double(x2, y4);
    951            }
    952            else if (anchor == ItemLabelAnchor.INSIDE11) {
    953                result = new Point2D.Double(x2, y4);
    954            }
    955            else if (anchor == ItemLabelAnchor.INSIDE12) {
    956                result = new Point2D.Double(x3, y4);
    957            }
    958            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    959                result = new Point2D.Double(x5, y6);
    960            }
    961            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    962                result = new Point2D.Double(x6, y5);
    963            }
    964            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    965                result = new Point2D.Double(x6, y3);
    966            }
    967            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    968                result = new Point2D.Double(x6, y1);
    969            }
    970            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    971                result = new Point2D.Double(x5, y0);
    972            }
    973            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    974                result = new Point2D.Double(x3, y0);
    975            }
    976            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    977                result = new Point2D.Double(x1, y0);
    978            }
    979            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    980                result = new Point2D.Double(x0, y1);
    981            }
    982            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    983                result = new Point2D.Double(x0, y3);
    984            }
    985            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    986                result = new Point2D.Double(x0, y5);
    987            }
    988            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    989                result = new Point2D.Double(x1, y6);
    990            }
    991            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    992                result = new Point2D.Double(x3, y6);
    993            }
    994
    995            return result;
    996
    997        }
    998
    999        /**
    1000         * Returns <code>true</code> if the specified anchor point is inside a bar.
    1001         *
    1002         * @param anchor  the anchor point.
    1003         *
    1004         * @return A boolean.
    1005         */
    1006        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    1007            return anchor == ItemLabelAnchor.CENTER
    1008                   || anchor == ItemLabelAnchor.INSIDE1
    1009                   || anchor == ItemLabelAnchor.INSIDE2
    1010                   || anchor == ItemLabelAnchor.INSIDE3
    1011                   || anchor == ItemLabelAnchor.INSIDE4
    1012                   || anchor == ItemLabelAnchor.INSIDE5
    1013                   || anchor == ItemLabelAnchor.INSIDE6
    1014                   || anchor == ItemLabelAnchor.INSIDE7
    1015                   || anchor == ItemLabelAnchor.INSIDE8
    1016                   || anchor == ItemLabelAnchor.INSIDE9
    1017                   || anchor == ItemLabelAnchor.INSIDE10
    1018                   || anchor == ItemLabelAnchor.INSIDE11
    1019                   || anchor == ItemLabelAnchor.INSIDE12;
    1020        }
    1021
    1022        /**
    1023         * Tests this instance for equality with an arbitrary object.
    1024         *
    1025         * @param obj  the object (<code>null</code> permitted).
    1026         *
    1027         * @return A boolean.
    1028         */
    1029        public boolean equals(Object obj) {
    1030
    1031            if (obj == this) {
    1032                return true;
    1033            }
    1034            if (!(obj instanceof BarRenderer)) {
    1035                return false;
    1036            }
    1037            if (!super.equals(obj)) {
    1038                return false;
    1039            }
    1040            BarRenderer that = (BarRenderer) obj;
    1041            if (this.base != that.base) {
    1042                return false;
    1043            }
    1044            if (this.itemMargin != that.itemMargin) {
    1045                return false;
    1046            }
    1047            if (this.drawBarOutline != that.drawBarOutline) {
    1048                return false;
    1049            }
    1050            if (this.maximumBarWidth != that.maximumBarWidth) {
    1051                return false;
    1052            }
    1053            if (this.minimumBarLength != that.minimumBarLength) {
    1054                return false;
    1055            }
    1056            if (!ObjectUtilities.equal(this.gradientPaintTransformer,
    1057                    that.gradientPaintTransformer)) {
    1058                return false;
    1059            }
    1060            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    1061                that.positiveItemLabelPositionFallback)) {
    1062                return false;
    1063            }
    1064            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    1065                that.negativeItemLabelPositionFallback)) {
    1066                return false;
    1067            }
    1068            return true;
    1069
    1070        }
    1071
    1072    }