    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, 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     *
    037     * $Id: XYBarRenderer.java,v 1.14.2.4 2005/11/28 12:06:35 mungady Exp $
    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     *
    075     */
    076
    077    package org.jfree.chart.renderer.xy;
    078
    079    import java.awt.GradientPaint;
    080    import java.awt.Graphics2D;
    081    import java.awt.Paint;
    082    import java.awt.Shape;
    083    import java.awt.Stroke;
    084    import java.awt.geom.Rectangle2D;
    085    import java.io.IOException;
    086    import java.io.ObjectInputStream;
    087    import java.io.ObjectOutputStream;
    088    import java.io.Serializable;
    089
    090    import org.jfree.chart.LegendItem;
    091    import org.jfree.chart.axis.ValueAxis;
    092    import org.jfree.chart.entity.EntityCollection;
    093    import org.jfree.chart.entity.XYItemEntity;
    094    import org.jfree.chart.event.RendererChangeEvent;
    095    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    096    import org.jfree.chart.labels.XYToolTipGenerator;
    097    import org.jfree.chart.plot.CrosshairState;
    098    import org.jfree.chart.plot.PlotOrientation;
    099    import org.jfree.chart.plot.PlotRenderingInfo;
    100    import org.jfree.chart.plot.XYPlot;
    101    import org.jfree.data.Range;
    102    import org.jfree.data.general.DatasetUtilities;
    103    import org.jfree.data.xy.IntervalXYDataset;
    104    import org.jfree.data.xy.XYDataset;
    105    import org.jfree.io.SerialUtilities;
    106    import org.jfree.ui.GradientPaintTransformer;
    107    import org.jfree.ui.RectangleEdge;
    108    import org.jfree.ui.StandardGradientPaintTransformer;
    109    import org.jfree.util.ObjectUtilities;
    110    import org.jfree.util.PublicCloneable;
    111    import org.jfree.util.ShapeUtilities;
    112
    113    /**
    114     * A renderer that draws bars on an {@link XYPlot} (requires an
    115     * {@link IntervalXYDataset}).
    116     * <P>
    117     * This renderer does not include any code for calculating the crosshair point.
    118     */
    119    public class XYBarRenderer extends AbstractXYItemRenderer
    120                               implements XYItemRenderer,
    121                                          Cloneable,
    122                                          PublicCloneable,
    123                                          Serializable {
    124
    125        /** For serialization. */
    126        private static final long serialVersionUID = 770559577251370036L;
    127
    128        /**
    129         * The state class used by this renderer.
    130         */
    131        protected class XYBarRendererState extends XYItemRendererState {
    132
    133            /** Base for bars against the range axis, in Java 2D space. */
    134            private double g2Base;
    135
    136            /**
    137             * Creates a new state object.
    138             *
    139             * @param info  the plot rendering info.
    140             */
    141            public XYBarRendererState(PlotRenderingInfo info) {
    142                super(info);
    143            }
    144
    145            /**
    146             * Returns the base (range) value in Java 2D space.
    147             *
    148             * @return The base value.
    149             */
    150            public double getG2Base() {
    151                return this.g2Base;
    152            }
    153
    154            /**
    155             * Sets the range axis base in Java2D space.
    156             *
    157             * @param value  the value.
    158             */
    159            public void setG2Base(double value) {
    160                this.g2Base = value;
    161            }
    162        }
    163
    164        /** The default base value for the bars. */
    165        private double base;
    166
    167        /**
    168         * A flag that controls whether the bars use the y-interval supplied by the
    169         * dataset.
    170         */
    171        private boolean useYInterval;
    172
    173        /** Percentage margin (to reduce the width of bars). */
    174        private double margin;
    175
    176        /** A flag that controls whether or not bar outlines are drawn. */
    177        private boolean drawBarOutline;
    178
    179        /**
    180         * An optional class used to transform gradient paint objects to fit each
    181         * bar.
    182         */
    183        private GradientPaintTransformer gradientPaintTransformer;
    184
    185        /**
    186         * The shape used to represent a bar in each legend item (this should never
    187         * be <code>null</code>).
    188         */
    189        private transient Shape legendBar;
    190
    191        /**
    192         * The default constructor.
    193         */
    194        public XYBarRenderer() {
    195            this(0.0);
    196        }
    197
    198        /**
    199         * Constructs a new renderer.
    200         *
    201         * @param margin  the percentage amount to trim from the width of each bar.
    202         */
    203        public XYBarRenderer(double margin) {
    204            super();
    205            this.margin = margin;
    206            this.base = 0.0;
    207            this.useYInterval = false;
    208            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    209            this.drawBarOutline = true;
    210            this.legendBar = new Rectangle2D.Double(-3.0, -5.0, 6.0, 10.0);
    211        }
    212
    213        /**
    214         * Returns the base value for the bars.
    215         *
    216         * @return The base value for the bars.
    217         */
    218        public double getBase() {
    219            return this.base;
    220        }
    221
    222        /**
    223         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    224         * to all registered listeners.  The base value is not used if the dataset's
    225         * y-interval is being used to determine the bar length.
    226         *
    227         * @param base  the new base value.
    228         */
    229        public void setBase(double base) {
    230            this.base = base;
    231            notifyListeners(new RendererChangeEvent(this));
    232        }
    233
    234        /**
    235         * Returns a flag that determines whether the y-interval from the dataset is
    236         * used to calculate the length of each bar.
    237         *
    238         * @return A boolean.
    239         */
    240        public boolean getUseYInterval() {
    241            return this.useYInterval;
    242        }
    243
    244        /**
    245         * Sets the flag that determines whether the y-interval from the dataset is
    246         * used to calculate the length of each bar, and sends a
    247         * {@link RendererChangeEvent} to all registered listeners..
    248         *
    249         * @param use  the flag.
    250         */
    251        public void setUseYInterval(boolean use) {
    252            this.useYInterval = use;
    253            notifyListeners(new RendererChangeEvent(this));
    254        }
    255
    256        /**
    257         * Returns the margin which is a percentage amount by which the bars are
    258         * trimmed.
    259         *
    260         * @return The margin.
    261         */
    262        public double getMargin() {
    263            return this.margin;
    264        }
    265
    266        /**
    267         * Sets the percentage amount by which the bars are trimmed and sends a
    268         * {@link RendererChangeEvent} to all registered listeners.
    269         *
    270         * @param margin  the new margin.
    271         */
    272        public void setMargin(double margin) {
    273            this.margin = margin;
    274            notifyListeners(new RendererChangeEvent(this));
    275        }
    276
    277        /**
    278         * Returns a flag that controls whether or not bar outlines are drawn.
    279         *
    280         * @return A boolean.
    281         */
    282        public boolean isDrawBarOutline() {
    283            return this.drawBarOutline;
    284        }
    285
    286        /**
    287         * Sets the flag that controls whether or not bar outlines are drawn and
    288         * sends a {@link RendererChangeEvent} to all registered listeners.
    289         *
    290         * @param draw  the flag.
    291         */
    292        public void setDrawBarOutline(boolean draw) {
    293            this.drawBarOutline = draw;
    294            notifyListeners(new RendererChangeEvent(this));
    295        }
    296
    297        /**
    298         * Returns the gradient paint transformer (an object used to transform
    299         * gradient paint objects to fit each bar.
    300         *
    301         * @return A transformer (<code>null</code> possible).
    302         */
    303        public GradientPaintTransformer getGradientPaintTransformer() {
    304            return this.gradientPaintTransformer;
    305        }
    306
    307        /**
    308         * Sets the gradient paint transformer and sends a
    309         * {@link RendererChangeEvent} to all registered listeners.
    310         *
    311         * @param transformer  the transformer (<code>null</code> permitted).
    312         */
    313        public void setGradientPaintTransformer(
    314                GradientPaintTransformer transformer) {
    315            this.gradientPaintTransformer = transformer;
    316            notifyListeners(new RendererChangeEvent(this));
    317        }
    318
    319        /**
    320         * Returns the shape used to represent bars in each legend item.
    321         *
    322         * @return The shape used to represent bars in each legend item (never
    323         *         <code>null</code>).
    324         */
    325        public Shape getLegendBar() {
    326            return this.legendBar;
    327        }
    328
    329        /**
    330         * Sets the shape used to represent bars in each legend item.
    331         *
    332         * @param bar  the bar shape (<code>null</code> not permitted).
    333         */
    334        public void setLegendBar(Shape bar) {
    335            if (bar == null) {
    336                throw new IllegalArgumentException("Null 'bar' argument.");
    337            }
    338            this.legendBar = bar;
    339            notifyListeners(new RendererChangeEvent(this));
    340        }
    341
    342        /**
    343         * Initialises the renderer and returns a state object that should be
    344         * passed to all subsequent calls to the drawItem() method.  Here we
    345         * calculate the Java2D y-coordinate for zero, since all the bars have
    346         * their bases fixed at zero.
    347         *
    348         * @param g2  the graphics device.
    349         * @param dataArea  the area inside the axes.
    350         * @param plot  the plot.
    351         * @param dataset  the data.
    352         * @param info  an optional info collection object to return data back to
    353         *              the caller.
    354         *
    355         * @return A state object.
    356         */
    357        public XYItemRendererState initialise(Graphics2D g2,
    358                                              Rectangle2D dataArea,
    359                                              XYPlot plot,
    360                                              XYDataset dataset,
    361                                              PlotRenderingInfo info) {
    362
    363            XYBarRendererState state = new XYBarRendererState(info);
    364            ValueAxis rangeAxis
    365                = plot.getRangeAxisForDataset(plot.indexOf(dataset));
    366            state.setG2Base(
    367                rangeAxis.valueToJava2D(
    368                    this.base, dataArea, plot.getRangeAxisEdge()
    369                )
    370            );
    371            return state;
    372
    373        }
    374
    375        /**
    376         * Returns a default legend item for the specified series.  Subclasses
    377         * should override this method to generate customised items.
    378         *
    379         * @param datasetIndex  the dataset index (zero-based).
    380         * @param series  the series index (zero-based).
    381         *
    382         * @return A legend item for the series.
    383         */
    384        public LegendItem getLegendItem(int datasetIndex, int series) {
    385            LegendItem result = null;
    386            XYPlot xyplot = getPlot();
    387            if (xyplot != null) {
    388                XYDataset dataset = xyplot.getDataset(datasetIndex);
    389                if (dataset != null) {
    390                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    391                    String label = lg.generateLabel(dataset, series);
    392                    String description = label;
    393                    String toolTipText = null;
    394                    if (getLegendItemToolTipGenerator() != null) {
    395                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    396                            dataset, series
    397                        );
    398                    }
    399                    String urlText = null;
    400                    if (getLegendItemURLGenerator() != null) {
    401                        urlText = getLegendItemURLGenerator().generateLabel(
    402                            dataset, series
    403                        );
    404                    }
    405                    Shape shape = this.legendBar;
    406                    Paint paint = getSeriesPaint(series);
    407                    Paint outlinePaint = getSeriesOutlinePaint(series);
    408                    Stroke outlineStroke = getSeriesOutlineStroke(series);
    409                    result = new LegendItem(label, description, toolTipText,
    410                            urlText, shape, paint, outlineStroke, outlinePaint);
    411                }
    412            }
    413            return result;
    414        }
    415
    416        /**
    417         * Draws the visual representation of a single data item.
    418         *
    419         * @param g2  the graphics device.
    420         * @param state  the renderer state.
    421         * @param dataArea  the area within which the plot is being drawn.
    422         * @param info  collects information about the drawing.
    423         * @param plot  the plot (can be used to obtain standard color
    424         *              information etc).
    425         * @param domainAxis  the domain axis.
    426         * @param rangeAxis  the range axis.
    427         * @param dataset  the dataset.
    428         * @param series  the series index (zero-based).
    429         * @param item  the item index (zero-based).
    430         * @param crosshairState  crosshair information for the plot
    431         *                        (<code>null</code> permitted).
    432         * @param pass  the pass index.
    433         */
    434        public void drawItem(Graphics2D g2,
    435                             XYItemRendererState state,
    436                             Rectangle2D dataArea,
    437                             PlotRenderingInfo info,
    438                             XYPlot plot,
    439                             ValueAxis domainAxis,
    440                             ValueAxis rangeAxis,
    441                             XYDataset dataset,
    442                             int series,
    443                             int item,
    444                             CrosshairState crosshairState,
    445                             int pass) {
    446
    447            if (!getItemVisible(series, item)) {
    448                return;
    449            }
    450            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
    451
    452            double value0;
    453            double value1;
    454            if (this.useYInterval) {
    455                value0 = intervalDataset.getStartYValue(series, item);
    456                value1 = intervalDataset.getEndYValue(series, item);
    457            }
    458            else {
    459                value0 = this.base;
    460                value1 = intervalDataset.getYValue(series, item);
    461            }
    462            if (Double.isNaN(value0) || Double.isNaN(value1)) {
    463                return;
    464            }
    465
    466            double translatedValue0 = rangeAxis.valueToJava2D(
    467                value0, dataArea, plot.getRangeAxisEdge()
    468            );
    469            double translatedValue1 = rangeAxis.valueToJava2D(
    470                value1, dataArea, plot.getRangeAxisEdge()
    471            );
    472
    473            RectangleEdge location = plot.getDomainAxisEdge();
    474            Number startXNumber = intervalDataset.getStartX(series, item);
    475            if (startXNumber == null) {
    476                return;
    477            }
    478            double translatedStartX = domainAxis.valueToJava2D(
    479                startXNumber.doubleValue(), dataArea, location
    480            );
    481
    482            Number endXNumber = intervalDataset.getEndX(series, item);
    483            if (endXNumber == null) {
    484                return;
    485            }
    486            double translatedEndX = domainAxis.valueToJava2D(
    487                endXNumber.doubleValue(), dataArea, location
    488            );
    489
    490            double translatedWidth = Math.max(
    491                1, Math.abs(translatedEndX - translatedStartX)
    492            );
    493            double translatedHeight = Math.abs(translatedValue1 - translatedValue0);
    494
    495            if (getMargin() > 0.0) {
    496                double cut = translatedWidth * getMargin();
    497                translatedWidth = translatedWidth - cut;
    498                translatedStartX = translatedStartX + cut / 2;
    499            }
    500
    501            Rectangle2D bar = null;
    502            PlotOrientation orientation = plot.getOrientation();
    503            if (orientation == PlotOrientation.HORIZONTAL) {
    504                bar = new Rectangle2D.Double(
    505                    Math.min(translatedValue0, translatedValue1),
    506                    Math.min(translatedStartX, translatedEndX),
    507                    translatedHeight, translatedWidth);
    508            }
    509            else if (orientation == PlotOrientation.VERTICAL) {
    510                bar = new Rectangle2D.Double(
    511                    Math.min(translatedStartX, translatedEndX),
    512                    Math.min(translatedValue0, translatedValue1),
    513                    translatedWidth, translatedHeight);
    514            }
    515
    516            Paint itemPaint = getItemPaint(series, item);
    517            if (getGradientPaintTransformer()
    518                    != null && itemPaint instanceof GradientPaint) {
    519                GradientPaint gp = (GradientPaint) itemPaint;
    520                itemPaint = getGradientPaintTransformer().transform(gp, bar);
    521            }
    522            g2.setPaint(itemPaint);
    523            g2.fill(bar);
    524            if (isDrawBarOutline()
    525                    && Math.abs(translatedEndX - translatedStartX) > 3) {
    526                Stroke stroke = getItemOutlineStroke(series, item);
    527                Paint paint = getItemOutlinePaint(series, item);
    528                if (stroke != null && paint != null) {
    529                    g2.setStroke(stroke);
    530                    g2.setPaint(paint);
    531                    g2.draw(bar);
    532                }
    533            }
    534
    535            // TODO: we need something better for the item labels
    536            if (isItemLabelVisible(series, item)) {
    537                drawItemLabel(
    538                    g2, orientation, dataset, series, item, bar.getCenterX(),
    539                    bar.getY(), value1 < 0.0
    540                );
    541            }
    542
    543            // add an entity for the item...
    544            if (info != null) {
    545                EntityCollection entities = info.getOwner().getEntityCollection();
    546                if (entities != null) {
    547                    String tip = null;
    548                    XYToolTipGenerator generator
    549                        = getToolTipGenerator(series, item);
    550                    if (generator != null) {
    551                        tip = generator.generateToolTip(dataset, series, item);
    552                    }
    553                    String url = null;
    554                    if (getURLGenerator() != null) {
    555                        url = getURLGenerator().generateURL(dataset, series, item);
    556                    }
    557                    XYItemEntity entity = new XYItemEntity(
    558                        bar, dataset, series, item, tip, url
    559                    );
    560                    entities.add(entity);
    561                }
    562            }
    563
    564        }
    565
    566        /**
    567         * Returns the lower and upper bounds (range) of the x-values in the
    568         * specified dataset.  Since this renderer uses the x-interval in the
    569         * dataset, this is taken into account for the range.
    570         *
    571         * @param dataset  the dataset (<code>null</code> permitted).
    572         *
    573         * @return The range (<code>null</code> if the dataset is
    574         *         <code>null</code> or empty).
    575         */
    576        public Range findDomainBounds(XYDataset dataset) {
    577            if (dataset != null) {
    578                return DatasetUtilities.findDomainBounds(dataset, true);
    579            }
    580            else {
    581                return null;
    582            }
    583        }
    584
    585        /**
    586         * Returns a clone of the renderer.
    587         *
    588         * @return A clone.
    589         *
    590         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    591         */
    592        public Object clone() throws CloneNotSupportedException {
    593            XYBarRenderer result = (XYBarRenderer) super.clone();
    594            if (this.gradientPaintTransformer != null) {
    595                result.gradientPaintTransformer = (GradientPaintTransformer)
    596                    ObjectUtilities.clone(this.gradientPaintTransformer);
    597            }
    598            return result;
    599        }
    600
    601        /**
    602         * Tests this renderer for equality with an arbitrary object.
    603         *
    604         * @param obj  the object to test against (<code>null</code> permitted).
    605         *
    606         * @return A boolean.
    607         */
    608        public boolean equals(Object obj) {
    609            if (obj == this) {
    610                return true;
    611            }
    612            if (!(obj instanceof XYBarRenderer)) {
    613                return false;
    614            }
    615            if (!super.equals(obj)) {
    616                return false;
    617            }
    618            XYBarRenderer that = (XYBarRenderer) obj;
    619            if (this.base != that.base) {
    620                return false;
    621            }
    622            if (this.drawBarOutline != that.drawBarOutline) {
    623                return false;
    624            }
    625            if (this.margin != that.margin) {
    626                return false;
    627            }
    628            if (this.useYInterval != that.useYInterval) {
    629                return false;
    630            }
    631            if (!ObjectUtilities.equal(
    632                this.gradientPaintTransformer, that.gradientPaintTransformer)
    633            ) {
    634                return false;
    635            }
    636            if (!ShapeUtilities.equal(this.legendBar, that.legendBar)) {
    637                return false;
    638            }
    639            return true;
    640        }
    641
    642        /**
    643         * Provides serialization support.
    644         *
    645         * @param stream  the input stream.
    646         *
    647         * @throws IOException  if there is an I/O error.
    648         * @throws ClassNotFoundException  if there is a classpath problem.
    649         */
    650        private void readObject(ObjectInputStream stream)
    651                throws IOException, ClassNotFoundException {
    652            stream.defaultReadObject();
    653            this.legendBar = SerialUtilities.readShape(stream);
    654        }
    655
    656        /**
    657         * Provides serialization support.
    658         *
    659         * @param stream  the output stream.
    660         *
    661         * @throws IOException  if there is an I/O error.
    662         */
    663        private void writeObject(ObjectOutputStream stream) throws IOException {
    664            stream.defaultWriteObject();
    665            SerialUtilities.writeShape(this.legendBar, stream);
    666        }
    667
    668    }