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