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