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