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