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