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