    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, 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-2009, 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     * 17-Jun-2008 : Apply legend font and paint attributes (DG);
    094     * 19-Jun-2008 : Added findRangeBounds() method override to fix bug in default
    095     *               axis range (DG);
    096     * 24-Jun-2008 : Added new barPainter mechanism (DG);
    097     * 03-Feb-2009 : Added defaultShadowsVisible flag (DG);
    098     * 05-Feb-2009 : Added barAlignmentFactor (DG);
    099     *
    100     */
    101
    102    package org.jfree.chart.renderer.xy;
    103
    104    import java.awt.Font;
    105    import java.awt.Graphics2D;
    106    import java.awt.Paint;
    107    import java.awt.Shape;
    108    import java.awt.Stroke;
    109    import java.awt.geom.Point2D;
    110    import java.awt.geom.Rectangle2D;
    111    import java.io.IOException;
    112    import java.io.ObjectInputStream;
    113    import java.io.ObjectOutputStream;
    114    import java.io.Serializable;
    115
    116    import org.jfree.chart.LegendItem;
    117    import org.jfree.chart.axis.ValueAxis;
    118    import org.jfree.chart.entity.EntityCollection;
    119    import org.jfree.chart.event.RendererChangeEvent;
    120    import org.jfree.chart.labels.ItemLabelAnchor;
    121    import org.jfree.chart.labels.ItemLabelPosition;
    122    import org.jfree.chart.labels.XYItemLabelGenerator;
    123    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    124    import org.jfree.chart.plot.CrosshairState;
    125    import org.jfree.chart.plot.PlotOrientation;
    126    import org.jfree.chart.plot.PlotRenderingInfo;
    127    import org.jfree.chart.plot.XYPlot;
    128    import org.jfree.data.Range;
    129    import org.jfree.data.general.DatasetUtilities;
    130    import org.jfree.data.xy.IntervalXYDataset;
    131    import org.jfree.data.xy.XYDataset;
    132    import org.jfree.io.SerialUtilities;
    133    import org.jfree.text.TextUtilities;
    134    import org.jfree.ui.GradientPaintTransformer;
    135    import org.jfree.ui.RectangleEdge;
    136    import org.jfree.ui.StandardGradientPaintTransformer;
    137    import org.jfree.util.ObjectUtilities;
    138    import org.jfree.util.PublicCloneable;
    139    import org.jfree.util.ShapeUtilities;
    140
    141    /**
    142     * A renderer that draws bars on an {@link XYPlot} (requires an
    143     * {@link IntervalXYDataset}).  The example shown here is generated by the
    144     * <code>XYBarChartDemo1.java</code> program included in the JFreeChart
    145     * demo collection:
    146     * <br><br>
    147     * <img src="../../../../../images/XYBarRendererSample.png"
    148     * alt="XYBarRendererSample.png" />
    149     */
    150    public class XYBarRenderer extends AbstractXYItemRenderer
    151            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
    152
    153        /** For serialization. */
    154        private static final long serialVersionUID = 770559577251370036L;
    155
    156        /**
    157         * The default bar painter assigned to each new instance of this renderer.
    158         *
    159         * @since 1.0.11
    160         */
    161        private static XYBarPainter defaultBarPainter = new GradientXYBarPainter();
    162
    163        /**
    164         * Returns the default bar painter.
    165         *
    166         * @return The default bar painter.
    167         *
    168         * @since 1.0.11
    169         */
    170        public static XYBarPainter getDefaultBarPainter() {
    171            return XYBarRenderer.defaultBarPainter;
    172        }
    173
    174        /**
    175         * Sets the default bar painter.
    176         *
    177         * @param painter  the painter (<code>null</code> not permitted).
    178         *
    179         * @since 1.0.11
    180         */
    181        public static void setDefaultBarPainter(XYBarPainter painter) {
    182            if (painter == null) {
    183                throw new IllegalArgumentException("Null 'painter' argument.");
    184            }
    185            XYBarRenderer.defaultBarPainter = painter;
    186        }
    187
    188        /**
    189         * The default value for the initialisation of the shadowsVisible flag.
    190         */
    191        private static boolean defaultShadowsVisible = true;
    192
    193        /**
    194         * Returns the default value for the <code>shadowsVisible</code> flag.
    195         *
    196         * @return A boolean.
    197         *
    198         * @see #setDefaultShadowsVisible(boolean)
    199         *
    200         * @since 1.0.13
    201         */
    202        public static boolean getDefaultShadowsVisible() {
    203            return XYBarRenderer.defaultShadowsVisible;
    204        }
    205
    206        /**
    207         * Sets the default value for the shadows visible flag.
    208         *
    209         * @param visible  the new value for the default.
    210         *
    211         * @see #getDefaultShadowsVisible()
    212         *
    213         * @since 1.0.13
    214         */
    215        public static void setDefaultShadowsVisible(boolean visible) {
    216            XYBarRenderer.defaultShadowsVisible = visible;
    217        }
    218
    219        /**
    220         * The state class used by this renderer.
    221         */
    222        protected class XYBarRendererState extends XYItemRendererState {
    223
    224            /** Base for bars against the range axis, in Java 2D space. */
    225            private double g2Base;
    226
    227            /**
    228             * Creates a new state object.
    229             *
    230             * @param info  the plot rendering info.
    231             */
    232            public XYBarRendererState(PlotRenderingInfo info) {
    233                super(info);
    234            }
    235
    236            /**
    237             * Returns the base (range) value in Java 2D space.
    238             *
    239             * @return The base value.
    240             */
    241            public double getG2Base() {
    242                return this.g2Base;
    243            }
    244
    245            /**
    246             * Sets the range axis base in Java2D space.
    247             *
    248             * @param value  the value.
    249             */
    250            public void setG2Base(double value) {
    251                this.g2Base = value;
    252            }
    253        }
    254
    255        /** The default base value for the bars. */
    256        private double base;
    257
    258        /**
    259         * A flag that controls whether the bars use the y-interval supplied by the
    260         * dataset.
    261         */
    262        private boolean useYInterval;
    263
    264        /** Percentage margin (to reduce the width of bars). */
    265        private double margin;
    266
    267        /** A flag that controls whether or not bar outlines are drawn. */
    268        private boolean drawBarOutline;
    269
    270        /**
    271         * An optional class used to transform gradient paint objects to fit each
    272         * bar.
    273         */
    274        private GradientPaintTransformer gradientPaintTransformer;
    275
    276        /**
    277         * The shape used to represent a bar in each legend item (this should never
    278         * be <code>null</code>).
    279         */
    280        private transient Shape legendBar;
    281
    282        /**
    283         * The fallback position if a positive item label doesn't fit inside the
    284         * bar.
    285         */
    286        private ItemLabelPosition positiveItemLabelPositionFallback;
    287
    288        /**
    289         * The fallback position if a negative item label doesn't fit inside the
    290         * bar.
    291         */
    292        private ItemLabelPosition negativeItemLabelPositionFallback;
    293
    294        /**
    295         * The bar painter (never <code>null</code>).
    296         *
    297         * @since 1.0.11
    298         */
    299        private XYBarPainter barPainter;
    300
    301        /**
    302         * The flag that controls whether or not shadows are drawn for the bars.
    303         *
    304         * @since 1.0.11
    305         */
    306        private boolean shadowsVisible;
    307
    308        /**
    309         * The x-offset for the shadow effect.
    310         *
    311         * @since 1.0.11
    312         */
    313        private double shadowXOffset;
    314
    315        /**
    316         * The y-offset for the shadow effect.
    317         *
    318         * @since 1.0.11
    319         */
    320        private double shadowYOffset;
    321
    322        /**
    323         * A factor used to align the bars about the x-value.
    324         *
    325         * @since 1.0.13
    326         */
    327        private double barAlignmentFactor;
    328
    329        /**
    330         * The default constructor.
    331         */
    332        public XYBarRenderer() {
    333            this(0.0);
    334        }
    335
    336        /**
    337         * Constructs a new renderer.
    338         *
    339         * @param margin  the percentage amount to trim from the width of each bar.
    340         */
    341        public XYBarRenderer(double margin) {
    342            super();
    343            this.margin = margin;
    344            this.base = 0.0;
    345            this.useYInterval = false;
    346            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    347            this.drawBarOutline = false;
    348            this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0);
    349            this.barPainter = getDefaultBarPainter();
    350            this.shadowsVisible = getDefaultShadowsVisible();
    351            this.shadowXOffset = 4.0;
    352            this.shadowYOffset = 4.0;
    353            this.barAlignmentFactor = -1.0;
    354        }
    355
    356        /**
    357         * Returns the base value for the bars.
    358         *
    359         * @return The base value for the bars.
    360         *
    361         * @see #setBase(double)
    362         */
    363        public double getBase() {
    364            return this.base;
    365        }
    366
    367        /**
    368         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    369         * to all registered listeners.  The base value is not used if the dataset's
    370         * y-interval is being used to determine the bar length.
    371         *
    372         * @param base  the new base value.
    373         *
    374         * @see #getBase()
    375         * @see #getUseYInterval()
    376         */
    377        public void setBase(double base) {
    378            this.base = base;
    379            fireChangeEvent();
    380        }
    381
    382        /**
    383         * Returns a flag that determines whether the y-interval from the dataset is
    384         * used to calculate the length of each bar.
    385         *
    386         * @return A boolean.
    387         *
    388         * @see #setUseYInterval(boolean)
    389         */
    390        public boolean getUseYInterval() {
    391            return this.useYInterval;
    392        }
    393
    394        /**
    395         * Sets the flag that determines whether the y-interval from the dataset is
    396         * used to calculate the length of each bar, and sends a
    397         * {@link RendererChangeEvent} to all registered listeners.
    398         *
    399         * @param use  the flag.
    400         *
    401         * @see #getUseYInterval()
    402         */
    403        public void setUseYInterval(boolean use) {
    404            if (this.useYInterval != use) {
    405                this.useYInterval = use;
    406                fireChangeEvent();
    407            }
    408        }
    409
    410        /**
    411         * Returns the margin which is a percentage amount by which the bars are
    412         * trimmed.
    413         *
    414         * @return The margin.
    415         *
    416         * @see #setMargin(double)
    417         */
    418        public double getMargin() {
    419            return this.margin;
    420        }
    421
    422        /**
    423         * Sets the percentage amount by which the bars are trimmed and sends a
    424         * {@link RendererChangeEvent} to all registered listeners.
    425         *
    426         * @param margin  the new margin.
    427         *
    428         * @see #getMargin()
    429         */
    430        public void setMargin(double margin) {
    431            this.margin = margin;
    432            fireChangeEvent();
    433        }
    434
    435        /**
    436         * Returns a flag that controls whether or not bar outlines are drawn.
    437         *
    438         * @return A boolean.
    439         *
    440         * @see #setDrawBarOutline(boolean)
    441         */
    442        public boolean isDrawBarOutline() {
    443            return this.drawBarOutline;
    444        }
    445
    446        /**
    447         * Sets the flag that controls whether or not bar outlines are drawn and
    448         * sends a {@link RendererChangeEvent} to all registered listeners.
    449         *
    450         * @param draw  the flag.
    451         *
    452         * @see #isDrawBarOutline()
    453         */
    454        public void setDrawBarOutline(boolean draw) {
    455            this.drawBarOutline = draw;
    456            fireChangeEvent();
    457        }
    458
    459        /**
    460         * Returns the gradient paint transformer (an object used to transform
    461         * gradient paint objects to fit each bar).
    462         *
    463         * @return A transformer (<code>null</code> possible).
    464         *
    465         * @see #setGradientPaintTransformer(GradientPaintTransformer)
    466         */
    467        public GradientPaintTransformer getGradientPaintTransformer() {
    468            return this.gradientPaintTransformer;
    469        }
    470
    471        /**
    472         * Sets the gradient paint transformer and sends a
    473         * {@link RendererChangeEvent} to all registered listeners.
    474         *
    475         * @param transformer  the transformer (<code>null</code> permitted).
    476         *
    477         * @see #getGradientPaintTransformer()
    478         */
    479        public void setGradientPaintTransformer(
    480                GradientPaintTransformer transformer) {
    481            this.gradientPaintTransformer = transformer;
    482            fireChangeEvent();
    483        }
    484
    485        /**
    486         * Returns the shape used to represent bars in each legend item.
    487         *
    488         * @return The shape used to represent bars in each legend item (never
    489         *         <code>null</code>).
    490         *
    491         * @see #setLegendBar(Shape)
    492         */
    493        public Shape getLegendBar() {
    494            return this.legendBar;
    495        }
    496
    497        /**
    498         * Sets the shape used to represent bars in each legend item and sends a
    499         * {@link RendererChangeEvent} to all registered listeners.
    500         *
    501         * @param bar  the bar shape (<code>null</code> not permitted).
    502         *
    503         * @see #getLegendBar()
    504         */
    505        public void setLegendBar(Shape bar) {
    506            if (bar == null) {
    507                throw new IllegalArgumentException("Null 'bar' argument.");
    508            }
    509            this.legendBar = bar;
    510            fireChangeEvent();
    511        }
    512
    513        /**
    514         * Returns the fallback position for positive item labels that don't fit
    515         * within a bar.
    516         *
    517         * @return The fallback position (<code>null</code> possible).
    518         *
    519         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    520         * @since 1.0.2
    521         */
    522        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    523            return this.positiveItemLabelPositionFallback;
    524        }
    525
    526        /**
    527         * Sets the fallback position for positive item labels that don't fit
    528         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    529         * listeners.
    530         *
    531         * @param position  the position (<code>null</code> permitted).
    532         *
    533         * @see #getPositiveItemLabelPositionFallback()
    534         * @since 1.0.2
    535         */
    536        public void setPositiveItemLabelPositionFallback(
    537                ItemLabelPosition position) {
    538            this.positiveItemLabelPositionFallback = position;
    539            fireChangeEvent();
    540        }
    541
    542        /**
    543         * Returns the fallback position for negative item labels that don't fit
    544         * within a bar.
    545         *
    546         * @return The fallback position (<code>null</code> possible).
    547         *
    548         * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition)
    549         * @since 1.0.2
    550         */
    551        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    552            return this.negativeItemLabelPositionFallback;
    553        }
    554
    555        /**
    556         * Sets the fallback position for negative item labels that don't fit
    557         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    558         * listeners.
    559         *
    560         * @param position  the position (<code>null</code> permitted).
    561         *
    562         * @see #getNegativeItemLabelPositionFallback()
    563         * @since 1.0.2
    564         */
    565        public void setNegativeItemLabelPositionFallback(
    566                ItemLabelPosition position) {
    567            this.negativeItemLabelPositionFallback = position;
    568            fireChangeEvent();
    569        }
    570
    571        /**
    572         * Returns the bar painter.
    573         *
    574         * @return The bar painter (never <code>null</code>).
    575         *
    576         * @since 1.0.11
    577         */
    578        public XYBarPainter getBarPainter() {
    579            return this.barPainter;
    580        }
    581
    582        /**
    583         * Sets the bar painter and sends a {@link RendererChangeEvent} to all
    584         * registered listeners.
    585         *
    586         * @param painter  the painter (<code>null</code> not permitted).
    587         *
    588         * @since 1.0.11
    589         */
    590        public void setBarPainter(XYBarPainter painter) {
    591            if (painter == null) {
    592                throw new IllegalArgumentException("Null 'painter' argument.");
    593            }
    594            this.barPainter = painter;
    595            fireChangeEvent();
    596        }
    597
    598        /**
    599         * Returns the flag that controls whether or not shadows are drawn for
    600         * the bars.
    601         *
    602         * @return A boolean.
    603         *
    604         * @since 1.0.11
    605         */
    606        public boolean getShadowsVisible() {
    607            return this.shadowsVisible;
    608        }
    609
    610        /**
    611         * Sets the flag that controls whether or not the renderer
    612         * draws shadows for the bars, and sends a
    613         * {@link RendererChangeEvent} to all registered listeners.
    614         *
    615         * @param visible  the new flag value.
    616         *
    617         * @since 1.0.11
    618         */
    619        public void setShadowVisible(boolean visible) {
    620            this.shadowsVisible = visible;
    621            fireChangeEvent();
    622        }
    623
    624        /**
    625         * Returns the shadow x-offset.
    626         *
    627         * @return The shadow x-offset.
    628         *
    629         * @since 1.0.11
    630         */
    631        public double getShadowXOffset() {
    632            return this.shadowXOffset;
    633        }
    634
    635        /**
    636         * Sets the x-offset for the bar shadow and sends a
    637         * {@link RendererChangeEvent} to all registered listeners.
    638         *
    639         * @param offset  the offset.
    640         *
    641         * @since 1.0.11
    642         */
    643        public void setShadowXOffset(double offset) {
    644            this.shadowXOffset = offset;
    645            fireChangeEvent();
    646        }
    647
    648        /**
    649         * Returns the shadow y-offset.
    650         *
    651         * @return The shadow y-offset.
    652         *
    653         * @since 1.0.11
    654         */
    655        public double getShadowYOffset() {
    656            return this.shadowYOffset;
    657        }
    658
    659        /**
    660         * Sets the y-offset for the bar shadow and sends a
    661         * {@link RendererChangeEvent} to all registered listeners.
    662         *
    663         * @param offset  the offset.
    664         *
    665         * @since 1.0.11
    666         */
    667        public void setShadowYOffset(double offset) {
    668            this.shadowYOffset = offset;
    669            fireChangeEvent();
    670        }
    671
    672        /**
    673         * Returns the bar alignment factor.
    674         *
    675         * @return The bar alignment factor.
    676         *
    677         * @since 1.0.13
    678         */
    679        public double getBarAlignmentFactor() {
    680            return this.barAlignmentFactor;
    681        }
    682
    683        /**
    684         * Sets the bar alignment factor and sends a {@link RendererChangeEvent}
    685         * to all registered listeners.  If the alignment factor is outside the
    686         * range 0.0 to 1.0, no alignment will be performed by the renderer.
    687         *
    688         * @param factor  the factor.
    689         *
    690         * @since 1.0.13
    691         */
    692        public void setBarAlignmentFactor(double factor) {
    693            this.barAlignmentFactor = factor;
    694            fireChangeEvent();
    695        }
    696
    697        /**
    698         * Initialises the renderer and returns a state object that should be
    699         * passed to all subsequent calls to the drawItem() method.  Here we
    700         * calculate the Java2D y-coordinate for zero, since all the bars have
    701         * their bases fixed at zero.
    702         *
    703         * @param g2  the graphics device.
    704         * @param dataArea  the area inside the axes.
    705         * @param plot  the plot.
    706         * @param dataset  the data.
    707         * @param info  an optional info collection object to return data back to
    708         *              the caller.
    709         *
    710         * @return A state object.
    711         */
    712        public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
    713                XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
    714
    715            XYBarRendererState state = new XYBarRendererState(info);
    716            ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf(
    717                    dataset));
    718            state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea,
    719                    plot.getRangeAxisEdge()));
    720            return state;
    721
    722        }
    723
    724        /**
    725         * Returns a default legend item for the specified series.  Subclasses
    726         * should override this method to generate customised items.
    727         *
    728         * @param datasetIndex  the dataset index (zero-based).
    729         * @param series  the series index (zero-based).
    730         *
    731         * @return A legend item for the series.
    732         */
    733        public LegendItem getLegendItem(int datasetIndex, int series) {
    734            LegendItem result = null;
    735            XYPlot xyplot = getPlot();
    736            if (xyplot != null) {
    737                XYDataset dataset = xyplot.getDataset(datasetIndex);
    738                if (dataset != null) {
    739                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    740                    String label = lg.generateLabel(dataset, series);
    741                    String description = label;
    742                    String toolTipText = null;
    743                    if (getLegendItemToolTipGenerator() != null) {
    744                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    745                                dataset, series);
    746                    }
    747                    String urlText = null;
    748                    if (getLegendItemURLGenerator() != null) {
    749                        urlText = getLegendItemURLGenerator().generateLabel(
    750                                dataset, series);
    751                    }
    752                    Shape shape = this.legendBar;
    753                    Paint paint = lookupSeriesPaint(series);
    754                    Paint outlinePaint = lookupSeriesOutlinePaint(series);
    755                    Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    756                    if (this.drawBarOutline) {
    757                        result = new LegendItem(label, description, toolTipText,
    758                                urlText, shape, paint, outlineStroke, outlinePaint);
    759                    }
    760                    else {
    761                        result = new LegendItem(label, description, toolTipText,
    762                                urlText, shape, paint);
    763                    }
    764                    result.setLabelFont(lookupLegendTextFont(series));
    765                    Paint labelPaint = lookupLegendTextPaint(series);
    766                    if (labelPaint != null) {
    767                        result.setLabelPaint(labelPaint);
    768                    }
    769                    result.setDataset(dataset);
    770                    result.setDatasetIndex(datasetIndex);
    771                    result.setSeriesKey(dataset.getSeriesKey(series));
    772                    result.setSeriesIndex(series);
    773                    if (getGradientPaintTransformer() != null) {
    774                        result.setFillPaintTransformer(
    775                                getGradientPaintTransformer());
    776                    }
    777                }
    778            }
    779            return result;
    780        }
    781
    782        /**
    783         * Draws the visual representation of a single data item.
    784         *
    785         * @param g2  the graphics device.
    786         * @param state  the renderer state.
    787         * @param dataArea  the area within which the plot is being drawn.
    788         * @param info  collects information about the drawing.
    789         * @param plot  the plot (can be used to obtain standard color
    790         *              information etc).
    791         * @param domainAxis  the domain axis.
    792         * @param rangeAxis  the range axis.
    793         * @param dataset  the dataset.
    794         * @param series  the series index (zero-based).
    795         * @param item  the item index (zero-based).
    796         * @param crosshairState  crosshair information for the plot
    797         *                        (<code>null</code> permitted).
    798         * @param pass  the pass index.
    799         */
    800        public void drawItem(Graphics2D g2,
    801                             XYItemRendererState state,
    802                             Rectangle2D dataArea,
    803                             PlotRenderingInfo info,
    804                             XYPlot plot,
    805                             ValueAxis domainAxis,
    806                             ValueAxis rangeAxis,
    807                             XYDataset dataset,
    808                             int series,
    809                             int item,
    810                             CrosshairState crosshairState,
    811                             int pass) {
    812
    813            if (!getItemVisible(series, item)) {
    814                return;
    815            }
    816            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    817
    818            double value0;
    819            double value1;
    820            if (this.useYInterval) {
    821                value0 = intervalDataset.getStartYValue(series, item);
    822                value1 = intervalDataset.getEndYValue(series, item);
    823            }
    824            else {
    825                value0 = this.base;
    826                value1 = intervalDataset.getYValue(series, item);
    827            }
    828            if (Double.isNaN(value0) || Double.isNaN(value1)) {
    829                return;
    830            }
    831            if (value0 <= value1) {
    832                if (!rangeAxis.getRange().intersects(value0, value1)) {
    833                    return;
    834                }
    835            }
    836            else {
    837                if (!rangeAxis.getRange().intersects(value1, value0)) {
    838                    return;
    839                }
    840            }
    841
    842            double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea,
    843                    plot.getRangeAxisEdge());
    844            double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea,
    845                    plot.getRangeAxisEdge());
    846            double bottom = Math.min(translatedValue0, translatedValue1);
    847            double top = Math.max(translatedValue0, translatedValue1);
    848
    849            double startX = intervalDataset.getStartXValue(series, item);
    850            if (Double.isNaN(startX)) {
    851                return;
    852            }
    853            double endX = intervalDataset.getEndXValue(series, item);
    854            if (Double.isNaN(endX)) {
    855                return;
    856            }
    857            if (startX <= endX) {
    858                if (!domainAxis.getRange().intersects(startX, endX)) {
    859                    return;
    860                }
    861            }
    862            else {
    863                if (!domainAxis.getRange().intersects(endX, startX)) {
    864                    return;
    865                }
    866            }
    867
    868            // is there an alignment adjustment to be made?
    869            if (this.barAlignmentFactor >= 0.0 && this.barAlignmentFactor <= 1.0) {
    870                double x = intervalDataset.getXValue(series, item);
    871                double interval = endX - startX;
    872                startX = x - interval * this.barAlignmentFactor;
    873                endX = startX + interval;
    874            }
    875
    876            RectangleEdge location = plot.getDomainAxisEdge();
    877            double translatedStartX = domainAxis.valueToJava2D(startX, dataArea,
    878                    location);
    879            double translatedEndX = domainAxis.valueToJava2D(endX, dataArea,
    880                    location);
    881
    882            double translatedWidth = Math.max(1, Math.abs(translatedEndX
    883                    - translatedStartX));
    884
    885            double left = Math.min(translatedStartX, translatedEndX);
    886            if (getMargin() > 0.0) {
    887                double cut = translatedWidth * getMargin();
    888                translatedWidth = translatedWidth - cut;
    889                left = left + cut / 2;
    890            }
    891
    892            Rectangle2D bar = null;
    893            PlotOrientation orientation = plot.getOrientation();
    894            if (orientation == PlotOrientation.HORIZONTAL) {
    895                // clip left and right bounds to data area
    896                bottom = Math.max(bottom, dataArea.getMinX());
    897                top = Math.min(top, dataArea.getMaxX());
    898                bar = new Rectangle2D.Double(
    899                    bottom, left, top - bottom, translatedWidth);
    900            }
    901            else if (orientation == PlotOrientation.VERTICAL) {
    902                // clip top and bottom bounds to data area
    903                bottom = Math.max(bottom, dataArea.getMinY());
    904                top = Math.min(top, dataArea.getMaxY());
    905                bar = new Rectangle2D.Double(left, bottom, translatedWidth,
    906                        top - bottom);
    907            }
    908
    909            boolean positive = (value1 > 0.0);
    910            boolean inverted = rangeAxis.isInverted();
    911            RectangleEdge barBase;
    912            if (orientation == PlotOrientation.HORIZONTAL) {
    913                if (positive && inverted || !positive && !inverted) {
    914                    barBase = RectangleEdge.RIGHT;
    915                }
    916                else {
    917                    barBase = RectangleEdge.LEFT;
    918                }
    919            }
    920            else {
    921                if (positive && !inverted || !positive && inverted) {
    922                    barBase = RectangleEdge.BOTTOM;
    923                }
    924                else {
    925                    barBase = RectangleEdge.TOP;
    926                }
    927            }
    928            if (getShadowsVisible()) {
    929                this.barPainter.paintBarShadow(g2, this, series, item, bar, barBase,
    930                    !this.useYInterval);
    931            }
    932            this.barPainter.paintBar(g2, this, series, item, bar, barBase);
    933
    934            if (isItemLabelVisible(series, item)) {
    935                XYItemLabelGenerator generator = getItemLabelGenerator(series,
    936                        item);
    937                drawItemLabel(g2, dataset, series, item, plot, generator, bar,
    938                        value1 < 0.0);
    939            }
    940
    941            // update the crosshair point
    942            double x1 = (startX + endX) / 2.0;
    943            double y1 = dataset.getYValue(series, item);
    944            double transX1 = domainAxis.valueToJava2D(x1, dataArea, location);
    945            double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    946                    plot.getRangeAxisEdge());
    947            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    948            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    949            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    950                    rangeAxisIndex, transX1, transY1, plot.getOrientation());
    951
    952            EntityCollection entities = state.getEntityCollection();
    953            if (entities != null) {
    954                addEntity(entities, bar, dataset, series, item, 0.0, 0.0);
    955            }
    956
    957        }
    958
    959        /**
    960         * Draws an item label.  This method is provided as an alternative to
    961         * {@link #drawItemLabel(Graphics2D, PlotOrientation, XYDataset, int, int,
    962         * double, double, boolean)} so that the bar can be used to calculate the
    963         * label anchor point.
    964         *
    965         * @param g2  the graphics device.
    966         * @param dataset  the dataset.
    967         * @param series  the series index.
    968         * @param item  the item index.
    969         * @param plot  the plot.
    970         * @param generator  the label generator (<code>null</code> permitted, in
    971         *         which case the method does nothing, just returns).
    972         * @param bar  the bar.
    973         * @param negative  a flag indicating a negative value.
    974         */
    975        protected void drawItemLabel(Graphics2D g2, XYDataset dataset,
    976                int series, int item, XYPlot plot, XYItemLabelGenerator generator,
    977                Rectangle2D bar, boolean negative) {
    978
    979            if (generator == null) {
    980                return;  // nothing to do
    981            }
    982            String label = generator.generateLabel(dataset, series, item);
    983            if (label == null) {
    984                return;  // nothing to do
    985            }
    986
    987            Font labelFont = getItemLabelFont(series, item);
    988            g2.setFont(labelFont);
    989            Paint paint = getItemLabelPaint(series, item);
    990            g2.setPaint(paint);
    991
    992            // find out where to place the label...
    993            ItemLabelPosition position = null;
    994            if (!negative) {
    995                position = getPositiveItemLabelPosition(series, item);
    996            }
    997            else {
    998                position = getNegativeItemLabelPosition(series, item);
    999            }
    1000
    1001            // work out the label anchor point...
    1002            Point2D anchorPoint = calculateLabelAnchorPoint(
    1003                    position.getItemLabelAnchor(), bar, plot.getOrientation());
    1004
    1005            if (isInternalAnchor(position.getItemLabelAnchor())) {
    1006                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
    1007                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    1008                        position.getTextAnchor(), position.getAngle(),
    1009                        position.getRotationAnchor());
    1010
    1011                if (bounds != null) {
    1012                    if (!bar.contains(bounds.getBounds2D())) {
    1013                        if (!negative) {
    1014                            position = getPositiveItemLabelPositionFallback();
    1015                        }
    1016                        else {
    1017                            position = getNegativeItemLabelPositionFallback();
    1018                        }
    1019                        if (position != null) {
    1020                            anchorPoint = calculateLabelAnchorPoint(
    1021                                    position.getItemLabelAnchor(), bar,
    1022                                    plot.getOrientation());
    1023                        }
    1024                    }
    1025                }
    1026
    1027            }
    1028
    1029            if (position != null) {
    1030                TextUtilities.drawRotatedString(label, g2,
    1031                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    1032                        position.getTextAnchor(), position.getAngle(),
    1033                        position.getRotationAnchor());
    1034            }
    1035        }
    1036
    1037        /**
    1038         * Calculates the item label anchor point.
    1039         *
    1040         * @param anchor  the anchor.
    1041         * @param bar  the bar.
    1042         * @param orientation  the plot orientation.
    1043         *
    1044         * @return The anchor point.
    1045         */
    1046        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    1047                Rectangle2D bar, PlotOrientation orientation) {
    1048
    1049            Point2D result = null;
    1050            double offset = getItemLabelAnchorOffset();
    1051            double x0 = bar.getX() - offset;
    1052            double x1 = bar.getX();
    1053            double x2 = bar.getX() + offset;
    1054            double x3 = bar.getCenterX();
    1055            double x4 = bar.getMaxX() - offset;
    1056            double x5 = bar.getMaxX();
    1057            double x6 = bar.getMaxX() + offset;
    1058
    1059            double y0 = bar.getMaxY() + offset;
    1060            double y1 = bar.getMaxY();
    1061            double y2 = bar.getMaxY() - offset;
    1062            double y3 = bar.getCenterY();
    1063            double y4 = bar.getMinY() + offset;
    1064            double y5 = bar.getMinY();
    1065            double y6 = bar.getMinY() - offset;
    1066
    1067            if (anchor == ItemLabelAnchor.CENTER) {
    1068                result = new Point2D.Double(x3, y3);
    1069            }
    1070            else if (anchor == ItemLabelAnchor.INSIDE1) {
    1071                result = new Point2D.Double(x4, y4);
    1072            }
    1073            else if (anchor == ItemLabelAnchor.INSIDE2) {
    1074                result = new Point2D.Double(x4, y4);
    1075            }
    1076            else if (anchor == ItemLabelAnchor.INSIDE3) {
    1077                result = new Point2D.Double(x4, y3);
    1078            }
    1079            else if (anchor == ItemLabelAnchor.INSIDE4) {
    1080                result = new Point2D.Double(x4, y2);
    1081            }
    1082            else if (anchor == ItemLabelAnchor.INSIDE5) {
    1083                result = new Point2D.Double(x4, y2);
    1084            }
    1085            else if (anchor == ItemLabelAnchor.INSIDE6) {
    1086                result = new Point2D.Double(x3, y2);
    1087            }
    1088            else if (anchor == ItemLabelAnchor.INSIDE7) {
    1089                result = new Point2D.Double(x2, y2);
    1090            }
    1091            else if (anchor == ItemLabelAnchor.INSIDE8) {
    1092                result = new Point2D.Double(x2, y2);
    1093            }
    1094            else if (anchor == ItemLabelAnchor.INSIDE9) {
    1095                result = new Point2D.Double(x2, y3);
    1096            }
    1097            else if (anchor == ItemLabelAnchor.INSIDE10) {
    1098                result = new Point2D.Double(x2, y4);
    1099            }
    1100            else if (anchor == ItemLabelAnchor.INSIDE11) {
    1101                result = new Point2D.Double(x2, y4);
    1102            }
    1103            else if (anchor == ItemLabelAnchor.INSIDE12) {
    1104                result = new Point2D.Double(x3, y4);
    1105            }
    1106            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    1107                result = new Point2D.Double(x5, y6);
    1108            }
    1109            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    1110                result = new Point2D.Double(x6, y5);
    1111            }
    1112            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    1113                result = new Point2D.Double(x6, y3);
    1114            }
    1115            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    1116                result = new Point2D.Double(x6, y1);
    1117            }
    1118            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    1119                result = new Point2D.Double(x5, y0);
    1120            }
    1121            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    1122                result = new Point2D.Double(x3, y0);
    1123            }
    1124            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    1125                result = new Point2D.Double(x1, y0);
    1126            }
    1127            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    1128                result = new Point2D.Double(x0, y1);
    1129            }
    1130            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    1131                result = new Point2D.Double(x0, y3);
    1132            }
    1133            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    1134                result = new Point2D.Double(x0, y5);
    1135            }
    1136            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    1137                result = new Point2D.Double(x1, y6);
    1138            }
    1139            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    1140                result = new Point2D.Double(x3, y6);
    1141            }
    1142
    1143            return result;
    1144
    1145        }
    1146
    1147        /**
    1148         * Returns <code>true</code> if the specified anchor point is inside a bar.
    1149         *
    1150         * @param anchor  the anchor point.
    1151         *
    1152         * @return A boolean.
    1153         */
    1154        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    1155            return anchor == ItemLabelAnchor.CENTER
    1156                   || anchor == ItemLabelAnchor.INSIDE1
    1157                   || anchor == ItemLabelAnchor.INSIDE2
    1158                   || anchor == ItemLabelAnchor.INSIDE3
    1159                   || anchor == ItemLabelAnchor.INSIDE4
    1160                   || anchor == ItemLabelAnchor.INSIDE5
    1161                   || anchor == ItemLabelAnchor.INSIDE6
    1162                   || anchor == ItemLabelAnchor.INSIDE7
    1163                   || anchor == ItemLabelAnchor.INSIDE8
    1164                   || anchor == ItemLabelAnchor.INSIDE9
    1165                   || anchor == ItemLabelAnchor.INSIDE10
    1166                   || anchor == ItemLabelAnchor.INSIDE11
    1167                   || anchor == ItemLabelAnchor.INSIDE12;
    1168        }
    1169
    1170        /**
    1171         * Returns the lower and upper bounds (range) of the x-values in the
    1172         * specified dataset.  Since this renderer uses the x-interval in the
    1173         * dataset, this is taken into account for the range.
    1174         *
    1175         * @param dataset  the dataset (<code>null</code> permitted).
    1176         *
    1177         * @return The range (<code>null</code> if the dataset is
    1178         *         <code>null</code> or empty).
    1179         */
    1180        public Range findDomainBounds(XYDataset dataset) {
    1181            if (dataset != null) {
    1182                return DatasetUtilities.findDomainBounds(dataset, true);
    1183            }
    1184            else {
    1185                return null;
    1186            }
    1187        }
    1188
    1189        /**
    1190         * Returns the lower and upper bounds (range) of the y-values in the
    1191         * specified dataset.  If the renderer is plotting the y-interval from the
    1192         * dataset, this is taken into account for the range.
    1193         *
    1194         * @param dataset  the dataset (<code>null</code> permitted).
    1195         *
    1196         * @return The range (<code>null</code> if the dataset is
    1197         *         <code>null</code> or empty).
    1198         */
    1199        public Range findRangeBounds(XYDataset dataset) {
    1200            if (dataset != null) {
    1201                return DatasetUtilities.findRangeBounds(dataset,
    1202                        this.useYInterval);
    1203            }
    1204            else {
    1205                return null;
    1206            }
    1207        }
    1208
    1209        /**
    1210         * Returns a clone of the renderer.
    1211         *
    1212         * @return A clone.
    1213         *
    1214         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    1215         */
    1216        public Object clone() throws CloneNotSupportedException {
    1217            XYBarRenderer result = (XYBarRenderer) super.clone();
    1218            if (this.gradientPaintTransformer != null) {
    1219                result.gradientPaintTransformer = (GradientPaintTransformer)
    1220                    ObjectUtilities.clone(this.gradientPaintTransformer);
    1221            }
    1222            result.legendBar = ShapeUtilities.clone(this.legendBar);
    1223            return result;
    1224        }
    1225
    1226        /**
    1227         * Tests this renderer for equality with an arbitrary object.
    1228         *
    1229         * @param obj  the object to test against (<code>null</code> permitted).
    1230         *
    1231         * @return A boolean.
    1232         */
    1233        public boolean equals(Object obj) {
    1234            if (obj == this) {
    1235                return true;
    1236            }
    1237            if (!(obj instanceof XYBarRenderer)) {
    1238                return false;
    1239            }
    1240            XYBarRenderer that = (XYBarRenderer) obj;
    1241            if (this.base != that.base) {
    1242                return false;
    1243            }
    1244            if (this.drawBarOutline != that.drawBarOutline) {
    1245                return false;
    1246            }
    1247            if (this.margin != that.margin) {
    1248                return false;
    1249            }
    1250            if (this.useYInterval != that.useYInterval) {
    1251                return false;
    1252            }
    1253            if (!ObjectUtilities.equal(
    1254                this.gradientPaintTransformer, that.gradientPaintTransformer)
    1255            ) {
    1256                return false;
    1257            }
    1258            if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) {
    1259                return false;
    1260            }
    1261            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    1262                    that.positiveItemLabelPositionFallback)) {
    1263                return false;
    1264            }
    1265            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    1266                    that.negativeItemLabelPositionFallback)) {
    1267                return false;
    1268            }
    1269            if (!this.barPainter.equals(that.barPainter)) {
    1270                return false;
    1271            }
    1272            if (this.shadowsVisible != that.shadowsVisible) {
    1273                return false;
    1274            }
    1275            if (this.shadowXOffset != that.shadowXOffset) {
    1276                return false;
    1277            }
    1278            if (this.shadowYOffset != that.shadowYOffset) {
    1279                return false;
    1280            }
    1281            if (this.barAlignmentFactor != that.barAlignmentFactor) {
    1282                return false;
    1283            }
    1284            return super.equals(obj);
    1285        }
    1286
    1287        /**
    1288         * Provides serialization support.
    1289         *
    1290         * @param stream  the input stream.
    1291         *
    1292         * @throws IOException  if there is an I/O error.
    1293         * @throws ClassNotFoundException  if there is a classpath problem.
    1294         */
    1295        private void readObject(ObjectInputStream stream)
    1296                throws IOException, ClassNotFoundException {
    1297            stream.defaultReadObject();
    1298            this.legendBar = SerialUtilities.readShape(stream);
    1299        }
    1300
    1301        /**
    1302         * Provides serialization support.
    1303         *
    1304         * @param stream  the output stream.
    1305         *
    1306         * @throws IOException  if there is an I/O error.
    1307         */
    1308        private void writeObject(ObjectOutputStream stream) throws IOException {
    1309            stream.defaultWriteObject();
    1310            SerialUtilities.writeShape(this.legendBar, stream);
    1311        }
    1312
    1313    }