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