001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------
028 * BarRenderer.java
029 * ----------------
030 * (C) Copyright 2002-2020, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Christian W. Zuckschwerdt;
034 *                   Peter Kolb (patches 2497611, 2791407);
035 *
036 */
037
038package org.jfree.chart.renderer.category;
039
040import java.awt.BasicStroke;
041import java.awt.Color;
042import java.awt.Font;
043import java.awt.Graphics2D;
044import java.awt.Paint;
045import java.awt.Shape;
046import java.awt.Stroke;
047import java.awt.geom.Line2D;
048import java.awt.geom.Point2D;
049import java.awt.geom.Rectangle2D;
050import java.io.IOException;
051import java.io.ObjectInputStream;
052import java.io.ObjectOutputStream;
053import java.io.Serializable;
054
055import org.jfree.chart.LegendItem;
056import org.jfree.chart.axis.CategoryAxis;
057import org.jfree.chart.axis.ValueAxis;
058import org.jfree.chart.entity.EntityCollection;
059import org.jfree.chart.event.RendererChangeEvent;
060import org.jfree.chart.labels.CategoryItemLabelGenerator;
061import org.jfree.chart.labels.ItemLabelAnchor;
062import org.jfree.chart.labels.ItemLabelPosition;
063import org.jfree.chart.plot.CategoryPlot;
064import org.jfree.chart.plot.PlotOrientation;
065import org.jfree.chart.plot.PlotRenderingInfo;
066import org.jfree.chart.text.TextUtils;
067import org.jfree.chart.ui.GradientPaintTransformer;
068import org.jfree.chart.ui.RectangleEdge;
069import org.jfree.chart.ui.StandardGradientPaintTransformer;
070import org.jfree.chart.util.ObjectUtils;
071import org.jfree.chart.util.PaintUtils;
072import org.jfree.chart.util.Args;
073import org.jfree.chart.util.PublicCloneable;
074import org.jfree.chart.util.SerialUtils;
075import org.jfree.data.KeyedValues2DItemKey;
076import org.jfree.data.Range;
077import org.jfree.data.category.CategoryDataset;
078
079/**
080 * A {@link CategoryItemRenderer} that draws individual data items as bars.
081 * The example shown here is generated by the {@code BarChartDemo1.java}
082 * program included in the JFreeChart Demo Collection:
083 * <br><br>
084 * <img src="doc-files/BarChartDemo1.svg" alt="BarChartDemo1.svg">
085 */
086public class BarRenderer extends AbstractCategoryItemRenderer
087        implements Cloneable, PublicCloneable, Serializable {
088
089    /** For serialization. */
090    private static final long serialVersionUID = 6000649414965887481L;
091
092    /** The default item margin percentage. */
093    public static final double DEFAULT_ITEM_MARGIN = 0.20;
094
095    /**
096     * Constant that controls the minimum width before a bar has an outline
097     * drawn.
098     */
099    public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
100
101    /**
102     * The default bar painter assigned to each new instance of this renderer.
103     *
104     * @since 1.0.11
105     */
106    private static BarPainter defaultBarPainter = new GradientBarPainter();
107
108    /**
109     * Returns the default bar painter.
110     *
111     * @return The default bar painter.
112     *
113     * @since 1.0.11
114     */
115    public static BarPainter getDefaultBarPainter() {
116        return BarRenderer.defaultBarPainter;
117    }
118
119    /**
120     * Sets the default bar painter.
121     *
122     * @param painter  the painter ({@code null} not permitted).
123     *
124     * @since 1.0.11
125     */
126    public static void setDefaultBarPainter(BarPainter painter) {
127        Args.nullNotPermitted(painter, "painter");
128        BarRenderer.defaultBarPainter = painter;
129    }
130
131    /**
132     * The default value for the initialisation of the shadowsVisible flag.
133     */
134    private static boolean defaultShadowsVisible = true;
135
136    /**
137     * Returns the default value for the {@code shadowsVisible} flag.
138     *
139     * @return A boolean.
140     *
141     * @see #setDefaultShadowsVisible(boolean)
142     *
143     * @since 1.0.13
144     */
145    public static boolean getDefaultShadowsVisible() {
146        return BarRenderer.defaultShadowsVisible;
147    }
148
149    /**
150     * Sets the default value for the shadows visible flag.
151     *
152     * @param visible  the new value for the default.
153     *
154     * @see #getDefaultShadowsVisible()
155     *
156     * @since 1.0.13
157     */
158    public static void setDefaultShadowsVisible(boolean visible) {
159        BarRenderer.defaultShadowsVisible = visible;
160    }
161
162    /** The margin between items (bars) within a category. */
163    private double itemMargin;
164
165    /** A flag that controls whether or not bar outlines are drawn. */
166    private boolean drawBarOutline;
167
168    /** The maximum bar width as a percentage of the available space. */
169    private double maximumBarWidth;
170
171    /** The minimum bar length (in Java2D units). */
172    private double minimumBarLength;
173
174    /**
175     * An optional class used to transform gradient paint objects to fit each
176     * bar.
177     */
178    private GradientPaintTransformer gradientPaintTransformer;
179
180    /**
181     * The fallback position if a positive item label doesn't fit inside the
182     * bar.
183     */
184    private ItemLabelPosition positiveItemLabelPositionFallback;
185
186    /**
187     * The fallback position if a negative item label doesn't fit inside the
188     * bar.
189     */
190    private ItemLabelPosition negativeItemLabelPositionFallback;
191
192    /** The upper clip (axis) value for the axis. */
193    private double upperClip;
194    // TODO:  this needs to move into the renderer state
195
196    /** The lower clip (axis) value for the axis. */
197    private double lowerClip;
198    // TODO:  this needs to move into the renderer state
199
200    /** The base value for the bars (defaults to 0.0). */
201    private double base;
202
203    /**
204     * A flag that controls whether the base value is included in the range
205     * returned by the findRangeBounds() method.
206     */
207    private boolean includeBaseInRange;
208
209    /**
210     * The bar painter (never {@code null}).
211     *
212     * @since 1.0.11
213     */
214    private BarPainter barPainter;
215
216    /**
217     * The flag that controls whether or not shadows are drawn for the bars.
218     *
219     * @since 1.0.11
220     */
221    private boolean shadowsVisible;
222
223    /**
224     * The shadow paint.
225     *
226     * @since 1.0.11
227     */
228    private transient Paint shadowPaint;
229
230    /**
231     * The x-offset for the shadow effect.
232     *
233     * @since 1.0.11
234     */
235    private double shadowXOffset;
236
237    /**
238     * The y-offset for the shadow effect.
239     *
240     * @since 1.0.11
241     */
242    private double shadowYOffset;
243
244    /**
245     * Creates a new bar renderer with default settings.
246     */
247    public BarRenderer() {
248        super();
249        this.base = 0.0;
250        this.includeBaseInRange = true;
251        this.itemMargin = DEFAULT_ITEM_MARGIN;
252        this.drawBarOutline = false;
253        this.maximumBarWidth = 1.0;
254            // 100 percent, so it will not apply unless changed
255        this.positiveItemLabelPositionFallback = null;
256        this.negativeItemLabelPositionFallback = null;
257        this.gradientPaintTransformer = new StandardGradientPaintTransformer();
258        this.minimumBarLength = 0.0;
259        setDefaultLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
260        this.barPainter = getDefaultBarPainter();
261        this.shadowsVisible = getDefaultShadowsVisible();
262        this.shadowPaint = Color.GRAY;
263        this.shadowXOffset = 4.0;
264        this.shadowYOffset = 4.0;
265    }
266
267    /**
268     * Returns the base value for the bars.  The default value is
269     * {@code 0.0}.
270     *
271     * @return The base value for the bars.
272     *
273     * @see #setBase(double)
274     */
275    public double getBase() {
276        return this.base;
277    }
278
279    /**
280     * Sets the base value for the bars and sends a {@link RendererChangeEvent}
281     * to all registered listeners.
282     *
283     * @param base  the new base value.
284     *
285     * @see #getBase()
286     */
287    public void setBase(double base) {
288        this.base = base;
289        fireChangeEvent();
290    }
291
292    /**
293     * Returns the item margin as a percentage of the available space for all
294     * bars.
295     *
296     * @return The margin percentage (where 0.10 is ten percent).
297     *
298     * @see #setItemMargin(double)
299     */
300    public double getItemMargin() {
301        return this.itemMargin;
302    }
303
304    /**
305     * Sets the item margin and sends a {@link RendererChangeEvent} to all
306     * registered listeners.  The value is expressed as a percentage of the
307     * available width for plotting all the bars, with the resulting amount to
308     * be distributed between all the bars evenly.
309     *
310     * @param percent  the margin (where 0.10 is ten percent).
311     *
312     * @see #getItemMargin()
313     */
314    public void setItemMargin(double percent) {
315        this.itemMargin = percent;
316        fireChangeEvent();
317    }
318
319    /**
320     * Returns a flag that controls whether or not bar outlines are drawn.
321     *
322     * @return A boolean.
323     *
324     * @see #setDrawBarOutline(boolean)
325     */
326    public boolean isDrawBarOutline() {
327        return this.drawBarOutline;
328    }
329
330    /**
331     * Sets the flag that controls whether or not bar outlines are drawn and
332     * sends a {@link RendererChangeEvent} to all registered listeners.
333     *
334     * @param draw  the flag.
335     *
336     * @see #isDrawBarOutline()
337     */
338    public void setDrawBarOutline(boolean draw) {
339        this.drawBarOutline = draw;
340        fireChangeEvent();
341    }
342
343    /**
344     * Returns the maximum bar width, as a percentage of the available drawing
345     * space.
346     *
347     * @return The maximum bar width.
348     *
349     * @see #setMaximumBarWidth(double)
350     */
351    public double getMaximumBarWidth() {
352        return this.maximumBarWidth;
353    }
354
355    /**
356     * Sets the maximum bar width, which is specified as a percentage of the
357     * available space for all bars, and sends a {@link RendererChangeEvent} to
358     * all registered listeners.
359     *
360     * @param percent  the percent (where 0.05 is five percent).
361     *
362     * @see #getMaximumBarWidth()
363     */
364    public void setMaximumBarWidth(double percent) {
365        this.maximumBarWidth = percent;
366        fireChangeEvent();
367    }
368
369    /**
370     * Returns the minimum bar length (in Java2D units).  The default value is
371     * 0.0.
372     *
373     * @return The minimum bar length.
374     *
375     * @see #setMinimumBarLength(double)
376     */
377    public double getMinimumBarLength() {
378        return this.minimumBarLength;
379    }
380
381    /**
382     * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
383     * all registered listeners.  The minimum bar length is specified in Java2D
384     * units, and can be used to prevent bars that represent very small data
385     * values from disappearing when drawn on the screen.  Typically you would
386     * set this to (say) 0.5 or 1.0 Java 2D units.  Use this attribute with
387     * caution, however, because setting it to a non-zero value will
388     * artificially increase the length of bars representing small values,
389     * which may misrepresent your data.
390     *
391     * @param min  the minimum bar length (in Java2D units, must be &gt;= 0.0).
392     *
393     * @see #getMinimumBarLength()
394     */
395    public void setMinimumBarLength(double min) {
396        if (min < 0.0) {
397            throw new IllegalArgumentException("Requires 'min' >= 0.0");
398        }
399        this.minimumBarLength = min;
400        fireChangeEvent();
401    }
402
403    /**
404     * Returns the gradient paint transformer (an object used to transform
405     * gradient paint objects to fit each bar).
406     *
407     * @return A transformer ({@code null} possible).
408     *
409     * @see #setGradientPaintTransformer(GradientPaintTransformer)
410     */
411    public GradientPaintTransformer getGradientPaintTransformer() {
412        return this.gradientPaintTransformer;
413    }
414
415    /**
416     * Sets the gradient paint transformer and sends a
417     * {@link RendererChangeEvent} to all registered listeners.
418     *
419     * @param transformer  the transformer ({@code null} permitted).
420     *
421     * @see #getGradientPaintTransformer()
422     */
423    public void setGradientPaintTransformer(
424            GradientPaintTransformer transformer) {
425        this.gradientPaintTransformer = transformer;
426        fireChangeEvent();
427    }
428
429    /**
430     * Returns the fallback position for positive item labels that don't fit
431     * within a bar.
432     *
433     * @return The fallback position ({@code null} possible).
434     *
435     * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
436     */
437    public ItemLabelPosition getPositiveItemLabelPositionFallback() {
438        return this.positiveItemLabelPositionFallback;
439    }
440
441    /**
442     * Sets the fallback position for positive item labels that don't fit
443     * within a bar, and sends a {@link RendererChangeEvent} to all registered
444     * listeners.
445     *
446     * @param position  the position ({@code null} permitted).
447     *
448     * @see #getPositiveItemLabelPositionFallback()
449     */
450    public void setPositiveItemLabelPositionFallback(
451            ItemLabelPosition position) {
452        this.positiveItemLabelPositionFallback = position;
453        fireChangeEvent();
454    }
455
456    /**
457     * Returns the fallback position for negative item labels that don't fit
458     * within a bar.
459     *
460     * @return The fallback position ({@code null} possible).
461     *
462     * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
463     */
464    public ItemLabelPosition getNegativeItemLabelPositionFallback() {
465        return this.negativeItemLabelPositionFallback;
466    }
467
468    /**
469     * Sets the fallback position for negative item labels that don't fit
470     * within a bar, and sends a {@link RendererChangeEvent} to all registered
471     * listeners.
472     *
473     * @param position  the position ({@code null} permitted).
474     *
475     * @see #getNegativeItemLabelPositionFallback()
476     */
477    public void setNegativeItemLabelPositionFallback(
478            ItemLabelPosition position) {
479        this.negativeItemLabelPositionFallback = position;
480        fireChangeEvent();
481    }
482
483    /**
484     * Returns the flag that controls whether or not the base value for the
485     * bars is included in the range calculated by
486     * {@link #findRangeBounds(CategoryDataset)}.
487     *
488     * @return {@code true} if the base is included in the range, and
489     *         {@code false} otherwise.
490     *
491     * @since 1.0.1
492     *
493     * @see #setIncludeBaseInRange(boolean)
494     */
495    public boolean getIncludeBaseInRange() {
496        return this.includeBaseInRange;
497    }
498
499    /**
500     * Sets the flag that controls whether or not the base value for the bars
501     * is included in the range calculated by
502     * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
503     * a {@link RendererChangeEvent} is sent to all registered listeners.
504     *
505     * @param include  the new value for the flag.
506     *
507     * @since 1.0.1
508     *
509     * @see #getIncludeBaseInRange()
510     */
511    public void setIncludeBaseInRange(boolean include) {
512        if (this.includeBaseInRange != include) {
513            this.includeBaseInRange = include;
514            fireChangeEvent();
515        }
516    }
517
518    /**
519     * Returns the bar painter.
520     *
521     * @return The bar painter (never {@code null}).
522     *
523     * @see #setBarPainter(BarPainter)
524     *
525     * @since 1.0.11
526     */
527    public BarPainter getBarPainter() {
528        return this.barPainter;
529    }
530
531    /**
532     * Sets the bar painter for this renderer and sends a
533     * {@link RendererChangeEvent} to all registered listeners.
534     *
535     * @param painter  the painter ({@code null} not permitted).
536     *
537     * @see #getBarPainter()
538     *
539     * @since 1.0.11
540     */
541    public void setBarPainter(BarPainter painter) {
542        Args.nullNotPermitted(painter, "painter");
543        this.barPainter = painter;
544        fireChangeEvent();
545    }
546
547    /**
548     * Returns the flag that controls whether or not shadows are drawn for
549     * the bars.
550     *
551     * @return A boolean.
552     *
553     * @since 1.0.11
554     */
555    public boolean getShadowsVisible() {
556        return this.shadowsVisible;
557    }
558
559    /**
560     * Sets the flag that controls whether or not shadows are
561     * drawn by the renderer.
562     *
563     * @param visible  the new flag value.
564     *
565     * @since 1.0.11
566     */
567    public void setShadowVisible(boolean visible) {
568        this.shadowsVisible = visible;
569        fireChangeEvent();
570    }
571
572    /**
573     * Returns the shadow paint.
574     *
575     * @return The shadow paint.
576     *
577     * @see #setShadowPaint(Paint)
578     *
579     * @since 1.0.11
580     */
581    public Paint getShadowPaint() {
582        return this.shadowPaint;
583    }
584
585    /**
586     * Sets the shadow paint and sends a {@link RendererChangeEvent} to all
587     * registered listeners.
588     *
589     * @param paint  the paint ({@code null} not permitted).
590     *
591     * @see #getShadowPaint()
592     *
593     * @since 1.0.11
594     */
595    public void setShadowPaint(Paint paint) {
596        Args.nullNotPermitted(paint, "paint");
597        this.shadowPaint = paint;
598        fireChangeEvent();
599    }
600
601    /**
602     * Returns the shadow x-offset.
603     *
604     * @return The shadow x-offset.
605     *
606     * @since 1.0.11
607     */
608    public double getShadowXOffset() {
609        return this.shadowXOffset;
610    }
611
612    /**
613     * Sets the x-offset for the bar shadow and sends a
614     * {@link RendererChangeEvent} to all registered listeners.
615     *
616     * @param offset  the offset.
617     *
618     * @since 1.0.11
619     */
620    public void setShadowXOffset(double offset) {
621        this.shadowXOffset = offset;
622        fireChangeEvent();
623    }
624
625    /**
626     * Returns the shadow y-offset.
627     *
628     * @return The shadow y-offset.
629     *
630     * @since 1.0.11
631     */
632    public double getShadowYOffset() {
633        return this.shadowYOffset;
634    }
635
636    /**
637     * Sets the y-offset for the bar shadow and sends a
638     * {@link RendererChangeEvent} to all registered listeners.
639     *
640     * @param offset  the offset.
641     *
642     * @since 1.0.11
643     */
644    public void setShadowYOffset(double offset) {
645        this.shadowYOffset = offset;
646        fireChangeEvent();
647    }
648
649    /**
650     * Returns the lower clip value.  This value is recalculated in the
651     * initialise() method.
652     *
653     * @return The value.
654     */
655    public double getLowerClip() {
656        // TODO:  this attribute should be transferred to the renderer state.
657        return this.lowerClip;
658    }
659
660    /**
661     * Returns the upper clip value.  This value is recalculated in the
662     * initialise() method.
663     *
664     * @return The value.
665     */
666    public double getUpperClip() {
667        // TODO:  this attribute should be transferred to the renderer state.
668        return this.upperClip;
669    }
670
671    /**
672     * Initialises the renderer and returns a state object that will be passed
673     * to subsequent calls to the drawItem method.  This method gets called
674     * once at the start of the process of drawing a chart.
675     *
676     * @param g2  the graphics device.
677     * @param dataArea  the area in which the data is to be plotted.
678     * @param plot  the plot.
679     * @param rendererIndex  the renderer index.
680     * @param info  collects chart rendering information for return to caller.
681     *
682     * @return The renderer state.
683     */
684    @Override
685    public CategoryItemRendererState initialise(Graphics2D g2, 
686            Rectangle2D dataArea, CategoryPlot plot, int rendererIndex,
687            PlotRenderingInfo info) {
688
689        CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
690                rendererIndex, info);
691
692        // get the clipping values...
693        ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
694        this.lowerClip = rangeAxis.getRange().getLowerBound();
695        this.upperClip = rangeAxis.getRange().getUpperBound();
696
697        // calculate the bar width
698        calculateBarWidth(plot, dataArea, rendererIndex, state);
699
700        return state;
701
702    }
703
704    /**
705     * Calculates the bar width and stores it in the renderer state.
706     *
707     * @param plot  the plot.
708     * @param dataArea  the data area.
709     * @param rendererIndex  the renderer index.
710     * @param state  the renderer state.
711     */
712    protected void calculateBarWidth(CategoryPlot plot,
713                                     Rectangle2D dataArea,
714                                     int rendererIndex,
715                                     CategoryItemRendererState state) {
716
717        CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
718        CategoryDataset dataset = plot.getDataset(rendererIndex);
719        if (dataset != null) {
720            int columns = dataset.getColumnCount();
721            int rows = state.getVisibleSeriesCount() >= 0
722                    ? state.getVisibleSeriesCount() : dataset.getRowCount();
723            double space = 0.0;
724            PlotOrientation orientation = plot.getOrientation();
725            if (orientation == PlotOrientation.HORIZONTAL) {
726                space = dataArea.getHeight();
727            }
728            else if (orientation == PlotOrientation.VERTICAL) {
729                space = dataArea.getWidth();
730            }
731            double maxWidth = space * getMaximumBarWidth();
732            double categoryMargin = 0.0;
733            double currentItemMargin = 0.0;
734            if (columns > 1) {
735                categoryMargin = domainAxis.getCategoryMargin();
736            }
737            if (rows > 1) {
738                currentItemMargin = getItemMargin();
739            }
740            double used = space * (1 - domainAxis.getLowerMargin()
741                                     - domainAxis.getUpperMargin()
742                                     - categoryMargin - currentItemMargin);
743            if ((rows * columns) > 0) {
744                state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
745            }
746            else {
747                state.setBarWidth(Math.min(used, maxWidth));
748            }
749        }
750    }
751
752    /**
753     * Calculates the coordinate of the first "side" of a bar.  This will be
754     * the minimum x-coordinate for a vertical bar, and the minimum
755     * y-coordinate for a horizontal bar.
756     *
757     * @param plot  the plot.
758     * @param orientation  the plot orientation.
759     * @param dataArea  the data area.
760     * @param domainAxis  the domain axis.
761     * @param state  the renderer state (has the bar width precalculated).
762     * @param row  the row index.
763     * @param column  the column index.
764     *
765     * @return The coordinate.
766     */
767    protected double calculateBarW0(CategoryPlot plot, 
768            PlotOrientation orientation, Rectangle2D dataArea, 
769            CategoryAxis domainAxis, CategoryItemRendererState state,
770            int row, int column) {
771        // calculate bar width...
772        double space;
773        if (orientation == PlotOrientation.HORIZONTAL) {
774            space = dataArea.getHeight();
775        }
776        else {
777            space = dataArea.getWidth();
778        }
779        double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
780                dataArea, plot.getDomainAxisEdge());
781        int seriesCount = state.getVisibleSeriesCount() >= 0
782                ? state.getVisibleSeriesCount() : getRowCount();
783        int categoryCount = getColumnCount();
784        if (seriesCount > 1) {
785            double seriesGap = space * getItemMargin()
786                               / (categoryCount * (seriesCount - 1));
787            double seriesW = calculateSeriesWidth(space, domainAxis,
788                    categoryCount, seriesCount);
789            barW0 = barW0 + row * (seriesW + seriesGap)
790                          + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
791        }
792        else {
793            barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
794                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
795                    / 2.0;
796        }
797        return barW0;
798    }
799
800    /**
801     * Calculates the coordinates for the length of a single bar.
802     *
803     * @param value  the value represented by the bar.
804     *
805     * @return The coordinates for each end of the bar (or {@code null} if
806     *         the bar is not visible for the current axis range).
807     */
808    protected double[] calculateBarL0L1(double value) {
809        double lclip = getLowerClip();
810        double uclip = getUpperClip();
811        double barLow = Math.min(this.base, value);
812        double barHigh = Math.max(this.base, value);
813        if (barHigh < lclip) {  // bar is not visible
814            return null;
815        }
816        if (barLow > uclip) {   // bar is not visible
817            return null;
818        }
819        barLow = Math.max(barLow, lclip);
820        barHigh = Math.min(barHigh, uclip);
821        return new double[] {barLow, barHigh};
822    }
823
824    /**
825     * Returns the range of values the renderer requires to display all the
826     * items from the specified dataset.  This takes into account the range
827     * of values in the dataset, plus the flag that determines whether or not
828     * the base value for the bars should be included in the range.
829     *
830     * @param dataset  the dataset ({@code null} permitted).
831     * @param includeInterval  include the interval if the dataset has one?
832     *
833     * @return The range (or {@code null} if the dataset is
834     *         {@code null} or empty).
835     */
836    @Override
837    public Range findRangeBounds(CategoryDataset dataset,
838            boolean includeInterval) {
839        if (dataset == null) {
840            return null;
841        }
842        Range result = super.findRangeBounds(dataset, includeInterval);
843        if (result != null) {
844            if (this.includeBaseInRange) {
845                result = Range.expandToInclude(result, this.base);
846            }
847        }
848        return result;
849    }
850
851    /**
852     * Returns a legend item for a series.
853     *
854     * @param datasetIndex  the dataset index (zero-based).
855     * @param series  the series index (zero-based).
856     *
857     * @return The legend item (possibly {@code null}).
858     */
859    @Override
860    public LegendItem getLegendItem(int datasetIndex, int series) {
861
862        CategoryPlot cp = getPlot();
863        if (cp == null) {
864            return null;
865        }
866
867        // check that a legend item needs to be displayed...
868        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
869            return null;
870        }
871
872        CategoryDataset dataset = cp.getDataset(datasetIndex);
873        String label = getLegendItemLabelGenerator().generateLabel(dataset,
874                series);
875        String description = label;
876        String toolTipText = null;
877        if (getLegendItemToolTipGenerator() != null) {
878            toolTipText = getLegendItemToolTipGenerator().generateLabel(
879                    dataset, series);
880        }
881        String urlText = null;
882        if (getLegendItemURLGenerator() != null) {
883            urlText = getLegendItemURLGenerator().generateLabel(dataset,
884                    series);
885        }
886        Shape shape = lookupLegendShape(series);
887        Paint paint = lookupSeriesPaint(series);
888        Paint outlinePaint = lookupSeriesOutlinePaint(series);
889        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
890
891        LegendItem result = new LegendItem(label, description, toolTipText,
892                urlText, true, shape, true, paint, isDrawBarOutline(),
893                outlinePaint, outlineStroke, false, new Line2D.Float(),
894                new BasicStroke(1.0f), Color.BLACK);
895        result.setLabelFont(lookupLegendTextFont(series));
896        Paint labelPaint = lookupLegendTextPaint(series);
897        if (labelPaint != null) {
898            result.setLabelPaint(labelPaint);
899        }
900        result.setDataset(dataset);
901        result.setDatasetIndex(datasetIndex);
902        result.setSeriesKey(dataset.getRowKey(series));
903        result.setSeriesIndex(series);
904        if (this.gradientPaintTransformer != null) {
905            result.setFillPaintTransformer(this.gradientPaintTransformer);
906        }
907        return result;
908    }
909
910    /**
911     * Draws the bar for a single (series, category) data item.
912     *
913     * @param g2  the graphics device.
914     * @param state  the renderer state.
915     * @param dataArea  the data area.
916     * @param plot  the plot.
917     * @param domainAxis  the domain axis.
918     * @param rangeAxis  the range axis.
919     * @param dataset  the dataset.
920     * @param row  the row index (zero-based).
921     * @param column  the column index (zero-based).
922     * @param pass  the pass index.
923     */
924    @Override
925    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
926            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
927            ValueAxis rangeAxis, CategoryDataset dataset, int row,
928            int column, int pass) {
929
930        // nothing is drawn if the row index is not included in the list with
931        // the indices of the visible rows...
932        int visibleRow = state.getVisibleSeriesIndex(row);
933        if (visibleRow < 0) {
934            return;
935        }
936        // nothing is drawn for null values...
937        Number dataValue = dataset.getValue(row, column);
938        if (dataValue == null) {
939            return;
940        }
941
942        final double value = dataValue.doubleValue();
943        PlotOrientation orientation = plot.getOrientation();
944        double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
945                state, visibleRow, column);
946        double[] barL0L1 = calculateBarL0L1(value);
947        if (barL0L1 == null) {
948            return;  // the bar is not visible
949        }
950
951        RectangleEdge edge = plot.getRangeAxisEdge();
952        double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
953        double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
954
955        // in the following code, barL0 is (in Java2D coordinates) the LEFT
956        // end of the bar for a horizontal bar chart, and the TOP end of the
957        // bar for a vertical bar chart.  Whether this is the BASE of the bar
958        // or not depends also on (a) whether the data value is 'negative'
959        // relative to the base value and (b) whether or not the range axis is
960        // inverted.  This only matters if/when we apply the minimumBarLength
961        // attribute, because we should extend the non-base end of the bar
962        boolean positive = (value >= this.base);
963        boolean inverted = rangeAxis.isInverted();
964        double barL0 = Math.min(transL0, transL1);
965        double barLength = Math.abs(transL1 - transL0);
966        double barLengthAdj = 0.0;
967        if (barLength > 0.0 && barLength < getMinimumBarLength()) {
968            barLengthAdj = getMinimumBarLength() - barLength;
969        }
970        double barL0Adj = 0.0;
971        RectangleEdge barBase;
972        if (orientation == PlotOrientation.HORIZONTAL) {
973            if (positive && inverted || !positive && !inverted) {
974                barL0Adj = barLengthAdj;
975                barBase = RectangleEdge.RIGHT;
976            }
977            else {
978                barBase = RectangleEdge.LEFT;
979            }
980        }
981        else {
982            if (positive && !inverted || !positive && inverted) {
983                barL0Adj = barLengthAdj;
984                barBase = RectangleEdge.BOTTOM;
985            }
986            else {
987                barBase = RectangleEdge.TOP;
988            }
989        }
990
991        // draw the bar...
992        Rectangle2D bar;
993        if (orientation == PlotOrientation.HORIZONTAL) {
994            bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
995                    barLength + barLengthAdj, state.getBarWidth());
996        }
997        else {
998            bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
999                    state.getBarWidth(), barLength + barLengthAdj);
1000        }
1001        if (state.getElementHinting()) {
1002            KeyedValues2DItemKey key = new KeyedValues2DItemKey(
1003                    dataset.getRowKey(row), dataset.getColumnKey(column));
1004            beginElementGroup(g2, key);
1005        }
1006        if (getShadowsVisible()) {
1007            this.barPainter.paintBarShadow(g2, this, row, column, bar, barBase,
1008                true);
1009        }
1010        this.barPainter.paintBar(g2, this, row, column, bar, barBase);
1011        if (state.getElementHinting()) {
1012            endElementGroup(g2);
1013        }
1014        
1015        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
1016                column);
1017        if (generator != null && isItemLabelVisible(row, column)) {
1018            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
1019                    (value < 0.0));
1020        }
1021
1022        // submit the current data point as a crosshair candidate
1023        int datasetIndex = plot.indexOf(dataset);
1024        updateCrosshairValues(state.getCrosshairState(),
1025                dataset.getRowKey(row), dataset.getColumnKey(column), value,
1026                datasetIndex, barW0, barL0, orientation);
1027
1028        // add an item entity, if this information is being collected
1029        EntityCollection entities = state.getEntityCollection();
1030        if (entities != null) {
1031            addItemEntity(entities, dataset, row, column, bar);
1032        }
1033
1034    }
1035    
1036    /**
1037     * Calculates the available space for each series.
1038     *
1039     * @param space  the space along the entire axis (in Java2D units).
1040     * @param axis  the category axis.
1041     * @param categories  the number of categories.
1042     * @param series  the number of series.
1043     *
1044     * @return The width of one series.
1045     */
1046    protected double calculateSeriesWidth(double space, CategoryAxis axis,
1047                                          int categories, int series) {
1048        double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
1049                            - axis.getUpperMargin();
1050        if (categories > 1) {
1051            factor = factor - axis.getCategoryMargin();
1052        }
1053        return (space * factor) / (categories * series);
1054    }
1055
1056    /**
1057     * Draws an item label.  This method is overridden so that the bar can be
1058     * used to calculate the label anchor point.
1059     *
1060     * @param g2  the graphics device.
1061     * @param data  the dataset.
1062     * @param row  the row.
1063     * @param column  the column.
1064     * @param plot  the plot.
1065     * @param generator  the label generator.
1066     * @param bar  the bar.
1067     * @param negative  a flag indicating a negative value.
1068     */
1069    protected void drawItemLabel(Graphics2D g2,
1070                                 CategoryDataset data,
1071                                 int row,
1072                                 int column,
1073                                 CategoryPlot plot,
1074                                 CategoryItemLabelGenerator generator,
1075                                 Rectangle2D bar,
1076                                 boolean negative) {
1077
1078        String label = generator.generateLabel(data, row, column);
1079        if (label == null) {
1080            return;  // nothing to do
1081        }
1082
1083        Font labelFont = getItemLabelFont(row, column);
1084        g2.setFont(labelFont);
1085        Paint paint = getItemLabelPaint(row, column);
1086        g2.setPaint(paint);
1087
1088        // find out where to place the label...
1089        ItemLabelPosition position;
1090        if (!negative) {
1091            position = getPositiveItemLabelPosition(row, column);
1092        }
1093        else {
1094            position = getNegativeItemLabelPosition(row, column);
1095        }
1096
1097        // work out the label anchor point...
1098        Point2D anchorPoint = calculateLabelAnchorPoint(
1099                position.getItemLabelAnchor(), bar, plot.getOrientation());
1100
1101        if (isInternalAnchor(position.getItemLabelAnchor())) {
1102            Shape bounds = TextUtils.calculateRotatedStringBounds(label,
1103                    g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1104                    position.getTextAnchor(), position.getAngle(),
1105                    position.getRotationAnchor());
1106
1107            if (bounds != null) {
1108                if (!bar.contains(bounds.getBounds2D())) {
1109                    if (!negative) {
1110                        position = getPositiveItemLabelPositionFallback();
1111                    }
1112                    else {
1113                        position = getNegativeItemLabelPositionFallback();
1114                    }
1115                    if (position != null) {
1116                        anchorPoint = calculateLabelAnchorPoint(
1117                                position.getItemLabelAnchor(), bar,
1118                                plot.getOrientation());
1119                    }
1120                }
1121            }
1122
1123        }
1124
1125        if (position != null) {
1126            TextUtils.drawRotatedString(label, g2,
1127                    (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1128                    position.getTextAnchor(), position.getAngle(),
1129                    position.getRotationAnchor());
1130        }
1131    }
1132
1133    /**
1134     * Calculates the item label anchor point.
1135     *
1136     * @param anchor  the anchor.
1137     * @param bar  the bar.
1138     * @param orientation  the plot orientation.
1139     *
1140     * @return The anchor point.
1141     */
1142    private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
1143                                              Rectangle2D bar,
1144                                              PlotOrientation orientation) {
1145
1146        Point2D result = null;
1147        double offset = getItemLabelAnchorOffset();
1148        double x0 = bar.getX() - offset;
1149        double x1 = bar.getX();
1150        double x2 = bar.getX() + offset;
1151        double x3 = bar.getCenterX();
1152        double x4 = bar.getMaxX() - offset;
1153        double x5 = bar.getMaxX();
1154        double x6 = bar.getMaxX() + offset;
1155
1156        double y0 = bar.getMaxY() + offset;
1157        double y1 = bar.getMaxY();
1158        double y2 = bar.getMaxY() - offset;
1159        double y3 = bar.getCenterY();
1160        double y4 = bar.getMinY() + offset;
1161        double y5 = bar.getMinY();
1162        double y6 = bar.getMinY() - offset;
1163
1164        if (anchor == ItemLabelAnchor.CENTER) {
1165            result = new Point2D.Double(x3, y3);
1166        }
1167        else if (anchor == ItemLabelAnchor.INSIDE1) {
1168            result = new Point2D.Double(x4, y4);
1169        }
1170        else if (anchor == ItemLabelAnchor.INSIDE2) {
1171            result = new Point2D.Double(x4, y4);
1172        }
1173        else if (anchor == ItemLabelAnchor.INSIDE3) {
1174            result = new Point2D.Double(x4, y3);
1175        }
1176        else if (anchor == ItemLabelAnchor.INSIDE4) {
1177            result = new Point2D.Double(x4, y2);
1178        }
1179        else if (anchor == ItemLabelAnchor.INSIDE5) {
1180            result = new Point2D.Double(x4, y2);
1181        }
1182        else if (anchor == ItemLabelAnchor.INSIDE6) {
1183            result = new Point2D.Double(x3, y2);
1184        }
1185        else if (anchor == ItemLabelAnchor.INSIDE7) {
1186            result = new Point2D.Double(x2, y2);
1187        }
1188        else if (anchor == ItemLabelAnchor.INSIDE8) {
1189            result = new Point2D.Double(x2, y2);
1190        }
1191        else if (anchor == ItemLabelAnchor.INSIDE9) {
1192            result = new Point2D.Double(x2, y3);
1193        }
1194        else if (anchor == ItemLabelAnchor.INSIDE10) {
1195            result = new Point2D.Double(x2, y4);
1196        }
1197        else if (anchor == ItemLabelAnchor.INSIDE11) {
1198            result = new Point2D.Double(x2, y4);
1199        }
1200        else if (anchor == ItemLabelAnchor.INSIDE12) {
1201            result = new Point2D.Double(x3, y4);
1202        }
1203        else if (anchor == ItemLabelAnchor.OUTSIDE1) {
1204            result = new Point2D.Double(x5, y6);
1205        }
1206        else if (anchor == ItemLabelAnchor.OUTSIDE2) {
1207            result = new Point2D.Double(x6, y5);
1208        }
1209        else if (anchor == ItemLabelAnchor.OUTSIDE3) {
1210            result = new Point2D.Double(x6, y3);
1211        }
1212        else if (anchor == ItemLabelAnchor.OUTSIDE4) {
1213            result = new Point2D.Double(x6, y1);
1214        }
1215        else if (anchor == ItemLabelAnchor.OUTSIDE5) {
1216            result = new Point2D.Double(x5, y0);
1217        }
1218        else if (anchor == ItemLabelAnchor.OUTSIDE6) {
1219            result = new Point2D.Double(x3, y0);
1220        }
1221        else if (anchor == ItemLabelAnchor.OUTSIDE7) {
1222            result = new Point2D.Double(x1, y0);
1223        }
1224        else if (anchor == ItemLabelAnchor.OUTSIDE8) {
1225            result = new Point2D.Double(x0, y1);
1226        }
1227        else if (anchor == ItemLabelAnchor.OUTSIDE9) {
1228            result = new Point2D.Double(x0, y3);
1229        }
1230        else if (anchor == ItemLabelAnchor.OUTSIDE10) {
1231            result = new Point2D.Double(x0, y5);
1232        }
1233        else if (anchor == ItemLabelAnchor.OUTSIDE11) {
1234            result = new Point2D.Double(x1, y6);
1235        }
1236        else if (anchor == ItemLabelAnchor.OUTSIDE12) {
1237            result = new Point2D.Double(x3, y6);
1238        }
1239
1240        return result;
1241
1242    }
1243
1244    /**
1245     * Returns {@code true} if the specified anchor point is inside a bar.
1246     *
1247     * @param anchor  the anchor point.
1248     *
1249     * @return A boolean.
1250     */
1251    private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1252        return anchor == ItemLabelAnchor.CENTER
1253               || anchor == ItemLabelAnchor.INSIDE1
1254               || anchor == ItemLabelAnchor.INSIDE2
1255               || anchor == ItemLabelAnchor.INSIDE3
1256               || anchor == ItemLabelAnchor.INSIDE4
1257               || anchor == ItemLabelAnchor.INSIDE5
1258               || anchor == ItemLabelAnchor.INSIDE6
1259               || anchor == ItemLabelAnchor.INSIDE7
1260               || anchor == ItemLabelAnchor.INSIDE8
1261               || anchor == ItemLabelAnchor.INSIDE9
1262               || anchor == ItemLabelAnchor.INSIDE10
1263               || anchor == ItemLabelAnchor.INSIDE11
1264               || anchor == ItemLabelAnchor.INSIDE12;
1265    }
1266
1267    /**
1268     * Tests this instance for equality with an arbitrary object.
1269     *
1270     * @param obj  the object ({@code null} permitted).
1271     *
1272     * @return A boolean.
1273     */
1274    @Override
1275    public boolean equals(Object obj) {
1276        if (obj == this) {
1277            return true;
1278        }
1279        if (!(obj instanceof BarRenderer)) {
1280            return false;
1281        }
1282        BarRenderer that = (BarRenderer) obj;
1283        if (this.base != that.base) {
1284            return false;
1285        }
1286        if (this.itemMargin != that.itemMargin) {
1287            return false;
1288        }
1289        if (this.drawBarOutline != that.drawBarOutline) {
1290            return false;
1291        }
1292        if (this.maximumBarWidth != that.maximumBarWidth) {
1293            return false;
1294        }
1295        if (this.minimumBarLength != that.minimumBarLength) {
1296            return false;
1297        }
1298        if (!ObjectUtils.equal(this.gradientPaintTransformer,
1299                that.gradientPaintTransformer)) {
1300            return false;
1301        }
1302        if (!ObjectUtils.equal(this.positiveItemLabelPositionFallback,
1303            that.positiveItemLabelPositionFallback)) {
1304            return false;
1305        }
1306        if (!ObjectUtils.equal(this.negativeItemLabelPositionFallback,
1307            that.negativeItemLabelPositionFallback)) {
1308            return false;
1309        }
1310        if (!this.barPainter.equals(that.barPainter)) {
1311            return false;
1312        }
1313        if (this.shadowsVisible != that.shadowsVisible) {
1314            return false;
1315        }
1316        if (!PaintUtils.equal(this.shadowPaint, that.shadowPaint)) {
1317            return false;
1318        }
1319        if (this.shadowXOffset != that.shadowXOffset) {
1320            return false;
1321        }
1322        if (this.shadowYOffset != that.shadowYOffset) {
1323            return false;
1324        }
1325        return super.equals(obj);
1326    }
1327
1328    /**
1329     * Provides serialization support.
1330     *
1331     * @param stream  the output stream.
1332     *
1333     * @throws IOException  if there is an I/O error.
1334     */
1335    private void writeObject(ObjectOutputStream stream) throws IOException {
1336        stream.defaultWriteObject();
1337        SerialUtils.writePaint(this.shadowPaint, stream);
1338    }
1339
1340    /**
1341     * Provides serialization support.
1342     *
1343     * @param stream  the input stream.
1344     *
1345     * @throws IOException  if there is an I/O error.
1346     * @throws ClassNotFoundException  if there is a classpath problem.
1347     */
1348    private void readObject(ObjectInputStream stream)
1349            throws IOException, ClassNotFoundException {
1350        stream.defaultReadObject();
1351        this.shadowPaint = SerialUtils.readPaint(stream);
1352    }
1353
1354}