    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    102package org.jfree.chart.renderer.xy;
    103
    104import java.awt.Font;
    105import java.awt.Graphics2D;
    106import java.awt.Paint;
    107import java.awt.Shape;
    108import java.awt.Stroke;
    109import java.awt.geom.Point2D;
    110import java.awt.geom.Rectangle2D;
    111import java.io.IOException;
    112import java.io.ObjectInputStream;
    113import java.io.ObjectOutputStream;
    114import java.io.Serializable;
    115
    116import org.jfree.chart.LegendItem;
    117import org.jfree.chart.axis.ValueAxis;
    118import org.jfree.chart.entity.EntityCollection;
    119import org.jfree.chart.event.RendererChangeEvent;
    120import org.jfree.chart.labels.ItemLabelAnchor;
    121import org.jfree.chart.labels.ItemLabelPosition;
    122import org.jfree.chart.labels.XYItemLabelGenerator;
    123import org.jfree.chart.labels.XYSeriesLabelGenerator;
    124import org.jfree.chart.plot.CrosshairState;
    125import org.jfree.chart.plot.PlotOrientation;
    126import org.jfree.chart.plot.PlotRenderingInfo;
    127import org.jfree.chart.plot.XYPlot;
    128import org.jfree.data.Range;
    129import org.jfree.data.general.DatasetUtilities;
    130import org.jfree.data.xy.IntervalXYDataset;
    131import org.jfree.data.xy.XYDataset;
    132import org.jfree.io.SerialUtilities;
    133import org.jfree.text.TextUtilities;
    134import org.jfree.ui.GradientPaintTransformer;
    135import org.jfree.ui.RectangleEdge;
    136import org.jfree.ui.StandardGradientPaintTransformer;
    137import org.jfree.util.ObjectUtilities;
    138import org.jfree.util.PublicCloneable;
    139import 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 */
    150public 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(this.gradientPaintTransformer,
    1254                that.gradientPaintTransformer)) {
    1255            return false;
    1256        }
    1257        if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) {
    1258            return false;
    1259        }
    1260        if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    1261                that.positiveItemLabelPositionFallback)) {
    1262            return false;
    1263        }
    1264        if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    1265                that.negativeItemLabelPositionFallback)) {
    1266            return false;
    1267        }
    1268        if (!this.barPainter.equals(that.barPainter)) {
    1269            return false;
    1270        }
    1271        if (this.shadowsVisible != that.shadowsVisible) {
    1272            return false;
    1273        }
    1274        if (this.shadowXOffset != that.shadowXOffset) {
    1275            return false;
    1276        }
    1277        if (this.shadowYOffset != that.shadowYOffset) {
    1278            return false;
    1279        }
    1280        if (this.barAlignmentFactor != that.barAlignmentFactor) {
    1281            return false;
    1282        }
    1283        return super.equals(obj);
    1284    }
    1285
    1286    /**
    1287     * Provides serialization support.
    1288     *
    1289     * @param stream  the input stream.
    1290     *
    1291     * @throws IOException  if there is an I/O error.
    1292     * @throws ClassNotFoundException  if there is a classpath problem.
    1293     */
    1294    private void readObject(ObjectInputStream stream)
    1295            throws IOException, ClassNotFoundException {
    1296        stream.defaultReadObject();
    1297        this.legendBar = SerialUtilities.readShape(stream);
    1298    }
    1299
    1300    /**
    1301     * Provides serialization support.
    1302     *
    1303     * @param stream  the output stream.
    1304     *
    1305     * @throws IOException  if there is an I/O error.
    1306     */
    1307    private void writeObject(ObjectOutputStream stream) throws IOException {
    1308        stream.defaultWriteObject();
    1309        SerialUtilities.writeShape(this.legendBar, stream);
    1310    }
    1311
    1312}