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     * BarRenderer.java
029     * ----------------
030     * (C) Copyright 2002-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Christian W. Zuckschwerdt;
034     *
035     * $Id: BarRenderer.java,v 1.13.2.18 2007/05/18 10:28:27 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 14-Mar-2002 : Version 1 (DG);
040     * 23-May-2002 : Added tooltip generator to renderer (DG);
041     * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
042     * 25-Jun-2002 : Changed constructor to protected and removed redundant 
043     *               code (DG);
044     * 26-Jun-2002 : Added axis to initialise method, and record upper and lower 
045     *               clip values (DG);
046     * 24-Sep-2002 : Added getLegendItem() method (DG);
047     * 09-Oct-2002 : Modified constructor to include URL generator (DG);
048     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
049     * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
050     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
051     * 25-Mar-2003 : Implemented Serializable (DG);
052     * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
053     * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
054     * 12-Jun-2003 : Updates for item labels (DG);
055     * 30-Jul-2003 : Modified entity constructor (CZ);
056     * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
057     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
058     * 07-Oct-2003 : Added renderer state (DG);
059     * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem() 
060     *               methods (DG);
061     * 28-Oct-2003 : Added support for gradient paint on bars (DG);
062     * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
063     * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste 
064     *               overriding (DG);
065     * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item 
066     *               label generators.  Fixed equals() method (DG);
067     * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
068     * 05-Nov-2004 : Modified drawItem() signature (DG);
069     * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
070     * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
071     * 18-May-2005 : Added configurable base value (DG);
072     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
073     * 01-Dec-2005 : Update legend item to use/not use outline (DG);
074     * ------------: JFreeChart 1.0.x ---------------------------------------------
075     * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
076     * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
077     * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value 
078     *               bars) (DG);
079     * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
080     * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
081     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
082     * 11-May-2007 : Check for visibility in getLegendItem() (DG);
083     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
084     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
085     * 
086     */
087    
088    package org.jfree.chart.renderer.category;
089    
090    import java.awt.BasicStroke;
091    import java.awt.Color;
092    import java.awt.Font;
093    import java.awt.GradientPaint;
094    import java.awt.Graphics2D;
095    import java.awt.Paint;
096    import java.awt.Shape;
097    import java.awt.Stroke;
098    import java.awt.geom.Line2D;
099    import java.awt.geom.Point2D;
100    import java.awt.geom.Rectangle2D;
101    import java.io.Serializable;
102    
103    import org.jfree.chart.LegendItem;
104    import org.jfree.chart.axis.CategoryAxis;
105    import org.jfree.chart.axis.ValueAxis;
106    import org.jfree.chart.entity.EntityCollection;
107    import org.jfree.chart.event.RendererChangeEvent;
108    import org.jfree.chart.labels.CategoryItemLabelGenerator;
109    import org.jfree.chart.labels.ItemLabelAnchor;
110    import org.jfree.chart.labels.ItemLabelPosition;
111    import org.jfree.chart.plot.CategoryPlot;
112    import org.jfree.chart.plot.PlotOrientation;
113    import org.jfree.chart.plot.PlotRenderingInfo;
114    import org.jfree.data.Range;
115    import org.jfree.data.category.CategoryDataset;
116    import org.jfree.data.general.DatasetUtilities;
117    import org.jfree.text.TextUtilities;
118    import org.jfree.ui.GradientPaintTransformer;
119    import org.jfree.ui.RectangleEdge;
120    import org.jfree.ui.StandardGradientPaintTransformer;
121    import org.jfree.util.ObjectUtilities;
122    import org.jfree.util.PublicCloneable;
123    
124    /**
125     * A {@link CategoryItemRenderer} that draws individual data items as bars.
126     */
127    public class BarRenderer extends AbstractCategoryItemRenderer 
128                             implements Cloneable, PublicCloneable, Serializable {
129    
130        /** For serialization. */
131        private static final long serialVersionUID = 6000649414965887481L;
132        
133        /** The default item margin percentage. */
134        public static final double DEFAULT_ITEM_MARGIN = 0.20;
135    
136        /** 
137         * Constant that controls the minimum width before a bar has an outline 
138         * drawn. 
139         */
140        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
141    
142        /** The margin between items (bars) within a category. */
143        private double itemMargin;
144    
145        /** A flag that controls whether or not bar outlines are drawn. */
146        private boolean drawBarOutline;
147        
148        /** The maximum bar width as a percentage of the available space. */
149        private double maximumBarWidth;
150        
151        /** The minimum bar length (in Java2D units). */
152        private double minimumBarLength;
153        
154        /** 
155         * An optional class used to transform gradient paint objects to fit each 
156         * bar. 
157         */
158        private GradientPaintTransformer gradientPaintTransformer;
159        
160        /** 
161         * The fallback position if a positive item label doesn't fit inside the 
162         * bar. 
163         */
164        private ItemLabelPosition positiveItemLabelPositionFallback;
165        
166        /** 
167         * The fallback position if a negative item label doesn't fit inside the 
168         * bar. 
169         */
170        private ItemLabelPosition negativeItemLabelPositionFallback;
171        
172        /** The upper clip (axis) value for the axis. */
173        private double upperClip;  
174        // TODO:  this needs to move into the renderer state
175    
176        /** The lower clip (axis) value for the axis. */
177        private double lowerClip;  
178        // TODO:  this needs to move into the renderer state
179    
180        /** The base value for the bars (defaults to 0.0). */
181        private double base;
182        
183        /** 
184         * A flag that controls whether the base value is included in the range
185         * returned by the findRangeBounds() method.
186         */
187        private boolean includeBaseInRange;
188        
189        /**
190         * Creates a new bar renderer with default settings.
191         */
192        public BarRenderer() {
193            super();
194            this.base = 0.0;
195            this.includeBaseInRange = true;
196            this.itemMargin = DEFAULT_ITEM_MARGIN;
197            this.drawBarOutline = true;
198            this.maximumBarWidth = 1.0;  
199                // 100 percent, so it will not apply unless changed
200            this.positiveItemLabelPositionFallback = null;
201            this.negativeItemLabelPositionFallback = null;
202            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
203            this.minimumBarLength = 0.0;
204        }
205    
206        /**
207         * Returns the base value for the bars.  The default value is 
208         * <code>0.0</code>.
209         * 
210         * @return The base value for the bars.
211         * 
212         * @see #setBase(double)
213         */
214        public double getBase() {
215            return this.base;    
216        }
217        
218        /**
219         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
220         * to all registered listeners.
221         * 
222         * @param base  the new base value.
223         * 
224         * @see #getBase()
225         */
226        public void setBase(double base) {
227            this.base = base;
228            notifyListeners(new RendererChangeEvent(this));
229        }
230        
231        /**
232         * Returns the item margin as a percentage of the available space for all 
233         * bars.
234         *
235         * @return The margin percentage (where 0.10 is ten percent).
236         * 
237         * @see #setItemMargin(double)
238         */
239        public double getItemMargin() {
240            return this.itemMargin;
241        }
242    
243        /**
244         * Sets the item margin and sends a {@link RendererChangeEvent} to all 
245         * registered listeners.  The value is expressed as a percentage of the 
246         * available width for plotting all the bars, with the resulting amount to 
247         * be distributed between all the bars evenly.
248         *
249         * @param percent  the margin (where 0.10 is ten percent).
250         * 
251         * @see #getItemMargin()
252         */
253        public void setItemMargin(double percent) {
254            this.itemMargin = percent;
255            notifyListeners(new RendererChangeEvent(this));
256        }
257    
258        /**
259         * Returns a flag that controls whether or not bar outlines are drawn.
260         * 
261         * @return A boolean.
262         * 
263         * @see #setDrawBarOutline(boolean)
264         */
265        public boolean isDrawBarOutline() {
266            return this.drawBarOutline;    
267        }
268        
269        /**
270         * Sets the flag that controls whether or not bar outlines are drawn and 
271         * sends a {@link RendererChangeEvent} to all registered listeners.
272         * 
273         * @param draw  the flag.
274         * 
275         * @see #isDrawBarOutline()
276         */
277        public void setDrawBarOutline(boolean draw) {
278            this.drawBarOutline = draw;
279            notifyListeners(new RendererChangeEvent(this));
280        }
281        
282        /**
283         * Returns the maximum bar width, as a percentage of the available drawing 
284         * space.
285         * 
286         * @return The maximum bar width.
287         * 
288         * @see #setMaximumBarWidth(double)
289         */
290        public double getMaximumBarWidth() {
291            return this.maximumBarWidth;
292        }
293        
294        /**
295         * Sets the maximum bar width, which is specified as a percentage of the 
296         * available space for all bars, and sends a {@link RendererChangeEvent} to
297         * all registered listeners.
298         * 
299         * @param percent  the percent (where 0.05 is five percent).
300         * 
301         * @see #getMaximumBarWidth()
302         */
303        public void setMaximumBarWidth(double percent) {
304            this.maximumBarWidth = percent;
305            notifyListeners(new RendererChangeEvent(this));
306        }
307    
308        /**
309         * Returns the minimum bar length (in Java2D units).
310         * 
311         * @return The minimum bar length.
312         * 
313         * @see #setMinimumBarLength(double)
314         */
315        public double getMinimumBarLength() {
316            return this.minimumBarLength;
317        }
318        
319        /**
320         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to 
321         * all registered listeners.  The minimum bar length is specified in Java2D
322         * units, and can be used to prevent bars that represent very small data 
323         * values from disappearing when drawn on the screen.
324         * 
325         * @param min  the minimum bar length (in Java2D units).
326         * 
327         * @see #getMinimumBarLength()
328         */
329        public void setMinimumBarLength(double min) {
330            this.minimumBarLength = min;
331            notifyListeners(new RendererChangeEvent(this));
332        }
333        
334        /**
335         * Returns the gradient paint transformer (an object used to transform 
336         * gradient paint objects to fit each bar).
337         * 
338         * @return A transformer (<code>null</code> possible).
339         * 
340         * @see #setGradientPaintTransformer(GradientPaintTransformer)
341         */    
342        public GradientPaintTransformer getGradientPaintTransformer() {
343            return this.gradientPaintTransformer;    
344        }
345        
346        /**
347         * Sets the gradient paint transformer and sends a 
348         * {@link RendererChangeEvent} to all registered listeners.
349         * 
350         * @param transformer  the transformer (<code>null</code> permitted).
351         * 
352         * @see #getGradientPaintTransformer()
353         */
354        public void setGradientPaintTransformer(
355                GradientPaintTransformer transformer) {
356            this.gradientPaintTransformer = transformer;
357            notifyListeners(new RendererChangeEvent(this));
358        }
359        
360        /**
361         * Returns the fallback position for positive item labels that don't fit 
362         * within a bar.
363         * 
364         * @return The fallback position (<code>null</code> possible).
365         * 
366         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
367         */
368        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
369            return this.positiveItemLabelPositionFallback;
370        }
371        
372        /**
373         * Sets the fallback position for positive item labels that don't fit 
374         * within a bar, and sends a {@link RendererChangeEvent} to all registered
375         * listeners.
376         * 
377         * @param position  the position (<code>null</code> permitted).
378         * 
379         * @see #getPositiveItemLabelPositionFallback()
380         */
381        public void setPositiveItemLabelPositionFallback(
382                ItemLabelPosition position) {
383            this.positiveItemLabelPositionFallback = position;
384            notifyListeners(new RendererChangeEvent(this));
385        }
386        
387        /**
388         * Returns the fallback position for negative item labels that don't fit 
389         * within a bar.
390         * 
391         * @return The fallback position (<code>null</code> possible).
392         * 
393         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
394         */
395        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
396            return this.negativeItemLabelPositionFallback;
397        }
398        
399        /**
400         * Sets the fallback position for negative item labels that don't fit 
401         * within a bar, and sends a {@link RendererChangeEvent} to all registered
402         * listeners.
403         * 
404         * @param position  the position (<code>null</code> permitted).
405         * 
406         * @see #getNegativeItemLabelPositionFallback()
407         */
408        public void setNegativeItemLabelPositionFallback(
409                ItemLabelPosition position) {
410            this.negativeItemLabelPositionFallback = position;
411            notifyListeners(new RendererChangeEvent(this));
412        }
413        
414        /**
415         * Returns the flag that controls whether or not the base value for the 
416         * bars is included in the range calculated by 
417         * {@link #findRangeBounds(CategoryDataset)}.
418         * 
419         * @return <code>true</code> if the base is included in the range, and
420         *         <code>false</code> otherwise.
421         * 
422         * @since 1.0.1
423         * 
424         * @see #setIncludeBaseInRange(boolean)
425         */
426        public boolean getIncludeBaseInRange() {
427            return this.includeBaseInRange;
428        }
429        
430        /**
431         * Sets the flag that controls whether or not the base value for the bars 
432         * is included in the range calculated by 
433         * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
434         * a {@link RendererChangeEvent} is sent to all registered listeners.
435         * 
436         * @param include  the new value for the flag.
437         * 
438         * @since 1.0.1
439         * 
440         * @see #getIncludeBaseInRange()
441         */
442        public void setIncludeBaseInRange(boolean include) {
443            if (this.includeBaseInRange != include) {
444                this.includeBaseInRange = include;
445                notifyListeners(new RendererChangeEvent(this));
446            }
447        }
448        
449        /**
450         * Returns the lower clip value.  This value is recalculated in the 
451         * initialise() method.
452         *
453         * @return The value.
454         */
455        public double getLowerClip() {
456            // TODO:  this attribute should be transferred to the renderer state.
457            return this.lowerClip;
458        }
459    
460        /**
461         * Returns the upper clip value.  This value is recalculated in the 
462         * initialise() method.
463         *
464         * @return The value.
465         */
466        public double getUpperClip() {
467            // TODO:  this attribute should be transferred to the renderer state.
468            return this.upperClip;
469        }
470    
471        /**
472         * Initialises the renderer and returns a state object that will be passed 
473         * to subsequent calls to the drawItem method.  This method gets called 
474         * once at the start of the process of drawing a chart.
475         *
476         * @param g2  the graphics device.
477         * @param dataArea  the area in which the data is to be plotted.
478         * @param plot  the plot.
479         * @param rendererIndex  the renderer index.
480         * @param info  collects chart rendering information for return to caller.
481         * 
482         * @return The renderer state.
483         */
484        public CategoryItemRendererState initialise(Graphics2D g2,
485                                                    Rectangle2D dataArea,
486                                                    CategoryPlot plot,
487                                                    int rendererIndex,
488                                                    PlotRenderingInfo info) {
489    
490            CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 
491                    rendererIndex, info);
492    
493            // get the clipping values...
494            ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
495            this.lowerClip = rangeAxis.getRange().getLowerBound();
496            this.upperClip = rangeAxis.getRange().getUpperBound();
497    
498            // calculate the bar width
499            calculateBarWidth(plot, dataArea, rendererIndex, state);
500    
501            return state;
502            
503        }
504        
505        /**
506         * Calculates the bar width and stores it in the renderer state.
507         * 
508         * @param plot  the plot.
509         * @param dataArea  the data area.
510         * @param rendererIndex  the renderer index.
511         * @param state  the renderer state.
512         */
513        protected void calculateBarWidth(CategoryPlot plot, 
514                                         Rectangle2D dataArea, 
515                                         int rendererIndex,
516                                         CategoryItemRendererState state) {
517                                             
518            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
519            CategoryDataset dataset = plot.getDataset(rendererIndex);
520            if (dataset != null) {
521                int columns = dataset.getColumnCount();
522                int rows = dataset.getRowCount();
523                double space = 0.0;
524                PlotOrientation orientation = plot.getOrientation();
525                if (orientation == PlotOrientation.HORIZONTAL) {
526                    space = dataArea.getHeight();
527                }
528                else if (orientation == PlotOrientation.VERTICAL) {
529                    space = dataArea.getWidth();
530                }
531                double maxWidth = space * getMaximumBarWidth();
532                double categoryMargin = 0.0;
533                double currentItemMargin = 0.0;
534                if (columns > 1) {
535                    categoryMargin = domainAxis.getCategoryMargin();
536                }
537                if (rows > 1) {
538                    currentItemMargin = getItemMargin();
539                }
540                double used = space * (1 - domainAxis.getLowerMargin() 
541                                         - domainAxis.getUpperMargin()
542                                         - categoryMargin - currentItemMargin);
543                if ((rows * columns) > 0) {
544                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
545                }
546                else {
547                    state.setBarWidth(Math.min(used, maxWidth));
548                }
549            }
550        }
551    
552        /**
553         * Calculates the coordinate of the first "side" of a bar.  This will be 
554         * the minimum x-coordinate for a vertical bar, and the minimum 
555         * y-coordinate for a horizontal bar.
556         *
557         * @param plot  the plot.
558         * @param orientation  the plot orientation.
559         * @param dataArea  the data area.
560         * @param domainAxis  the domain axis.
561         * @param state  the renderer state (has the bar width precalculated).
562         * @param row  the row index.
563         * @param column  the column index.
564         * 
565         * @return The coordinate.
566         */
567        protected double calculateBarW0(CategoryPlot plot, 
568                                        PlotOrientation orientation, 
569                                        Rectangle2D dataArea,
570                                        CategoryAxis domainAxis,
571                                        CategoryItemRendererState state,
572                                        int row,
573                                        int column) {
574            // calculate bar width...
575            double space = 0.0;
576            if (orientation == PlotOrientation.HORIZONTAL) {
577                space = dataArea.getHeight();
578            }
579            else {
580                space = dataArea.getWidth();
581            }
582            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(), 
583                    dataArea, plot.getDomainAxisEdge());
584            int seriesCount = getRowCount();
585            int categoryCount = getColumnCount();
586            if (seriesCount > 1) {
587                double seriesGap = space * getItemMargin() 
588                                   / (categoryCount * (seriesCount - 1));
589                double seriesW = calculateSeriesWidth(space, domainAxis, 
590                        categoryCount, seriesCount);
591                barW0 = barW0 + row * (seriesW + seriesGap) 
592                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
593            }
594            else {
595                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
596                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() 
597                        / 2.0;
598            }
599            return barW0;
600        }
601        
602        /**
603         * Calculates the coordinates for the length of a single bar.
604         * 
605         * @param value  the value represented by the bar.
606         * 
607         * @return The coordinates for each end of the bar (or <code>null</code> if 
608         *         the bar is not visible for the current axis range).
609         */
610        protected double[] calculateBarL0L1(double value) {
611            double lclip = getLowerClip();
612            double uclip = getUpperClip();
613            double barLow = Math.min(this.base, value);
614            double barHigh = Math.max(this.base, value);
615            if (barHigh < lclip) {  // bar is not visible
616                return null;
617            }
618            if (barLow > uclip) {   // bar is not visible
619                return null;
620            }
621            barLow = Math.max(barLow, lclip);
622            barHigh = Math.min(barHigh, uclip);
623            return new double[] {barLow, barHigh};
624        }
625    
626        /**
627         * Returns the range of values the renderer requires to display all the 
628         * items from the specified dataset.  This takes into account the range
629         * of values in the dataset, plus the flag that determines whether or not
630         * the base value for the bars should be included in the range.
631         * 
632         * @param dataset  the dataset (<code>null</code> permitted).
633         * 
634         * @return The range (or <code>null</code> if the dataset is 
635         *         <code>null</code> or empty).
636         */
637        public Range findRangeBounds(CategoryDataset dataset) {
638            Range result = DatasetUtilities.findRangeBounds(dataset);
639            if (result != null) {
640                if (this.includeBaseInRange) {
641                    result = Range.expandToInclude(result, this.base);
642                }
643            }
644            return result;
645        }
646    
647        /**
648         * Returns a legend item for a series.
649         *
650         * @param datasetIndex  the dataset index (zero-based).
651         * @param series  the series index (zero-based).
652         *
653         * @return The legend item (possibly <code>null</code>).
654         */
655        public LegendItem getLegendItem(int datasetIndex, int series) {
656    
657            CategoryPlot cp = getPlot();
658            if (cp == null) {
659                return null;
660            }
661    
662            // check that a legend item needs to be displayed...
663            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
664                return null;
665            }
666    
667            CategoryDataset dataset = cp.getDataset(datasetIndex);
668            String label = getLegendItemLabelGenerator().generateLabel(dataset, 
669                    series);
670            String description = label;
671            String toolTipText = null; 
672            if (getLegendItemToolTipGenerator() != null) {
673                toolTipText = getLegendItemToolTipGenerator().generateLabel(
674                        dataset, series);   
675            }
676            String urlText = null;
677            if (getLegendItemURLGenerator() != null) {
678                urlText = getLegendItemURLGenerator().generateLabel(dataset, 
679                        series);   
680            }
681            Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
682            Paint paint = lookupSeriesPaint(series);
683            Paint outlinePaint = lookupSeriesOutlinePaint(series);
684            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
685    
686            LegendItem result = new LegendItem(label, description, toolTipText, 
687                    urlText, true, shape, true, paint, isDrawBarOutline(), 
688                    outlinePaint, outlineStroke, false, new Line2D.Float(), 
689                    new BasicStroke(1.0f), Color.black);
690            result.setDataset(dataset);
691            result.setDatasetIndex(datasetIndex);
692            result.setSeriesKey(dataset.getRowKey(series));
693            result.setSeriesIndex(series);
694            if (this.gradientPaintTransformer != null) {
695                result.setFillPaintTransformer(this.gradientPaintTransformer);
696            }
697            return result;
698        }
699    
700        /**
701         * Draws the bar for a single (series, category) data item.
702         *
703         * @param g2  the graphics device.
704         * @param state  the renderer state.
705         * @param dataArea  the data area.
706         * @param plot  the plot.
707         * @param domainAxis  the domain axis.
708         * @param rangeAxis  the range axis.
709         * @param dataset  the dataset.
710         * @param row  the row index (zero-based).
711         * @param column  the column index (zero-based).
712         * @param pass  the pass index.
713         */
714        public void drawItem(Graphics2D g2,
715                             CategoryItemRendererState state,
716                             Rectangle2D dataArea,
717                             CategoryPlot plot,
718                             CategoryAxis domainAxis,
719                             ValueAxis rangeAxis,
720                             CategoryDataset dataset,
721                             int row,
722                             int column,
723                             int pass) {
724    
725            // nothing is drawn for null values...
726            Number dataValue = dataset.getValue(row, column);
727            if (dataValue == null) {
728                return;
729            }
730            
731            double value = dataValue.doubleValue();
732            
733            PlotOrientation orientation = plot.getOrientation();
734            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis, 
735                    state, row, column);
736            double[] barL0L1 = calculateBarL0L1(value);
737            if (barL0L1 == null) {
738                return;  // the bar is not visible
739            }
740            
741            RectangleEdge edge = plot.getRangeAxisEdge();
742            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
743            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
744            double barL0 = Math.min(transL0, transL1);
745            double barLength = Math.max(Math.abs(transL1 - transL0), 
746                    getMinimumBarLength());
747    
748            // draw the bar...
749            Rectangle2D bar = null;
750            if (orientation == PlotOrientation.HORIZONTAL) {
751                bar = new Rectangle2D.Double(barL0, barW0, barLength, 
752                        state.getBarWidth());
753            }
754            else {
755                bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 
756                        barLength);
757            }
758            Paint itemPaint = getItemPaint(row, column);
759            GradientPaintTransformer t = getGradientPaintTransformer();
760            if (t != null && itemPaint instanceof GradientPaint) {
761                itemPaint = t.transform((GradientPaint) itemPaint, bar);
762            }
763            g2.setPaint(itemPaint);
764            g2.fill(bar);
765    
766            // draw the outline...
767            if (isDrawBarOutline() 
768                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
769                Stroke stroke = getItemOutlineStroke(row, column);
770                Paint paint = getItemOutlinePaint(row, column);
771                if (stroke != null && paint != null) {
772                    g2.setStroke(stroke);
773                    g2.setPaint(paint);
774                    g2.draw(bar);
775                }
776            }
777    
778            CategoryItemLabelGenerator generator 
779                = getItemLabelGenerator(row, column);
780            if (generator != null && isItemLabelVisible(row, column)) {
781                drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
782                        (value < 0.0));
783            }        
784    
785            // add an item entity, if this information is being collected
786            EntityCollection entities = state.getEntityCollection();
787            if (entities != null) {
788                addItemEntity(entities, dataset, row, column, bar);
789            }
790    
791        }
792    
793        /**
794         * Calculates the available space for each series.
795         * 
796         * @param space  the space along the entire axis (in Java2D units).
797         * @param axis  the category axis.
798         * @param categories  the number of categories.
799         * @param series  the number of series.
800         * 
801         * @return The width of one series.
802         */
803        protected double calculateSeriesWidth(double space, CategoryAxis axis, 
804                                              int categories, int series) {
805            double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 
806                                - axis.getUpperMargin();
807            if (categories > 1) {
808                factor = factor - axis.getCategoryMargin();
809            }
810            return (space * factor) / (categories * series);
811        }
812        
813        /**
814         * Draws an item label.  This method is overridden so that the bar can be 
815         * used to calculate the label anchor point.
816         * 
817         * @param g2  the graphics device.
818         * @param data  the dataset.
819         * @param row  the row.
820         * @param column  the column.
821         * @param plot  the plot.
822         * @param generator  the label generator.
823         * @param bar  the bar.
824         * @param negative  a flag indicating a negative value.
825         */
826        protected void drawItemLabel(Graphics2D g2,
827                                     CategoryDataset data,
828                                     int row,
829                                     int column,
830                                     CategoryPlot plot,
831                                     CategoryItemLabelGenerator generator,
832                                     Rectangle2D bar,
833                                     boolean negative) {
834                                         
835            String label = generator.generateLabel(data, row, column);
836            if (label == null) {
837                return;  // nothing to do   
838            }
839            
840            Font labelFont = getItemLabelFont(row, column);
841            g2.setFont(labelFont);
842            Paint paint = getItemLabelPaint(row, column);
843            g2.setPaint(paint);
844    
845            // find out where to place the label...
846            ItemLabelPosition position = null;
847            if (!negative) {
848                position = getPositiveItemLabelPosition(row, column);
849            }
850            else {
851                position = getNegativeItemLabelPosition(row, column);
852            }
853    
854            // work out the label anchor point...
855            Point2D anchorPoint = calculateLabelAnchorPoint(
856                    position.getItemLabelAnchor(), bar, plot.getOrientation());
857            
858            if (isInternalAnchor(position.getItemLabelAnchor())) {
859                Shape bounds = TextUtilities.calculateRotatedStringBounds(label, 
860                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
861                        position.getTextAnchor(), position.getAngle(),
862                        position.getRotationAnchor());
863                
864                if (bounds != null) {
865                    if (!bar.contains(bounds.getBounds2D())) {
866                        if (!negative) {
867                            position = getPositiveItemLabelPositionFallback();
868                        }
869                        else {
870                            position = getNegativeItemLabelPositionFallback();
871                        }
872                        if (position != null) {
873                            anchorPoint = calculateLabelAnchorPoint(
874                                    position.getItemLabelAnchor(), bar, 
875                                    plot.getOrientation());
876                        }
877                    }
878                }
879            
880            }
881            
882            if (position != null) {
883                TextUtilities.drawRotatedString(label, g2, 
884                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
885                        position.getTextAnchor(), position.getAngle(), 
886                        position.getRotationAnchor());
887            }        
888        }
889        
890        /**
891         * Calculates the item label anchor point.
892         *
893         * @param anchor  the anchor.
894         * @param bar  the bar.
895         * @param orientation  the plot orientation.
896         *
897         * @return The anchor point.
898         */
899        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
900                                                  Rectangle2D bar, 
901                                                  PlotOrientation orientation) {
902    
903            Point2D result = null;
904            double offset = getItemLabelAnchorOffset();
905            double x0 = bar.getX() - offset;
906            double x1 = bar.getX();
907            double x2 = bar.getX() + offset;
908            double x3 = bar.getCenterX();
909            double x4 = bar.getMaxX() - offset;
910            double x5 = bar.getMaxX();
911            double x6 = bar.getMaxX() + offset;
912    
913            double y0 = bar.getMaxY() + offset;
914            double y1 = bar.getMaxY();
915            double y2 = bar.getMaxY() - offset;
916            double y3 = bar.getCenterY();
917            double y4 = bar.getMinY() + offset;
918            double y5 = bar.getMinY();
919            double y6 = bar.getMinY() - offset;
920    
921            if (anchor == ItemLabelAnchor.CENTER) {
922                result = new Point2D.Double(x3, y3);
923            }
924            else if (anchor == ItemLabelAnchor.INSIDE1) {
925                result = new Point2D.Double(x4, y4);
926            }
927            else if (anchor == ItemLabelAnchor.INSIDE2) {
928                result = new Point2D.Double(x4, y4);
929            }
930            else if (anchor == ItemLabelAnchor.INSIDE3) {
931                result = new Point2D.Double(x4, y3);
932            }
933            else if (anchor == ItemLabelAnchor.INSIDE4) {
934                result = new Point2D.Double(x4, y2);
935            }
936            else if (anchor == ItemLabelAnchor.INSIDE5) {
937                result = new Point2D.Double(x4, y2);
938            }
939            else if (anchor == ItemLabelAnchor.INSIDE6) {
940                result = new Point2D.Double(x3, y2);
941            }
942            else if (anchor == ItemLabelAnchor.INSIDE7) {
943                result = new Point2D.Double(x2, y2);
944            }
945            else if (anchor == ItemLabelAnchor.INSIDE8) {
946                result = new Point2D.Double(x2, y2);
947            }
948            else if (anchor == ItemLabelAnchor.INSIDE9) {
949                result = new Point2D.Double(x2, y3);
950            }
951            else if (anchor == ItemLabelAnchor.INSIDE10) {
952                result = new Point2D.Double(x2, y4);
953            }
954            else if (anchor == ItemLabelAnchor.INSIDE11) {
955                result = new Point2D.Double(x2, y4);
956            }
957            else if (anchor == ItemLabelAnchor.INSIDE12) {
958                result = new Point2D.Double(x3, y4);
959            }
960            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
961                result = new Point2D.Double(x5, y6);
962            }
963            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
964                result = new Point2D.Double(x6, y5);
965            }
966            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
967                result = new Point2D.Double(x6, y3);
968            }
969            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
970                result = new Point2D.Double(x6, y1);
971            }
972            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
973                result = new Point2D.Double(x5, y0);
974            }
975            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
976                result = new Point2D.Double(x3, y0);
977            }
978            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
979                result = new Point2D.Double(x1, y0);
980            }
981            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
982                result = new Point2D.Double(x0, y1);
983            }
984            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
985                result = new Point2D.Double(x0, y3);
986            }
987            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
988                result = new Point2D.Double(x0, y5);
989            }
990            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
991                result = new Point2D.Double(x1, y6);
992            }
993            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
994                result = new Point2D.Double(x3, y6);
995            }
996    
997            return result;
998    
999        }
1000        
1001        /**
1002         * Returns <code>true</code> if the specified anchor point is inside a bar.
1003         * 
1004         * @param anchor  the anchor point.
1005         * 
1006         * @return A boolean.
1007         */
1008        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1009            return anchor == ItemLabelAnchor.CENTER 
1010                   || anchor == ItemLabelAnchor.INSIDE1
1011                   || anchor == ItemLabelAnchor.INSIDE2
1012                   || anchor == ItemLabelAnchor.INSIDE3
1013                   || anchor == ItemLabelAnchor.INSIDE4
1014                   || anchor == ItemLabelAnchor.INSIDE5
1015                   || anchor == ItemLabelAnchor.INSIDE6
1016                   || anchor == ItemLabelAnchor.INSIDE7
1017                   || anchor == ItemLabelAnchor.INSIDE8
1018                   || anchor == ItemLabelAnchor.INSIDE9
1019                   || anchor == ItemLabelAnchor.INSIDE10
1020                   || anchor == ItemLabelAnchor.INSIDE11
1021                   || anchor == ItemLabelAnchor.INSIDE12;  
1022        }
1023        
1024        /**
1025         * Tests this instance for equality with an arbitrary object.
1026         * 
1027         * @param obj  the object (<code>null</code> permitted).
1028         * 
1029         * @return A boolean.
1030         */
1031        public boolean equals(Object obj) {
1032            
1033            if (obj == this) {
1034                return true;
1035            }
1036            if (!(obj instanceof BarRenderer)) {
1037                return false;
1038            }
1039            if (!super.equals(obj)) {
1040                return false;
1041            }
1042            BarRenderer that = (BarRenderer) obj;
1043            if (this.base != that.base) {
1044                return false;   
1045            }
1046            if (this.itemMargin != that.itemMargin) {
1047                return false;
1048            }              
1049            if (this.drawBarOutline != that.drawBarOutline) {
1050                return false;
1051            }
1052            if (this.maximumBarWidth != that.maximumBarWidth) {
1053                return false;
1054            }
1055            if (this.minimumBarLength != that.minimumBarLength) {
1056                return false;
1057            }
1058            if (!ObjectUtilities.equal(this.gradientPaintTransformer, 
1059                    that.gradientPaintTransformer)) {
1060                return false;
1061            }
1062            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback, 
1063                that.positiveItemLabelPositionFallback)) {
1064                return false;
1065            }
1066            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback, 
1067                that.negativeItemLabelPositionFallback)) {
1068                return false;
1069            }
1070            return true;
1071            
1072        }
1073    
1074    }