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