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