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