001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * XYBarRenderer.java
029 * ------------------
030 * (C) Copyright 2001-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Richard Atkinson;
034 *                   Christian W. Zuckschwerdt;
035 *                   Bill Kelemen;
036 *                   Marc van Glabbeek (bug 1775452);
037 *                   Richard West, Advanced Micro Devices, Inc.;
038 *
039 */
040
041package org.jfree.chart.renderer.xy;
042
043import java.awt.Font;
044import java.awt.Graphics2D;
045import java.awt.Paint;
046import java.awt.Shape;
047import java.awt.Stroke;
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.ValueAxis;
058import org.jfree.chart.entity.EntityCollection;
059import org.jfree.chart.event.RendererChangeEvent;
060import org.jfree.chart.labels.ItemLabelAnchor;
061import org.jfree.chart.labels.ItemLabelPosition;
062import org.jfree.chart.labels.XYItemLabelGenerator;
063import org.jfree.chart.labels.XYSeriesLabelGenerator;
064import org.jfree.chart.plot.CrosshairState;
065import org.jfree.chart.plot.PlotOrientation;
066import org.jfree.chart.plot.PlotRenderingInfo;
067import org.jfree.chart.plot.XYPlot;
068import org.jfree.chart.text.TextUtils;
069import org.jfree.chart.ui.GradientPaintTransformer;
070import org.jfree.chart.ui.RectangleEdge;
071import org.jfree.chart.ui.StandardGradientPaintTransformer;
072import org.jfree.chart.util.ObjectUtils;
073import org.jfree.chart.util.Args;
074import org.jfree.chart.util.PublicCloneable;
075import org.jfree.chart.util.SerialUtils;
076import org.jfree.chart.util.ShapeUtils;
077import org.jfree.data.Range;
078import org.jfree.data.xy.IntervalXYDataset;
079import org.jfree.data.xy.XYDataset;
080
081/**
082 * A renderer that draws bars on an {@link XYPlot} (requires an
083 * {@link IntervalXYDataset}).  The example shown here is generated by the
084 * {@code XYBarChartDemo1.java} program included in the JFreeChart
085 * demo collection:
086 * <br><br>
087 * <img src="doc-files/XYBarRendererSample.png" alt="XYBarRendererSample.png">
088 */
089public class XYBarRenderer extends AbstractXYItemRenderer
090        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
091
092    /** For serialization. */
093    private static final long serialVersionUID = 770559577251370036L;
094
095    /**
096     * The default bar painter assigned to each new instance of this renderer.
097     */
098    private static XYBarPainter defaultBarPainter = new GradientXYBarPainter();
099
100    /**
101     * Returns the default bar painter.
102     *
103     * @return The default bar painter.
104     */
105    public static XYBarPainter getDefaultBarPainter() {
106        return XYBarRenderer.defaultBarPainter;
107    }
108
109    /**
110     * Sets the default bar painter.
111     *
112     * @param painter  the painter ({@code null} not permitted).
113     */
114    public static void setDefaultBarPainter(XYBarPainter painter) {
115        Args.nullNotPermitted(painter, "painter");
116        XYBarRenderer.defaultBarPainter = painter;
117    }
118
119    /**
120     * The default value for the initialisation of the shadowsVisible flag.
121     */
122    private static boolean defaultShadowsVisible = true;
123
124    /**
125     * Returns the default value for the {@code shadowsVisible} flag.
126     *
127     * @return A boolean.
128     *
129     * @see #setDefaultShadowsVisible(boolean)
130     */
131    public static boolean getDefaultShadowsVisible() {
132        return XYBarRenderer.defaultShadowsVisible;
133    }
134
135    /**
136     * Sets the default value for the shadows visible flag.
137     *
138     * @param visible  the new value for the default.
139     *
140     * @see #getDefaultShadowsVisible()
141     */
142    public static void setDefaultShadowsVisible(boolean visible) {
143        XYBarRenderer.defaultShadowsVisible = visible;
144    }
145
146    /**
147     * The state class used by this renderer.
148     */
149    protected class XYBarRendererState extends XYItemRendererState {
150
151        /** Base for bars against the range axis, in Java 2D space. */
152        private double g2Base;
153
154        /**
155         * Creates a new state object.
156         *
157         * @param info  the plot rendering info.
158         */
159        public XYBarRendererState(PlotRenderingInfo info) {
160            super(info);
161        }
162
163        /**
164         * Returns the base (range) value in Java 2D space.
165         *
166         * @return The base value.
167         */
168        public double getG2Base() {
169            return this.g2Base;
170        }
171
172        /**
173         * Sets the range axis base in Java2D space.
174         *
175         * @param value  the value.
176         */
177        public void setG2Base(double value) {
178            this.g2Base = value;
179        }
180    }
181
182    /** The default base value for the bars. */
183    private double base;
184
185    /**
186     * A flag that controls whether the bars use the y-interval supplied by the
187     * dataset.
188     */
189    private boolean useYInterval;
190
191    /** Percentage margin (to reduce the width of bars). */
192    private double margin;
193
194    /** A flag that controls whether or not bar outlines are drawn. */
195    private boolean drawBarOutline;
196
197    /**
198     * An optional class used to transform gradient paint objects to fit each
199     * bar.
200     */
201    private GradientPaintTransformer gradientPaintTransformer;
202
203    /**
204     * The shape used to represent a bar in each legend item (this should never
205     * be {@code null}).
206     */
207    private transient Shape legendBar;
208
209    /**
210     * The fallback position if a positive item label doesn't fit inside the
211     * bar.
212     */
213    private ItemLabelPosition positiveItemLabelPositionFallback;
214
215    /**
216     * The fallback position if a negative item label doesn't fit inside the
217     * bar.
218     */
219    private ItemLabelPosition negativeItemLabelPositionFallback;
220
221    /**
222     * The bar painter (never {@code null}).
223     */
224    private XYBarPainter barPainter;
225
226    /**
227     * The flag that controls whether or not shadows are drawn for the bars.
228     */
229    private boolean shadowsVisible;
230
231    /**
232     * The x-offset for the shadow effect.
233     */
234    private double shadowXOffset;
235
236    /**
237     * The y-offset for the shadow effect.
238     */
239    private double shadowYOffset;
240
241    /**
242     * A factor used to align the bars about the x-value.
243     */
244    private double barAlignmentFactor;
245
246    /**
247     * The default constructor.
248     */
249    public XYBarRenderer() {
250        this(0.0);
251    }
252
253    /**
254     * Constructs a new renderer.
255     *
256     * @param margin  the percentage amount to trim from the width of each bar.
257     */
258    public XYBarRenderer(double margin) {
259        super();
260        this.margin = margin;
261        this.base = 0.0;
262        this.useYInterval = false;
263        this.gradientPaintTransformer = new StandardGradientPaintTransformer();
264        this.drawBarOutline = false;
265        this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0);
266        this.barPainter = getDefaultBarPainter();
267        this.shadowsVisible = getDefaultShadowsVisible();
268        this.shadowXOffset = 4.0;
269        this.shadowYOffset = 4.0;
270        this.barAlignmentFactor = -1.0;
271    }
272
273    /**
274     * Returns the base value for the bars.
275     *
276     * @return The base value for the bars.
277     *
278     * @see #setBase(double)
279     */
280    public double getBase() {
281        return this.base;
282    }
283
284    /**
285     * Sets the base value for the bars and sends a {@link RendererChangeEvent}
286     * to all registered listeners.  The base value is not used if the dataset's
287     * y-interval is being used to determine the bar length.
288     *
289     * @param base  the new base value.
290     *
291     * @see #getBase()
292     * @see #getUseYInterval()
293     */
294    public void setBase(double base) {
295        this.base = base;
296        fireChangeEvent();
297    }
298
299    /**
300     * Returns a flag that determines whether the y-interval from the dataset is
301     * used to calculate the length of each bar.
302     *
303     * @return A boolean.
304     *
305     * @see #setUseYInterval(boolean)
306     */
307    public boolean getUseYInterval() {
308        return this.useYInterval;
309    }
310
311    /**
312     * Sets the flag that determines whether the y-interval from the dataset is
313     * used to calculate the length of each bar, and sends a
314     * {@link RendererChangeEvent} to all registered listeners.
315     *
316     * @param use  the flag.
317     *
318     * @see #getUseYInterval()
319     */
320    public void setUseYInterval(boolean use) {
321        if (this.useYInterval != use) {
322            this.useYInterval = use;
323            fireChangeEvent();
324        }
325    }
326
327    /**
328     * Returns the margin which is a percentage amount by which the bars are
329     * trimmed.
330     *
331     * @return The margin.
332     *
333     * @see #setMargin(double)
334     */
335    public double getMargin() {
336        return this.margin;
337    }
338
339    /**
340     * Sets the percentage amount by which the bars are trimmed and sends a
341     * {@link RendererChangeEvent} to all registered listeners.
342     *
343     * @param margin  the new margin.
344     *
345     * @see #getMargin()
346     */
347    public void setMargin(double margin) {
348        this.margin = margin;
349        fireChangeEvent();
350    }
351
352    /**
353     * Returns a flag that controls whether or not bar outlines are drawn.
354     *
355     * @return A boolean.
356     *
357     * @see #setDrawBarOutline(boolean)
358     */
359    public boolean isDrawBarOutline() {
360        return this.drawBarOutline;
361    }
362
363    /**
364     * Sets the flag that controls whether or not bar outlines are drawn and
365     * sends a {@link RendererChangeEvent} to all registered listeners.
366     *
367     * @param draw  the flag.
368     *
369     * @see #isDrawBarOutline()
370     */
371    public void setDrawBarOutline(boolean draw) {
372        this.drawBarOutline = draw;
373        fireChangeEvent();
374    }
375
376    /**
377     * Returns the gradient paint transformer (an object used to transform
378     * gradient paint objects to fit each bar).
379     *
380     * @return A transformer ({@code null} possible).
381     *
382     * @see #setGradientPaintTransformer(GradientPaintTransformer)
383     */
384    public GradientPaintTransformer getGradientPaintTransformer() {
385        return this.gradientPaintTransformer;
386    }
387
388    /**
389     * Sets the gradient paint transformer and sends a
390     * {@link RendererChangeEvent} to all registered listeners.
391     *
392     * @param transformer  the transformer ({@code null} permitted).
393     *
394     * @see #getGradientPaintTransformer()
395     */
396    public void setGradientPaintTransformer(
397            GradientPaintTransformer transformer) {
398        this.gradientPaintTransformer = transformer;
399        fireChangeEvent();
400    }
401
402    /**
403     * Returns the shape used to represent bars in each legend item.
404     *
405     * @return The shape used to represent bars in each legend item (never
406     *         {@code null}).
407     *
408     * @see #setLegendBar(Shape)
409     */
410    public Shape getLegendBar() {
411        return this.legendBar;
412    }
413
414    /**
415     * Sets the shape used to represent bars in each legend item and sends a
416     * {@link RendererChangeEvent} to all registered listeners.
417     *
418     * @param bar  the bar shape ({@code null} not permitted).
419     *
420     * @see #getLegendBar()
421     */
422    public void setLegendBar(Shape bar) {
423        Args.nullNotPermitted(bar, "bar");
424        this.legendBar = bar;
425        fireChangeEvent();
426    }
427
428    /**
429     * Returns the fallback position for positive item labels that don't fit
430     * within a bar.
431     *
432     * @return The fallback position ({@code null} possible).
433     *
434     * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
435     */
436    public ItemLabelPosition getPositiveItemLabelPositionFallback() {
437        return this.positiveItemLabelPositionFallback;
438    }
439
440    /**
441     * Sets the fallback position for positive item labels that don't fit
442     * within a bar, and sends a {@link RendererChangeEvent} to all registered
443     * listeners.
444     *
445     * @param position  the position ({@code null} permitted).
446     *
447     * @see #getPositiveItemLabelPositionFallback()
448     */
449    public void setPositiveItemLabelPositionFallback(
450            ItemLabelPosition position) {
451        this.positiveItemLabelPositionFallback = position;
452        fireChangeEvent();
453    }
454
455    /**
456     * Returns the fallback position for negative item labels that don't fit
457     * within a bar.
458     *
459     * @return The fallback position ({@code null} possible).
460     *
461     * @see #setNegativeItemLabelPositionFallback(ItemLabelPosition)
462     */
463    public ItemLabelPosition getNegativeItemLabelPositionFallback() {
464        return this.negativeItemLabelPositionFallback;
465    }
466
467    /**
468     * Sets the fallback position for negative item labels that don't fit
469     * within a bar, and sends a {@link RendererChangeEvent} to all registered
470     * listeners.
471     *
472     * @param position  the position ({@code null} permitted).
473     *
474     * @see #getNegativeItemLabelPositionFallback()
475     */
476    public void setNegativeItemLabelPositionFallback(
477            ItemLabelPosition position) {
478        this.negativeItemLabelPositionFallback = position;
479        fireChangeEvent();
480    }
481
482    /**
483     * Returns the bar painter.
484     *
485     * @return The bar painter (never {@code null}).
486     */
487    public XYBarPainter getBarPainter() {
488        return this.barPainter;
489    }
490
491    /**
492     * Sets the bar painter and sends a {@link RendererChangeEvent} to all
493     * registered listeners.
494     *
495     * @param painter  the painter ({@code null} not permitted).
496     */
497    public void setBarPainter(XYBarPainter painter) {
498        Args.nullNotPermitted(painter, "painter");
499        this.barPainter = painter;
500        fireChangeEvent();
501    }
502
503    /**
504     * Returns the flag that controls whether or not shadows are drawn for
505     * the bars.
506     *
507     * @return A boolean.
508     */
509    public boolean getShadowsVisible() {
510        return this.shadowsVisible;
511    }
512
513    /**
514     * Sets the flag that controls whether or not the renderer
515     * draws shadows for the bars, and sends a
516     * {@link RendererChangeEvent} to all registered listeners.
517     *
518     * @param visible  the new flag value.
519     */
520    public void setShadowVisible(boolean visible) {
521        this.shadowsVisible = visible;
522        fireChangeEvent();
523    }
524
525    /**
526     * Returns the shadow x-offset.
527     *
528     * @return The shadow x-offset.
529     */
530    public double getShadowXOffset() {
531        return this.shadowXOffset;
532    }
533
534    /**
535     * Sets the x-offset for the bar shadow and sends a
536     * {@link RendererChangeEvent} to all registered listeners.
537     *
538     * @param offset  the offset.
539     */
540    public void setShadowXOffset(double offset) {
541        this.shadowXOffset = offset;
542        fireChangeEvent();
543    }
544
545    /**
546     * Returns the shadow y-offset.
547     *
548     * @return The shadow y-offset.
549     */
550    public double getShadowYOffset() {
551        return this.shadowYOffset;
552    }
553
554    /**
555     * Sets the y-offset for the bar shadow and sends a
556     * {@link RendererChangeEvent} to all registered listeners.
557     *
558     * @param offset  the offset.
559     */
560    public void setShadowYOffset(double offset) {
561        this.shadowYOffset = offset;
562        fireChangeEvent();
563    }
564
565    /**
566     * Returns the bar alignment factor. 
567     * 
568     * @return The bar alignment factor.
569     */
570    public double getBarAlignmentFactor() {
571        return this.barAlignmentFactor;
572    }
573
574    /**
575     * Sets the bar alignment factor and sends a {@link RendererChangeEvent}
576     * to all registered listeners.  If the alignment factor is outside the
577     * range 0.0 to 1.0, no alignment will be performed by the renderer.
578     *
579     * @param factor  the factor.
580     */
581    public void setBarAlignmentFactor(double factor) {
582        this.barAlignmentFactor = factor;
583        fireChangeEvent();
584    }
585
586    /**
587     * Initialises the renderer and returns a state object that should be
588     * passed to all subsequent calls to the drawItem() method.  Here we
589     * calculate the Java2D y-coordinate for zero, since all the bars have
590     * their bases fixed at zero.
591     *
592     * @param g2  the graphics device.
593     * @param dataArea  the area inside the axes.
594     * @param plot  the plot.
595     * @param dataset  the data.
596     * @param info  an optional info collection object to return data back to
597     *              the caller.
598     *
599     * @return A state object.
600     */
601    @Override
602    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
603            XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
604
605        XYBarRendererState state = new XYBarRendererState(info);
606        ValueAxis rangeAxis = plot.getRangeAxisForDataset(plot.indexOf(
607                dataset));
608        state.setG2Base(rangeAxis.valueToJava2D(this.base, dataArea,
609                plot.getRangeAxisEdge()));
610        return state;
611
612    }
613
614    /**
615     * Returns a default legend item for the specified series.  Subclasses
616     * should override this method to generate customised items.
617     *
618     * @param datasetIndex  the dataset index (zero-based).
619     * @param series  the series index (zero-based).
620     *
621     * @return A legend item for the series.
622     */
623    @Override
624    public LegendItem getLegendItem(int datasetIndex, int series) {
625        XYPlot xyplot = getPlot();
626        if (xyplot == null) {
627            return null;
628        }
629        XYDataset dataset = xyplot.getDataset(datasetIndex);
630        if (dataset == null) {
631            return null;
632        }
633        LegendItem result;
634        XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
635        String label = lg.generateLabel(dataset, series);
636        String description = label;
637        String toolTipText = null;
638        if (getLegendItemToolTipGenerator() != null) {
639            toolTipText = getLegendItemToolTipGenerator().generateLabel(
640                    dataset, series);
641        }
642        String urlText = null;
643        if (getLegendItemURLGenerator() != null) {
644            urlText = getLegendItemURLGenerator().generateLabel(dataset, 
645                    series);
646        }
647        Shape shape = this.legendBar;
648        Paint paint = lookupSeriesPaint(series);
649        Paint outlinePaint = lookupSeriesOutlinePaint(series);
650        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
651        if (this.drawBarOutline) {
652            result = new LegendItem(label, description, toolTipText,
653                    urlText, shape, paint, outlineStroke, outlinePaint);
654        }
655        else {
656            result = new LegendItem(label, description, toolTipText, urlText, 
657                    shape, paint);
658        }
659        result.setLabelFont(lookupLegendTextFont(series));
660        Paint labelPaint = lookupLegendTextPaint(series);
661        if (labelPaint != null) {
662            result.setLabelPaint(labelPaint);
663        }
664        result.setDataset(dataset);
665        result.setDatasetIndex(datasetIndex);
666        result.setSeriesKey(dataset.getSeriesKey(series));
667        result.setSeriesIndex(series);
668        if (getGradientPaintTransformer() != null) {
669            result.setFillPaintTransformer(getGradientPaintTransformer());
670        }
671        return result;
672    }
673
674    /**
675     * Draws the visual representation of a single data item.
676     *
677     * @param g2  the graphics device.
678     * @param state  the renderer state.
679     * @param dataArea  the area within which the plot is being drawn.
680     * @param info  collects information about the drawing.
681     * @param plot  the plot (can be used to obtain standard color
682     *              information etc).
683     * @param domainAxis  the domain axis.
684     * @param rangeAxis  the range axis.
685     * @param dataset  the dataset.
686     * @param series  the series index (zero-based).
687     * @param item  the item index (zero-based).
688     * @param crosshairState  crosshair information for the plot
689     *                        ({@code null} permitted).
690     * @param pass  the pass index.
691     */
692    @Override
693    public void drawItem(Graphics2D g2, XYItemRendererState state,
694            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
695            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
696            int series, int item, CrosshairState crosshairState, int pass) {
697
698        if (!getItemVisible(series, item)) {
699            return;
700        }
701        IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
702
703        double value0;
704        double value1;
705        if (this.useYInterval) {
706            value0 = intervalDataset.getStartYValue(series, item);
707            value1 = intervalDataset.getEndYValue(series, item);
708        } else {
709            value0 = this.base;
710            value1 = intervalDataset.getYValue(series, item);
711        }
712        if (Double.isNaN(value0) || Double.isNaN(value1)) {
713            return;
714        }
715        if (value0 <= value1) {
716            if (!rangeAxis.getRange().intersects(value0, value1)) {
717                return;
718            }
719        } else {
720            if (!rangeAxis.getRange().intersects(value1, value0)) {
721                return;
722            }
723        }
724
725        double translatedValue0 = rangeAxis.valueToJava2D(value0, dataArea,
726                plot.getRangeAxisEdge());
727        double translatedValue1 = rangeAxis.valueToJava2D(value1, dataArea,
728                plot.getRangeAxisEdge());
729        double bottom = Math.min(translatedValue0, translatedValue1);
730        double top = Math.max(translatedValue0, translatedValue1);
731
732        double startX = intervalDataset.getStartXValue(series, item);
733        if (Double.isNaN(startX)) {
734            return;
735        }
736        double endX = intervalDataset.getEndXValue(series, item);
737        if (Double.isNaN(endX)) {
738            return;
739        }
740        if (startX <= endX) {
741            if (!domainAxis.getRange().intersects(startX, endX)) {
742                return;
743            }
744        } else {
745            if (!domainAxis.getRange().intersects(endX, startX)) {
746                return;
747            }
748        }
749
750        // is there an alignment adjustment to be made?
751        if (this.barAlignmentFactor >= 0.0 && this.barAlignmentFactor <= 1.0) {
752            double x = intervalDataset.getXValue(series, item);
753            double interval = endX - startX;
754            startX = x - interval * this.barAlignmentFactor;
755            endX = startX + interval;
756        }
757
758        RectangleEdge location = plot.getDomainAxisEdge();
759        double translatedStartX = domainAxis.valueToJava2D(startX, dataArea,
760                location);
761        double translatedEndX = domainAxis.valueToJava2D(endX, dataArea,
762                location);
763
764        double translatedWidth = Math.max(1, Math.abs(translatedEndX
765                - translatedStartX));
766
767        double left = Math.min(translatedStartX, translatedEndX);
768        if (getMargin() > 0.0) {
769            double cut = translatedWidth * getMargin();
770            translatedWidth = translatedWidth - cut;
771            left = left + cut / 2;
772        }
773
774        Rectangle2D bar = null;
775        PlotOrientation orientation = plot.getOrientation();
776        if (orientation.isHorizontal()) {
777            // clip left and right bounds to data area
778            bottom = Math.max(bottom, dataArea.getMinX());
779            top = Math.min(top, dataArea.getMaxX());
780            bar = new Rectangle2D.Double(
781                bottom, left, top - bottom, translatedWidth);
782        } else if (orientation.isVertical()) {
783            // clip top and bottom bounds to data area
784            bottom = Math.max(bottom, dataArea.getMinY());
785            top = Math.min(top, dataArea.getMaxY());
786            bar = new Rectangle2D.Double(left, bottom, translatedWidth,
787                    top - bottom);
788        }
789
790        boolean positive = (value1 > 0.0);
791        boolean inverted = rangeAxis.isInverted();
792        RectangleEdge barBase;
793        if (orientation.isHorizontal()) {
794            if (positive && inverted || !positive && !inverted) {
795                barBase = RectangleEdge.RIGHT;
796            } else {
797                barBase = RectangleEdge.LEFT;
798            }
799        } else {
800            if (positive && !inverted || !positive && inverted) {
801                barBase = RectangleEdge.BOTTOM;
802            } else {
803                barBase = RectangleEdge.TOP;
804            }
805        }
806        
807        if (state.getElementHinting()) {
808            beginElementGroup(g2, dataset.getSeriesKey(series), item);
809        }
810        if (getShadowsVisible()) {
811            this.barPainter.paintBarShadow(g2, this, series, item, bar, barBase,
812                !this.useYInterval);
813        }
814        this.barPainter.paintBar(g2, this, series, item, bar, barBase);
815        if (state.getElementHinting()) {
816            endElementGroup(g2);
817        }
818
819        if (isItemLabelVisible(series, item)) {
820            XYItemLabelGenerator generator = getItemLabelGenerator(series,
821                    item);
822            drawItemLabel(g2, dataset, series, item, plot, generator, bar,
823                    value1 < 0.0);
824        }
825
826        // update the crosshair point
827        double x1 = (startX + endX) / 2.0;
828        double y1 = dataset.getYValue(series, item);
829        double transX1 = domainAxis.valueToJava2D(x1, dataArea, location);
830        double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
831                plot.getRangeAxisEdge());
832        int datasetIndex = plot.indexOf(dataset);
833        updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
834                transX1, transY1, plot.getOrientation());
835
836        EntityCollection entities = state.getEntityCollection();
837        if (entities != null) {
838            addEntity(entities, bar, dataset, series, item, 0.0, 0.0);
839        }
840
841    }
842
843    /**
844     * Draws an item label.  This method is provided as an alternative to
845     * {@link #drawItemLabel(Graphics2D, PlotOrientation, XYDataset, int, int,
846     * double, double, boolean)} so that the bar can be used to calculate the
847     * label anchor point.
848     *
849     * @param g2  the graphics device.
850     * @param dataset  the dataset.
851     * @param series  the series index.
852     * @param item  the item index.
853     * @param plot  the plot.
854     * @param generator  the label generator ({@code null} permitted, in
855     *         which case the method does nothing, just returns).
856     * @param bar  the bar.
857     * @param negative  a flag indicating a negative value.
858     */
859    protected void drawItemLabel(Graphics2D g2, XYDataset dataset,
860            int series, int item, XYPlot plot, XYItemLabelGenerator generator,
861            Rectangle2D bar, boolean negative) {
862
863        if (generator == null) {
864            return;  // nothing to do
865        }
866        String label = generator.generateLabel(dataset, series, item);
867        if (label == null) {
868            return;  // nothing to do
869        }
870
871        Font labelFont = getItemLabelFont(series, item);
872        g2.setFont(labelFont);
873        Paint paint = getItemLabelPaint(series, item);
874        g2.setPaint(paint);
875
876        // find out where to place the label...
877        ItemLabelPosition position;
878        if (!negative) {
879            position = getPositiveItemLabelPosition(series, item);
880        } else {
881            position = getNegativeItemLabelPosition(series, item);
882        }
883
884        // work out the label anchor point...
885        Point2D anchorPoint = calculateLabelAnchorPoint(
886                position.getItemLabelAnchor(), bar, plot.getOrientation());
887
888        if (isInternalAnchor(position.getItemLabelAnchor())) {
889            Shape bounds = TextUtils.calculateRotatedStringBounds(label,
890                    g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
891                    position.getTextAnchor(), position.getAngle(),
892                    position.getRotationAnchor());
893
894            if (bounds != null) {
895                if (!bar.contains(bounds.getBounds2D())) {
896                    if (!negative) {
897                        position = getPositiveItemLabelPositionFallback();
898                    }
899                    else {
900                        position = getNegativeItemLabelPositionFallback();
901                    }
902                    if (position != null) {
903                        anchorPoint = calculateLabelAnchorPoint(
904                                position.getItemLabelAnchor(), bar,
905                                plot.getOrientation());
906                    }
907                }
908            }
909
910        }
911
912        if (position != null) {
913            TextUtils.drawRotatedString(label, g2,
914                    (float) anchorPoint.getX(), (float) anchorPoint.getY(),
915                    position.getTextAnchor(), position.getAngle(),
916                    position.getRotationAnchor());
917        }
918    }
919
920    /**
921     * Calculates the item label anchor point.
922     *
923     * @param anchor  the anchor.
924     * @param bar  the bar.
925     * @param orientation  the plot orientation.
926     *
927     * @return The anchor point.
928     */
929    private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
930            Rectangle2D bar, PlotOrientation orientation) {
931
932        Point2D result = null;
933        double offset = getItemLabelAnchorOffset();
934        double x0 = bar.getX() - offset;
935        double x1 = bar.getX();
936        double x2 = bar.getX() + offset;
937        double x3 = bar.getCenterX();
938        double x4 = bar.getMaxX() - offset;
939        double x5 = bar.getMaxX();
940        double x6 = bar.getMaxX() + offset;
941
942        double y0 = bar.getMaxY() + offset;
943        double y1 = bar.getMaxY();
944        double y2 = bar.getMaxY() - offset;
945        double y3 = bar.getCenterY();
946        double y4 = bar.getMinY() + offset;
947        double y5 = bar.getMinY();
948        double y6 = bar.getMinY() - offset;
949
950        if (anchor == ItemLabelAnchor.CENTER) {
951            result = new Point2D.Double(x3, y3);
952        }
953        else if (anchor == ItemLabelAnchor.INSIDE1) {
954            result = new Point2D.Double(x4, y4);
955        }
956        else if (anchor == ItemLabelAnchor.INSIDE2) {
957            result = new Point2D.Double(x4, y4);
958        }
959        else if (anchor == ItemLabelAnchor.INSIDE3) {
960            result = new Point2D.Double(x4, y3);
961        }
962        else if (anchor == ItemLabelAnchor.INSIDE4) {
963            result = new Point2D.Double(x4, y2);
964        }
965        else if (anchor == ItemLabelAnchor.INSIDE5) {
966            result = new Point2D.Double(x4, y2);
967        }
968        else if (anchor == ItemLabelAnchor.INSIDE6) {
969            result = new Point2D.Double(x3, y2);
970        }
971        else if (anchor == ItemLabelAnchor.INSIDE7) {
972            result = new Point2D.Double(x2, y2);
973        }
974        else if (anchor == ItemLabelAnchor.INSIDE8) {
975            result = new Point2D.Double(x2, y2);
976        }
977        else if (anchor == ItemLabelAnchor.INSIDE9) {
978            result = new Point2D.Double(x2, y3);
979        }
980        else if (anchor == ItemLabelAnchor.INSIDE10) {
981            result = new Point2D.Double(x2, y4);
982        }
983        else if (anchor == ItemLabelAnchor.INSIDE11) {
984            result = new Point2D.Double(x2, y4);
985        }
986        else if (anchor == ItemLabelAnchor.INSIDE12) {
987            result = new Point2D.Double(x3, y4);
988        }
989        else if (anchor == ItemLabelAnchor.OUTSIDE1) {
990            result = new Point2D.Double(x5, y6);
991        }
992        else if (anchor == ItemLabelAnchor.OUTSIDE2) {
993            result = new Point2D.Double(x6, y5);
994        }
995        else if (anchor == ItemLabelAnchor.OUTSIDE3) {
996            result = new Point2D.Double(x6, y3);
997        }
998        else if (anchor == ItemLabelAnchor.OUTSIDE4) {
999            result = new Point2D.Double(x6, y1);
1000        }
1001        else if (anchor == ItemLabelAnchor.OUTSIDE5) {
1002            result = new Point2D.Double(x5, y0);
1003        }
1004        else if (anchor == ItemLabelAnchor.OUTSIDE6) {
1005            result = new Point2D.Double(x3, y0);
1006        }
1007        else if (anchor == ItemLabelAnchor.OUTSIDE7) {
1008            result = new Point2D.Double(x1, y0);
1009        }
1010        else if (anchor == ItemLabelAnchor.OUTSIDE8) {
1011            result = new Point2D.Double(x0, y1);
1012        }
1013        else if (anchor == ItemLabelAnchor.OUTSIDE9) {
1014            result = new Point2D.Double(x0, y3);
1015        }
1016        else if (anchor == ItemLabelAnchor.OUTSIDE10) {
1017            result = new Point2D.Double(x0, y5);
1018        }
1019        else if (anchor == ItemLabelAnchor.OUTSIDE11) {
1020            result = new Point2D.Double(x1, y6);
1021        }
1022        else if (anchor == ItemLabelAnchor.OUTSIDE12) {
1023            result = new Point2D.Double(x3, y6);
1024        }
1025
1026        return result;
1027
1028    }
1029
1030    /**
1031     * Returns {@code true} if the specified anchor point is inside a bar.
1032     *
1033     * @param anchor  the anchor point.
1034     *
1035     * @return A boolean.
1036     */
1037    private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1038        return anchor == ItemLabelAnchor.CENTER
1039               || anchor == ItemLabelAnchor.INSIDE1
1040               || anchor == ItemLabelAnchor.INSIDE2
1041               || anchor == ItemLabelAnchor.INSIDE3
1042               || anchor == ItemLabelAnchor.INSIDE4
1043               || anchor == ItemLabelAnchor.INSIDE5
1044               || anchor == ItemLabelAnchor.INSIDE6
1045               || anchor == ItemLabelAnchor.INSIDE7
1046               || anchor == ItemLabelAnchor.INSIDE8
1047               || anchor == ItemLabelAnchor.INSIDE9
1048               || anchor == ItemLabelAnchor.INSIDE10
1049               || anchor == ItemLabelAnchor.INSIDE11
1050               || anchor == ItemLabelAnchor.INSIDE12;
1051    }
1052
1053    /**
1054     * Returns the lower and upper bounds (range) of the x-values in the
1055     * specified dataset.  Since this renderer uses the x-interval in the
1056     * dataset, this is taken into account for the range.
1057     *
1058     * @param dataset  the dataset ({@code null} permitted).
1059     *
1060     * @return The range ({@code null} if the dataset is
1061     *         {@code null} or empty).
1062     */
1063    @Override
1064    public Range findDomainBounds(XYDataset dataset) {
1065        return findDomainBounds(dataset, true);
1066    }
1067
1068    /**
1069     * Returns the lower and upper bounds (range) of the y-values in the
1070     * specified dataset.  If the renderer is plotting the y-interval from the
1071     * dataset, this is taken into account for the range.
1072     *
1073     * @param dataset  the dataset ({@code null} permitted).
1074     *
1075     * @return The range ({@code null} if the dataset is
1076     *         {@code null} or empty).
1077     */
1078    @Override
1079    public Range findRangeBounds(XYDataset dataset) {
1080        return findRangeBounds(dataset, this.useYInterval);
1081    }
1082
1083    /**
1084     * Returns a clone of the renderer.
1085     *
1086     * @return A clone.
1087     *
1088     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
1089     */
1090    @Override
1091    public Object clone() throws CloneNotSupportedException {
1092        XYBarRenderer result = (XYBarRenderer) super.clone();
1093        if (this.gradientPaintTransformer != null) {
1094            result.gradientPaintTransformer = (GradientPaintTransformer)
1095                ObjectUtils.clone(this.gradientPaintTransformer);
1096        }
1097        result.legendBar = ShapeUtils.clone(this.legendBar);
1098        return result;
1099    }
1100
1101    /**
1102     * Tests this renderer for equality with an arbitrary object.
1103     *
1104     * @param obj  the object to test against ({@code null} permitted).
1105     *
1106     * @return A boolean.
1107     */
1108    @Override
1109    public boolean equals(Object obj) {
1110        if (obj == this) {
1111            return true;
1112        }
1113        if (!(obj instanceof XYBarRenderer)) {
1114            return false;
1115        }
1116        XYBarRenderer that = (XYBarRenderer) obj;
1117        if (this.base != that.base) {
1118            return false;
1119        }
1120        if (this.drawBarOutline != that.drawBarOutline) {
1121            return false;
1122        }
1123        if (this.margin != that.margin) {
1124            return false;
1125        }
1126        if (this.useYInterval != that.useYInterval) {
1127            return false;
1128        }
1129        if (!Objects.equals(this.gradientPaintTransformer,
1130                that.gradientPaintTransformer)) {
1131            return false;
1132        }
1133        if (!ShapeUtils.equal(this.legendBar, that.legendBar)) {
1134            return false;
1135        }
1136        if (!Objects.equals(this.positiveItemLabelPositionFallback,
1137                that.positiveItemLabelPositionFallback)) {
1138            return false;
1139        }
1140        if (!Objects.equals(this.negativeItemLabelPositionFallback,
1141                that.negativeItemLabelPositionFallback)) {
1142            return false;
1143        }
1144        if (!this.barPainter.equals(that.barPainter)) {
1145            return false;
1146        }
1147        if (this.shadowsVisible != that.shadowsVisible) {
1148            return false;
1149        }
1150        if (this.shadowXOffset != that.shadowXOffset) {
1151            return false;
1152        }
1153        if (this.shadowYOffset != that.shadowYOffset) {
1154            return false;
1155        }
1156        if (this.barAlignmentFactor != that.barAlignmentFactor) {
1157            return false;
1158        }
1159        return super.equals(obj);
1160    }
1161
1162    /**
1163     * Provides serialization support.
1164     *
1165     * @param stream  the input stream.
1166     *
1167     * @throws IOException  if there is an I/O error.
1168     * @throws ClassNotFoundException  if there is a classpath problem.
1169     */
1170    private void readObject(ObjectInputStream stream)
1171            throws IOException, ClassNotFoundException {
1172        stream.defaultReadObject();
1173        this.legendBar = SerialUtils.readShape(stream);
1174    }
1175
1176    /**
1177     * Provides serialization support.
1178     *
1179     * @param stream  the output stream.
1180     *
1181     * @throws IOException  if there is an I/O error.
1182     */
1183    private void writeObject(ObjectOutputStream stream) throws IOException {
1184        stream.defaultWriteObject();
1185        SerialUtils.writeShape(this.legendBar, stream);
1186    }
1187
1188}