    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     * XYBarRenderer.java
    029     * ------------------
    030     * (C) Copyright 2001-2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Richard Atkinson;
    034     *                   Christian W. Zuckschwerdt;
    035     *                   Bill Kelemen;
    036     *
    037     * $Id: XYBarRenderer.java,v 1.14.2.12 2007/03/05 15:11:44 mungady Exp $
    038     *
    039     * Changes
    040     * -------
    041     * 13-Dec-2001 : Version 1, makes VerticalXYBarPlot class redundant (DG);
    042     * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
    043     * 09-Apr-2002 : Removed the translated zero from the drawItem method. Override
    044     *               the initialise() method to calculate it (DG);
    045     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
    046     * 25-Jun-2002 : Removed redundant import (DG);
    047     * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
    048     *               image maps (RA);
    049     * 25-Mar-2003 : Implemented Serializable (DG);
    050     * 01-May-2003 : Modified drawItem() method signature (DG);
    051     * 30-Jul-2003 : Modified entity constructor (CZ);
    052     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    053     * 24-Aug-2003 : Added null checks in drawItem (BK);
    054     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    055     * 07-Oct-2003 : Added renderer state (DG);
    056     * 05-Dec-2003 : Changed call to obtain outline paint (DG);
    057     * 10-Feb-2004 : Added state class, updated drawItem() method to make
    058     *               cut-and-paste overriding easier, and replaced property change
    059     *               with RendererChangeEvent (DG);
    060     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    061     * 26-Apr-2004 : Added gradient paint transformer (DG);
    062     * 19-May-2004 : Fixed bug (879709) with bar zero value for secondary axis (DG);
    063     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    064     *               getYValue() (DG);
    065     * 01-Sep-2004 : Added a flag to control whether or not the bar outlines are
    066     *               drawn (DG);
    067     * 03-Sep-2004 : Added option to use y-interval from dataset to determine the
    068     *               length of the bars (DG);
    069     * 08-Sep-2004 : Added equals() method and updated clone() method (DG);
    070     * 26-Jan-2005 : Added override for getLegendItem() method (DG);
    071     * 20-Apr-2005 : Use generators for label tooltips and URLs (DG);
    072     * 19-May-2005 : Added minimal item label implementation - needs improving (DG);
    073     * 14-Oct-2005 : Fixed rendering problem with inverted axes (DG);
    074     * ------------- JFREECHART 1.0.x ---------------------------------------------
    075     * 21-Jun-2006 : Improved item label handling - see bug 1501768 (DG);
    076     * 24-Aug-2006 : Added crosshair support (DG);
    077     * 13-Dec-2006 : Updated getLegendItems() to return gradient paint
    078     *               transformer (DG);
    079     * 02-Feb-2007 : Changed setUseYInterval() to only notify when the flag
    080     *               changes (DG);
    081     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    082     * 09-Feb-2007 : Updated getLegendItem() to observe drawBarOutline flag (DG);
    083     * 05-Mar-2007 : Applied patch 1671126 by Sergei Ivanov, to fix rendering with
    084     *               LogarithmicAxis (DG);
    085     *
    086     */
    087
    088    package org.jfree.chart.renderer.xy;
    089
    090    import java.awt.Font;
    091    import java.awt.GradientPaint;
    092    import java.awt.Graphics2D;
    093    import java.awt.Paint;
    094    import java.awt.Shape;
    095    import java.awt.Stroke;
    096    import java.awt.geom.Point2D;
    097    import java.awt.geom.Rectangle2D;
    098    import java.io.IOException;
    099    import java.io.ObjectInputStream;
    100    import java.io.ObjectOutputStream;
    101    import java.io.Serializable;
    102
    103    import org.jfree.chart.LegendItem;
    104    import org.jfree.chart.axis.ValueAxis;
    105    import org.jfree.chart.entity.EntityCollection;
    106    import org.jfree.chart.entity.XYItemEntity;
    107    import org.jfree.chart.event.RendererChangeEvent;
    108    import org.jfree.chart.labels.ItemLabelAnchor;
    109    import org.jfree.chart.labels.ItemLabelPosition;
    110    import org.jfree.chart.labels.XYItemLabelGenerator;
    111    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    112    import org.jfree.chart.labels.XYToolTipGenerator;
    113    import org.jfree.chart.plot.CrosshairState;
    114    import org.jfree.chart.plot.PlotOrientation;
    115    import org.jfree.chart.plot.PlotRenderingInfo;
    116    import org.jfree.chart.plot.XYPlot;
    117    import org.jfree.data.Range;
    118    import org.jfree.data.general.DatasetUtilities;
    119    import org.jfree.data.xy.IntervalXYDataset;
    120    import org.jfree.data.xy.XYDataset;
    121    import org.jfree.io.SerialUtilities;
    122    import org.jfree.text.TextUtilities;
    123    import org.jfree.ui.GradientPaintTransformer;
    124    import org.jfree.ui.RectangleEdge;
    125    import org.jfree.ui.StandardGradientPaintTransformer;
    126    import org.jfree.util.ObjectUtilities;
    127    import org.jfree.util.PublicCloneable;
    128    import org.jfree.util.ShapeUtilities;
    129
    130    /**
    131     * A renderer that draws bars on an {@link XYPlot} (requires an
    132     * {@link IntervalXYDataset}).
    133     */
    134    public class XYBarRenderer extends AbstractXYItemRenderer
    135            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
    136
    137        /** For serialization. */
    138        private static final long serialVersionUID = 770559577251370036L;
    139
    140        /**
    141         * The state class used by this renderer.
    142         */
    143        protected class XYBarRendererState extends XYItemRendererState {
    144
    145            /** Base for bars against the range axis, in Java 2D space. */
    146            private double g2Base;
    147
    148            /**
    149             * Creates a new state object.
    150             *
    151             * @param info  the plot rendering info.
    152             */
    153            public XYBarRendererState(PlotRenderingInfo info) {
    154                super(info);
    155            }
    156
    157            /**
    158             * Returns the base (range) value in Java 2D space.
    159             *
    160             * @return The base value.
    161             */
    162            public double getG2Base() {
    163                return this.g2Base;
    164            }
    165
    166            /**
    167             * Sets the range axis base in Java2D space.
    168             *
    169             * @param value  the value.
    170             */
    171            public void setG2Base(double value) {
    172                this.g2Base = value;
    173            }
    174        }
    175
    176        /** The default base value for the bars. */
    177        private double base;
    178
    179        /**
    180         * A flag that controls whether the bars use the y-interval supplied by the
    181         * dataset.
    182         */
    183        private boolean useYInterval;
    184
    185        /** Percentage margin (to reduce the width of bars). */
    186        private double margin;
    187
    188        /** A flag that controls whether or not bar outlines are drawn. */
    189        private boolean drawBarOutline;
    190
    191        /**
    192         * An optional class used to transform gradient paint objects to fit each
    193         * bar.
    194         */
    195        private GradientPaintTransformer gradientPaintTransformer;
    196
    197        /**
    198         * The shape used to represent a bar in each legend item (this should never
    199         * be <code>null</code>).
    200         */
    201        private transient Shape legendBar;
    202
    203        /**
    204         * The fallback position if a positive item label doesn't fit inside the
    205         * bar.
    206         */
    207        private ItemLabelPosition positiveItemLabelPositionFallback;
    208
    209        /**
    210         * The fallback position if a negative item label doesn't fit inside the
    211         * bar.
    212         */
    213        private ItemLabelPosition negativeItemLabelPositionFallback;
    214
    215        /**
    216         * The default constructor.
    217         */
    218        public XYBarRenderer() {
    219            this(0.0);
    220        }
    221
    222        /**
    223         * Constructs a new renderer.
    224         *
    225         * @param margin  the percentage amount to trim from the width of each bar.
    226         */
    227        public XYBarRenderer(double margin) {
    228            super();
    229            this.margin = margin;
    230            this.base = 0.0;
    231            this.useYInterval = false;
    232            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    233            this.drawBarOutline = true;
    234            this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0);
    235        }
    236
    237        /**
    238         * Returns the base value for the bars.
    239         *
    240         * @return The base value for the bars.
    241         *
    242         * @see #setBase(double)
    243         */
    244        public double getBase() {
    245            return this.base;
    246        }
    247
    248        /**
    249         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    250         * to all registered listeners.  The base value is not used if the dataset's
    251         * y-interval is being used to determine the bar length.
    252         *
    253         * @param base  the new base value.
    254         *
    255         * @see #getBase()
    256         * @see #getUseYInterval()
    257         */
    258        public void setBase(double base) {
    259            this.base = base;
    260            notifyListeners(new RendererChangeEvent(this));
    261        }
    262
    263        /**
    264         * Returns a flag that determines whether the y-interval from the dataset is
    265         * used to calculate the length of each bar.
    266         *
    267         * @return A boolean.
    268         *
    269         * @see #setUseYInterval(boolean)
    270         */
    271        public boolean getUseYInterval() {
    272            return this.useYInterval;
    273        }
    274
    275        /**
    276         * Sets the flag that determines whether the y-interval from the dataset is
    277         * used to calculate the length of each bar, and sends a
    278         * {@link RendererChangeEvent} to all registered listeners.
    279         *
    280         * @param use  the flag.
    281         *
    282         * @see #getUseYInterval()
    283         */
    284        public void setUseYInterval(boolean use) {
    285            if (this.useYInterval != use) {
    286                this.useYInterval = use;
    287                notifyListeners(new RendererChangeEvent(this));
    288            }
    289        }
    290
    291        /**
    292         * Returns the margin which is a percentage amount by which the bars are
    293         * trimmed.
    294         *
    295         * @return The margin.
    296         *
    297         * @see #setMargin(double)
    298         */
    299        public double getMargin() {
    300            return this.margin;
    301        }
    302
    303        /**
    304         * Sets the percentage amount by which the bars are trimmed and sends a
    305         * {@link RendererChangeEvent} to all registered listeners.
    306         *
    307         * @param margin  the new margin.
    308         *
    309         * @see #getMargin()
    310         */
    311        public void setMargin(double margin) {
    312            this.margin = margin;
    313            notifyListeners(new RendererChangeEvent(this));
    314        }
    315
    316        /**
    317         * Returns a flag that controls whether or not bar outlines are drawn.
    318         *
    319         * @return A boolean.
    320         *
    321         * @see #setDrawBarOutline(boolean)
    322         */
    323        public boolean isDrawBarOutline() {
    324            return this.drawBarOutline;
    325        }
    326
    327        /**
    328         * Sets the flag that controls whether or not bar outlines are drawn and
    329         * sends a {@link RendererChangeEvent} to all registered listeners.
    330         *
    331         * @param draw  the flag.
    332         *
    333         * @see #isDrawBarOutline()
    334         */
    335        public void setDrawBarOutline(boolean draw) {
    336            this.drawBarOutline = draw;
    337            notifyListeners(new RendererChangeEvent(this));
    338        }
    339
    340        /**
    341         * Returns the gradient paint transformer (an object used to transform
    342         * gradient paint objects to fit each bar.
    343         *
    344         * @return A transformer (<code>null</code> possible).
    345         *
    346         * @see #setGradientPaintTransformer(GradientPaintTransformer)
    347         */
    348        public GradientPaintTransformer getGradientPaintTransformer() {
    349            return this.gradientPaintTransformer;
    350        }
    351
    352        /**
    353         * Sets the gradient paint transformer and sends a
    354         * {@link RendererChangeEvent} to all registered listeners.
    355         *
    356         * @param transformer  the transformer (<code>null</code> permitted).
    357         *
    358         * @see #getGradientPaintTransformer()
    359         */
    360        public void setGradientPaintTransformer(
    361                GradientPaintTransformer transformer) {
    362            this.gradientPaintTransformer = transformer;
    363            notifyListeners(new RendererChangeEvent(this));
    364        }
    365
    366        /**
    367         * Returns the shape used to represent bars in each legend item.
    368         *
    369         * @return The shape used to represent bars in each legend item (never
    370         *         <code>null</code>).
    371         *
    372         * @see #setLegendBar(Shape)
    373         */
    374        public Shape getLegendBar() {
    375            return this.legendBar;
    376        }
    377
    378        /**
    379         * Sets the shape used to represent bars in each legend item and sends a
    380         * {@link RendererChangeEvent} to all registered listeners.
    381         *
    382         * @param bar  the bar shape (<code>null</code> not permitted).
    383         *
    384         * @see #getLegendBar()
    385         */
    386        public void setLegendBar(Shape bar) {
    387            if (bar == null) {
    388                throw new IllegalArgumentException("Null 'bar' argument.");
    389            }
    390            this.legendBar = bar;
    391            notifyListeners(new RendererChangeEvent(this));
    392        }
    393
    394        /**
    395         * Returns the fallback position for positive item labels that don't fit
    396         * within a bar.
    397         *
    398         * @return The fallback position (<code>null</code> possible).
    399         *
    400         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    401         * @since 1.0.2
    402         */
    403        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    404            return this.positiveItemLabelPositionFallback;
    405        }
    406
    407        /**
    408         * Sets the fallback position for positive item labels that don't fit
    409         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    410         * listeners.
    411         *
    412         * @param position  the position (<code>null</code> permitted).
    413         *
    414         * @see #getPositiveItemLabelPositionFallback()
    415         * @since 1.0.2
    416         */
    417        public void setPositiveItemLabelPositionFallback(
    418                ItemLabelPosition position) {
    419            this.positiveItemLabelPositionFallback = position;
    420            notifyListeners(new RendererChangeEvent(this));
    421        }
    422
    423        /**
    424         * Returns the fallback position for negative item labels that don't fit
    425         * within a bar.
    426         *
    427         * @return The fallback position (<code>null</code> possible).
    428         *
    429         * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition)
    430         * @since 1.0.2
    431         */
    432        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    433            return this.negativeItemLabelPositionFallback;
    434        }
    435
    436        /**
    437         * Sets the fallback position for negative item labels that don't fit
    438         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    439         * listeners.
    440         *
    441         * @param position  the position (<code>null</code> permitted).
    442         *
    443         * @see #getNegativeItemLabelPositionFallback()
    444         * @since 1.0.2
    445         */
    446        public void setNegativeItemLabelPositionFallback(
    447                ItemLabelPosition position) {
    448            this.negativeItemLabelPositionFallback = position;
    449            notifyListeners(new RendererChangeEvent(this));
    450        }
    451
    452        /**
    453         * Initialises the renderer and returns a state object that should be
    454         * passed to all subsequent calls to the drawItem() method.  Here we
    455         * calculate the Java2D y-coordinate for zero, since all the bars have
    456         * their bases fixed at zero.
    457         *
    458         * @param g2  the graphics device.
    459         * @param dataArea  the area inside the axes.
    460         * @param plot  the plot.
    461         * @param dataset  the data.
    462         * @param info  an optional info collection object to return data back to
    463         *              the caller.
    464         *
    465         * @return A state object.
    466         */
    467        public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
    468                XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
    469
    470            XYBarRendererState state = new XYBarRendererState(info);
    471            ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf(
    472                    dataset));
    473            state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea,
    474                    plot.getRangeAxisEdge()));
    475            return state;
    476
    477        }
    478
    479        /**
    480         * Returns a default legend item for the specified series.  Subclasses
    481         * should override this method to generate customised items.
    482         *
    483         * @param datasetIndex  the dataset index (zero-based).
    484         * @param series  the series index (zero-based).
    485         *
    486         * @return A legend item for the series.
    487         */
    488        public LegendItem getLegendItem(int datasetIndex, int series) {
    489            LegendItem result = null;
    490            XYPlot xyplot = getPlot();
    491            if (xyplot != null) {
    492                XYDataset dataset = xyplot.getDataset(datasetIndex);
    493                if (dataset != null) {
    494                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    495                    String label = lg.generateLabel(dataset, series);
    496                    String description = label;
    497                    String toolTipText = null;
    498                    if (getLegendItemToolTipGenerator() != null) {
    499                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    500                                dataset, series);
    501                    }
    502                    String urlText = null;
    503                    if (getLegendItemURLGenerator() != null) {
    504                        urlText = getLegendItemURLGenerator().generateLabel(
    505                                dataset, series);
    506                    }
    507                    Shape shape = this.legendBar;
    508                    Paint paint = getSeriesPaint(series);
    509                    Paint outlinePaint = getSeriesOutlinePaint(series);
    510                    Stroke outlineStroke = getSeriesOutlineStroke(series);
    511                    if (this.drawBarOutline) {
    512                        result = new LegendItem(label, description, toolTipText,
    513                                urlText, shape, paint, outlineStroke, outlinePaint);
    514                    }
    515                    else {
    516                        result = new LegendItem(label, description, toolTipText,
    517                                urlText, shape, paint);
    518                    }
    519                    if (getGradientPaintTransformer() != null) {
    520                        result.setFillPaintTransformer(
    521                                getGradientPaintTransformer());
    522                    }
    523                }
    524            }
    525            return result;
    526        }
    527
    528        /**
    529         * Draws the visual representation of a single data item.
    530         *
    531         * @param g2  the graphics device.
    532         * @param state  the renderer state.
    533         * @param dataArea  the area within which the plot is being drawn.
    534         * @param info  collects information about the drawing.
    535         * @param plot  the plot (can be used to obtain standard color
    536         *              information etc).
    537         * @param domainAxis  the domain axis.
    538         * @param rangeAxis  the range axis.
    539         * @param dataset  the dataset.
    540         * @param series  the series index (zero-based).
    541         * @param item  the item index (zero-based).
    542         * @param crosshairState  crosshair information for the plot
    543         *                        (<code>null</code> permitted).
    544         * @param pass  the pass index.
    545         */
    546        public void drawItem(Graphics2D g2,
    547                             XYItemRendererState state,
    548                             Rectangle2D dataArea,
    549                             PlotRenderingInfo info,
    550                             XYPlot plot,
    551                             ValueAxis domainAxis,
    552                             ValueAxis rangeAxis,
    553                             XYDataset dataset,
    554                             int series,
    555                             int item,
    556                             CrosshairState crosshairState,
    557                             int pass) {
    558
    559            if (!getItemVisible(series, item)) {
    560                return;
    561            }
    562            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    563
    564            double value0;
    565            double value1;
    566            if (this.useYInterval) {
    567                value0 = intervalDataset.getStartYValue(series, item);
    568                value1 = intervalDataset.getEndYValue(series, item);
    569            }
    570            else {
    571                value0 = this.base;
    572                value1 = intervalDataset.getYValue(series, item);
    573            }
    574            if (Double.isNaN(value0) || Double.isNaN(value1)) {
    575                return;
    576            }
    577            if (value0 <= value1) {
    578                if (!rangeAxis.getRange().intersects(value0, value1)) {
    579                    return;
    580                }
    581            }
    582            else {
    583                if (!rangeAxis.getRange().intersects(value1, value0)) {
    584                    return;
    585                }
    586            }
    587
    588            double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea,
    589                    plot.getRangeAxisEdge());
    590            double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea,
    591                    plot.getRangeAxisEdge());
    592            double bottom = Math.min(translatedValue0, translatedValue1);
    593            double top = Math.max(translatedValue0, translatedValue1);
    594
    595            double startX = intervalDataset.getStartXValue(series, item);
    596            if (Double.isNaN(startX)) {
    597                return;
    598            }
    599            double endX = intervalDataset.getEndXValue(series, item);
    600            if (Double.isNaN(endX)) {
    601                return;
    602            }
    603            if (startX <= endX) {
    604                if (!domainAxis.getRange().intersects(startX, endX)) {
    605                    return;
    606                }
    607            }
    608            else {
    609                if (!domainAxis.getRange().intersects(endX, startX)) {
    610                    return;
    611                }
    612            }
    613
    614            RectangleEdge location = plot.getDomainAxisEdge();
    615            double translatedStartX = domainAxis.valueToJava2D(startX, dataArea,
    616                    location);
    617            double translatedEndX = domainAxis.valueToJava2D(endX, dataArea,
    618                    location);
    619
    620            double translatedWidth = Math.max(1, Math.abs(translatedEndX
    621                    - translatedStartX));
    622
    623            if (getMargin() > 0.0) {
    624                double cut = translatedWidth * getMargin();
    625                translatedWidth = translatedWidth - cut;
    626                translatedStartX = translatedStartX + cut / 2;
    627            }
    628
    629            Rectangle2D bar = null;
    630            PlotOrientation orientation = plot.getOrientation();
    631            if (orientation == PlotOrientation.HORIZONTAL) {
    632                // clip left and right bounds to data area
    633                bottom = Math.max(bottom, dataArea.getMinX());
    634                top = Math.min(top, dataArea.getMaxX());
    635                bar = new Rectangle2D.Double(
    636                    bottom,
    637                    Math.min(translatedStartX, translatedEndX),
    638                    top - bottom, translatedWidth);
    639            }
    640            else if (orientation == PlotOrientation.VERTICAL) {
    641                // clip top and bottom bounds to data area
    642                bottom = Math.max(bottom, dataArea.getMinY());
    643                top = Math.min(top, dataArea.getMaxY());
    644                bar = new Rectangle2D.Double(
    645                    Math.min(translatedStartX, translatedEndX),
    646                    bottom,
    647                    translatedWidth, top - bottom);
    648            }
    649
    650            Paint itemPaint = getItemPaint(series, item);
    651            if (getGradientPaintTransformer()
    652                    != null && itemPaint instanceof GradientPaint) {
    653                GradientPaint gp = (GradientPaint) itemPaint;
    654                itemPaint = getGradientPaintTransformer().transform(gp, bar);
    655            }
    656            g2.setPaint(itemPaint);
    657            g2.fill(bar);
    658            if (isDrawBarOutline()
    659                    && Math.abs(translatedEndX - translatedStartX) > 3) {
    660                Stroke stroke = getItemOutlineStroke(series, item);
    661                Paint paint = getItemOutlinePaint(series, item);
    662                if (stroke != null && paint != null) {
    663                    g2.setStroke(stroke);
    664                    g2.setPaint(paint);
    665                    g2.draw(bar);
    666                }
    667            }
    668
    669            if (isItemLabelVisible(series, item)) {
    670                XYItemLabelGenerator generator = getItemLabelGenerator(series,
    671                        item);
    672                drawItemLabel(g2, dataset, series, item, plot, generator, bar,
    673                        value1 < 0.0);
    674            }
    675
    676            // update the crosshair point
    677            double x1 = (startX + endX) / 2.0;
    678            double y1 = dataset.getYValue(series, item);
    679            double transX1 = domainAxis.valueToJava2D(x1, dataArea, location);
    680            double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    681                    plot.getRangeAxisEdge());
    682            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    683            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    684            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    685                    rangeAxisIndex, transX1, transY1, plot.getOrientation());
    686
    687            // add an entity for the item...
    688            if (info != null) {
    689                EntityCollection entities = info.getOwner().getEntityCollection();
    690                if (entities != null) {
    691                    String tip = null;
    692                    XYToolTipGenerator generator = getToolTipGenerator(series,
    693                            item);
    694                    if (generator != null) {
    695                        tip = generator.generateToolTip(dataset, series, item);
    696                    }
    697                    String url = null;
    698                    if (getURLGenerator() != null) {
    699                        url = getURLGenerator().generateURL(dataset, series, item);
    700                    }
    701                    XYItemEntity entity = new XYItemEntity(bar, dataset, series,
    702                            item, tip, url);
    703                    entities.add(entity);
    704                }
    705            }
    706
    707        }
    708
    709        /**
    710         * Draws an item label.  This method is overridden so that the bar can be
    711         * used to calculate the label anchor point.
    712         *
    713         * @param g2  the graphics device.
    714         * @param dataset  the dataset.
    715         * @param series  the series index.
    716         * @param item  the item index.
    717         * @param plot  the plot.
    718         * @param generator  the label generator.
    719         * @param bar  the bar.
    720         * @param negative  a flag indicating a negative value.
    721         */
    722        protected void drawItemLabel(Graphics2D g2, XYDataset dataset,
    723                int series, int item, XYPlot plot, XYItemLabelGenerator generator,
    724                Rectangle2D bar, boolean negative) {
    725
    726            String label = generator.generateLabel(dataset, series, item);
    727            if (label == null) {
    728                return;  // nothing to do
    729            }
    730
    731            Font labelFont = getItemLabelFont(series, item);
    732            g2.setFont(labelFont);
    733            Paint paint = getItemLabelPaint(series, item);
    734            g2.setPaint(paint);
    735
    736            // find out where to place the label...
    737            ItemLabelPosition position = null;
    738            if (!negative) {
    739                position = getPositiveItemLabelPosition(series, item);
    740            }
    741            else {
    742                position = getNegativeItemLabelPosition(series, item);
    743            }
    744
    745            // work out the label anchor point...
    746            Point2D anchorPoint = calculateLabelAnchorPoint(
    747                    position.getItemLabelAnchor(), bar, plot.getOrientation());
    748
    749            if (isInternalAnchor(position.getItemLabelAnchor())) {
    750                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
    751                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    752                        position.getTextAnchor(), position.getAngle(),
    753                        position.getRotationAnchor());
    754
    755                if (bounds != null) {
    756                    if (!bar.contains(bounds.getBounds2D())) {
    757                        if (!negative) {
    758                            position = getPositiveItemLabelPositionFallback();
    759                        }
    760                        else {
    761                            position = getNegativeItemLabelPositionFallback();
    762                        }
    763                        if (position != null) {
    764                            anchorPoint = calculateLabelAnchorPoint(
    765                                    position.getItemLabelAnchor(), bar,
    766                                    plot.getOrientation());
    767                        }
    768                    }
    769                }
    770
    771            }
    772
    773            if (position != null) {
    774                TextUtilities.drawRotatedString(label, g2,
    775                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    776                        position.getTextAnchor(), position.getAngle(),
    777                        position.getRotationAnchor());
    778            }
    779        }
    780
    781        /**
    782         * Calculates the item label anchor point.
    783         *
    784         * @param anchor  the anchor.
    785         * @param bar  the bar.
    786         * @param orientation  the plot orientation.
    787         *
    788         * @return The anchor point.
    789         */
    790        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    791                Rectangle2D bar, PlotOrientation orientation) {
    792
    793            Point2D result = null;
    794            double offset = getItemLabelAnchorOffset();
    795            double x0 = bar.getX() - offset;
    796            double x1 = bar.getX();
    797            double x2 = bar.getX() + offset;
    798            double x3 = bar.getCenterX();
    799            double x4 = bar.getMaxX() - offset;
    800            double x5 = bar.getMaxX();
    801            double x6 = bar.getMaxX() + offset;
    802
    803            double y0 = bar.getMaxY() + offset;
    804            double y1 = bar.getMaxY();
    805            double y2 = bar.getMaxY() - offset;
    806            double y3 = bar.getCenterY();
    807            double y4 = bar.getMinY() + offset;
    808            double y5 = bar.getMinY();
    809            double y6 = bar.getMinY() - offset;
    810
    811            if (anchor == ItemLabelAnchor.CENTER) {
    812                result = new Point2D.Double(x3, y3);
    813            }
    814            else if (anchor == ItemLabelAnchor.INSIDE1) {
    815                result = new Point2D.Double(x4, y4);
    816            }
    817            else if (anchor == ItemLabelAnchor.INSIDE2) {
    818                result = new Point2D.Double(x4, y4);
    819            }
    820            else if (anchor == ItemLabelAnchor.INSIDE3) {
    821                result = new Point2D.Double(x4, y3);
    822            }
    823            else if (anchor == ItemLabelAnchor.INSIDE4) {
    824                result = new Point2D.Double(x4, y2);
    825            }
    826            else if (anchor == ItemLabelAnchor.INSIDE5) {
    827                result = new Point2D.Double(x4, y2);
    828            }
    829            else if (anchor == ItemLabelAnchor.INSIDE6) {
    830                result = new Point2D.Double(x3, y2);
    831            }
    832            else if (anchor == ItemLabelAnchor.INSIDE7) {
    833                result = new Point2D.Double(x2, y2);
    834            }
    835            else if (anchor == ItemLabelAnchor.INSIDE8) {
    836                result = new Point2D.Double(x2, y2);
    837            }
    838            else if (anchor == ItemLabelAnchor.INSIDE9) {
    839                result = new Point2D.Double(x2, y3);
    840            }
    841            else if (anchor == ItemLabelAnchor.INSIDE10) {
    842                result = new Point2D.Double(x2, y4);
    843            }
    844            else if (anchor == ItemLabelAnchor.INSIDE11) {
    845                result = new Point2D.Double(x2, y4);
    846            }
    847            else if (anchor == ItemLabelAnchor.INSIDE12) {
    848                result = new Point2D.Double(x3, y4);
    849            }
    850            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    851                result = new Point2D.Double(x5, y6);
    852            }
    853            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    854                result = new Point2D.Double(x6, y5);
    855            }
    856            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    857                result = new Point2D.Double(x6, y3);
    858            }
    859            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    860                result = new Point2D.Double(x6, y1);
    861            }
    862            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    863                result = new Point2D.Double(x5, y0);
    864            }
    865            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    866                result = new Point2D.Double(x3, y0);
    867            }
    868            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    869                result = new Point2D.Double(x1, y0);
    870            }
    871            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    872                result = new Point2D.Double(x0, y1);
    873            }
    874            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    875                result = new Point2D.Double(x0, y3);
    876            }
    877            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    878                result = new Point2D.Double(x0, y5);
    879            }
    880            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    881                result = new Point2D.Double(x1, y6);
    882            }
    883            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    884                result = new Point2D.Double(x3, y6);
    885            }
    886
    887            return result;
    888
    889        }
    890
    891        /**
    892         * Returns <code>true</code> if the specified anchor point is inside a bar.
    893         *
    894         * @param anchor  the anchor point.
    895         *
    896         * @return A boolean.
    897         */
    898        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    899            return anchor == ItemLabelAnchor.CENTER
    900                   || anchor == ItemLabelAnchor.INSIDE1
    901                   || anchor == ItemLabelAnchor.INSIDE2
    902                   || anchor == ItemLabelAnchor.INSIDE3
    903                   || anchor == ItemLabelAnchor.INSIDE4
    904                   || anchor == ItemLabelAnchor.INSIDE5
    905                   || anchor == ItemLabelAnchor.INSIDE6
    906                   || anchor == ItemLabelAnchor.INSIDE7
    907                   || anchor == ItemLabelAnchor.INSIDE8
    908                   || anchor == ItemLabelAnchor.INSIDE9
    909                   || anchor == ItemLabelAnchor.INSIDE10
    910                   || anchor == ItemLabelAnchor.INSIDE11
    911                   || anchor == ItemLabelAnchor.INSIDE12;
    912        }
    913
    914        /**
    915         * Returns the lower and upper bounds (range) of the x-values in the
    916         * specified dataset.  Since this renderer uses the x-interval in the
    917         * dataset, this is taken into account for the range.
    918         *
    919         * @param dataset  the dataset (<code>null</code> permitted).
    920         *
    921         * @return The range (<code>null</code> if the dataset is
    922         *         <code>null</code> or empty).
    923         */
    924        public Range findDomainBounds(XYDataset dataset) {
    925            if (dataset != null) {
    926                return DatasetUtilities.findDomainBounds(dataset, true);
    927            }
    928            else {
    929                return null;
    930            }
    931        }
    932
    933        /**
    934         * Returns a clone of the renderer.
    935         *
    936         * @return A clone.
    937         *
    938         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    939         */
    940        public Object clone() throws CloneNotSupportedException {
    941            XYBarRenderer result = (XYBarRenderer) super.clone();
    942            if (this.gradientPaintTransformer != null) {
    943                result.gradientPaintTransformer = (GradientPaintTransformer)
    944                    ObjectUtilities.clone(this.gradientPaintTransformer);
    945            }
    946            result.legendBar = ShapeUtilities.clone(this.legendBar);
    947            return result;
    948        }
    949
    950        /**
    951         * Tests this renderer for equality with an arbitrary object.
    952         *
    953         * @param obj  the object to test against (<code>null</code> permitted).
    954         *
    955         * @return A boolean.
    956         */
    957        public boolean equals(Object obj) {
    958            if (obj == this) {
    959                return true;
    960            }
    961            if (!(obj instanceof XYBarRenderer)) {
    962                return false;
    963            }
    964            if (!super.equals(obj)) {
    965                return false;
    966            }
    967            XYBarRenderer that = (XYBarRenderer) obj;
    968            if (this.base != that.base) {
    969                return false;
    970            }
    971            if (this.drawBarOutline != that.drawBarOutline) {
    972                return false;
    973            }
    974            if (this.margin != that.margin) {
    975                return false;
    976            }
    977            if (this.useYInterval != that.useYInterval) {
    978                return false;
    979            }
    980            if (!ObjectUtilities.equal(
    981                this.gradientPaintTransformer, that.gradientPaintTransformer)
    982            ) {
    983                return false;
    984            }
    985            if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) {
    986                return false;
    987            }
    988            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    989                    that.positiveItemLabelPositionFallback)) {
    990                return false;
    991            }
    992            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    993                    that.negativeItemLabelPositionFallback)) {
    994                return false;
    995            }
    996            return true;
    997        }
    998
    999        /**
    1000         * Provides serialization support.
    1001         *
    1002         * @param stream  the input stream.
    1003         *
    1004         * @throws IOException  if there is an I/O error.
    1005         * @throws ClassNotFoundException  if there is a classpath problem.
    1006         */
    1007        private void readObject(ObjectInputStream stream)
    1008                throws IOException, ClassNotFoundException {
    1009            stream.defaultReadObject();
    1010            this.legendBar = SerialUtilities.readShape(stream);
    1011        }
    1012
    1013        /**
    1014         * Provides serialization support.
    1015         *
    1016         * @param stream  the output stream.
    1017         *
    1018         * @throws IOException  if there is an I/O error.
    1019         */
    1020        private void writeObject(ObjectOutputStream stream) throws IOException {
    1021            stream.defaultWriteObject();
    1022            SerialUtilities.writeShape(this.legendBar, stream);
    1023        }
    1024
    1025    }