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