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