    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     * WaterfallBarRenderer.java
    029     * -------------------------
    030     * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  Darshan Shah;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: WaterfallBarRenderer.java,v 1.9.2.2 2005/10/25 20:54:16 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 20-Oct-2003 : Version 1, contributed by Darshan Shah (DG);
    040     * 06-Nov-2003 : Changed order of parameters in constructor, and added support
    041     *               for GradientPaint (DG);
    042     * 10-Feb-2004 : Updated drawItem() method to make cut-and-paste overriding
    043     *               easier.  Also fixed a bug that meant the minimum bar length
    044     *               was being ignored (DG);
    045     * 04-Oct-2004 : Reworked equals() method and renamed PaintUtils
    046     *               --> PaintUtilities (DG);
    047     * 05-Nov-2004 : Modified drawItem() signature (DG);
    048     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
    049     * 23-Feb-2005 : Added argument checking (DG);
    050     * 20-Apr-2005 : Renamed CategoryLabelGenerator
    051     *               --> CategoryItemLabelGenerator (DG);
    052     * 09-Jun-2005 : Use addItemEntity() from superclass (DG);
    053     */
    054
    055    package org.jfree.chart.renderer.category;
    056
    057    import java.awt.Color;
    058    import java.awt.GradientPaint;
    059    import java.awt.Graphics2D;
    060    import java.awt.Paint;
    061    import java.awt.Stroke;
    062    import java.awt.geom.Rectangle2D;
    063    import java.io.IOException;
    064    import java.io.ObjectInputStream;
    065    import java.io.ObjectOutputStream;
    066    import java.io.Serializable;
    067
    068    import org.jfree.chart.axis.CategoryAxis;
    069    import org.jfree.chart.axis.ValueAxis;
    070    import org.jfree.chart.entity.EntityCollection;
    071    import org.jfree.chart.event.RendererChangeEvent;
    072    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    073    import org.jfree.chart.plot.CategoryPlot;
    074    import org.jfree.chart.plot.PlotOrientation;
    075    import org.jfree.chart.renderer.AbstractRenderer;
    076    import org.jfree.data.Range;
    077    import org.jfree.data.category.CategoryDataset;
    078    import org.jfree.data.general.DatasetUtilities;
    079    import org.jfree.io.SerialUtilities;
    080    import org.jfree.ui.GradientPaintTransformType;
    081    import org.jfree.ui.RectangleEdge;
    082    import org.jfree.ui.StandardGradientPaintTransformer;
    083    import org.jfree.util.PaintUtilities;
    084    import org.jfree.util.PublicCloneable;
    085
    086    /**
    087     * A renderer that handles the drawing of waterfall bar charts, for use with
    088     * the {@link CategoryPlot} class.  Note that the bar colors are defined
    089     * using special methods in this class - the inherited methods (for example,
    090     * {@link AbstractRenderer#setSeriesPaint(int, Paint)}) are ignored.
    091     */
    092    public class WaterfallBarRenderer extends BarRenderer
    093                                      implements Cloneable, PublicCloneable,
    094                                                 Serializable {
    095
    096        /** For serialization. */
    097        private static final long serialVersionUID = -2482910643727230911L;
    098
    099        /** The paint used to draw the first bar. */
    100        private transient Paint firstBarPaint;
    101
    102        /** The paint used to draw the last bar. */
    103        private transient Paint lastBarPaint;
    104
    105        /** The paint used to draw bars having positive values. */
    106        private transient Paint positiveBarPaint;
    107
    108        /** The paint used to draw bars having negative values. */
    109        private transient Paint negativeBarPaint;
    110
    111        /**
    112         * Constructs a new renderer with default values for the bar colors.
    113         */
    114        public WaterfallBarRenderer() {
    115            this(
    116                new GradientPaint(
    117                    0.0f, 0.0f, new Color(0x22, 0x22, 0xFF),
    118                    0.0f, 0.0f, new Color(0x66, 0x66, 0xFF)
    119                ),
    120                new GradientPaint(
    121                    0.0f, 0.0f, new Color(0x22, 0xFF, 0x22),
    122                    0.0f, 0.0f, new Color(0x66, 0xFF, 0x66)
    123                ),
    124                new GradientPaint(
    125                    0.0f, 0.0f, new Color(0xFF, 0x22, 0x22),
    126                    0.0f, 0.0f, new Color(0xFF, 0x66, 0x66)
    127                ),
    128                new GradientPaint(
    129                    0.0f, 0.0f, new Color(0xFF, 0xFF, 0x22),
    130                    0.0f, 0.0f, new Color(0xFF, 0xFF, 0x66)
    131                )
    132            );
    133        }
    134
    135        /**
    136         * Constructs a new waterfall renderer.
    137         *
    138         * @param firstBarPaint  the color of the first bar (<code>null</code> not
    139         *                       permitted).
    140         * @param positiveBarPaint  the color for bars with positive values
    141         *                          (<code>null</code> not permitted).
    142         * @param negativeBarPaint  the color for bars with negative values
    143         *                          (<code>null</code> not permitted).
    144         * @param lastBarPaint  the color of the last bar (<code>null</code> not
    145         *                      permitted).
    146         */
    147        public WaterfallBarRenderer(Paint firstBarPaint,
    148                                    Paint positiveBarPaint,
    149                                    Paint negativeBarPaint,
    150                                    Paint lastBarPaint) {
    151            super();
    152            if (firstBarPaint == null) {
    153                throw new IllegalArgumentException("Null 'firstBarPaint' argument");
    154            }
    155            if (positiveBarPaint == null) {
    156                throw new IllegalArgumentException(
    157                    "Null 'positiveBarPaint' argument"
    158                );
    159            }
    160            if (negativeBarPaint == null) {
    161                throw new IllegalArgumentException(
    162                    "Null 'negativeBarPaint' argument"
    163                );
    164            }
    165            if (lastBarPaint == null) {
    166                throw new IllegalArgumentException("Null 'lastBarPaint' argument");
    167            }
    168            this.firstBarPaint = firstBarPaint;
    169            this.lastBarPaint = lastBarPaint;
    170            this.positiveBarPaint = positiveBarPaint;
    171            this.negativeBarPaint = negativeBarPaint;
    172            setGradientPaintTransformer(
    173                new StandardGradientPaintTransformer(
    174                    GradientPaintTransformType.CENTER_VERTICAL
    175                )
    176            );
    177            setMinimumBarLength(1.0);
    178        }
    179
    180        /**
    181         * Returns the range of values the renderer requires to display all the
    182         * items from the specified dataset.
    183         *
    184         * @param dataset  the dataset (<code>null</code> not permitted).
    185         *
    186         * @return The range (or <code>null</code> if the dataset is empty).
    187         */
    188        public Range findRangeBounds(CategoryDataset dataset) {
    189            return DatasetUtilities.findCumulativeRangeBounds(dataset);
    190        }
    191
    192        /**
    193         * Returns the paint used to draw the first bar.
    194         *
    195         * @return The paint (never <code>null</code>).
    196         */
    197        public Paint getFirstBarPaint() {
    198            return this.firstBarPaint;
    199        }
    200
    201        /**
    202         * Sets the paint that will be used to draw the first bar and sends a
    203         * {@link RendererChangeEvent} to all registered listeners.
    204         *
    205         * @param paint  the paint (<code>null</code> not permitted).
    206         */
    207        public void setFirstBarPaint(Paint paint) {
    208            if (paint == null) {
    209                throw new IllegalArgumentException("Null 'paint' argument");
    210            }
    211            this.firstBarPaint = paint;
    212            notifyListeners(new RendererChangeEvent(this));
    213        }
    214
    215        /**
    216         * Returns the paint used to draw the last bar.
    217         *
    218         * @return The paint (never <code>null</code>).
    219         */
    220        public Paint getLastBarPaint() {
    221            return this.lastBarPaint;
    222        }
    223
    224        /**
    225         * Sets the paint that will be used to draw the last bar.
    226         *
    227         * @param paint  the paint (<code>null</code> not permitted).
    228         */
    229        public void setLastBarPaint(Paint paint) {
    230            if (paint == null) {
    231                throw new IllegalArgumentException("Null 'paint' argument");
    232            }
    233            this.lastBarPaint = paint;
    234            notifyListeners(new RendererChangeEvent(this));
    235        }
    236
    237        /**
    238         * Returns the paint used to draw bars with positive values.
    239         *
    240         * @return The paint (never <code>null</code>).
    241         */
    242        public Paint getPositiveBarPaint() {
    243            return this.positiveBarPaint;
    244        }
    245
    246        /**
    247         * Sets the paint that will be used to draw bars having positive values.
    248         *
    249         * @param paint  the paint (<code>null</code> not permitted).
    250         */
    251        public void setPositiveBarPaint(Paint paint) {
    252            if (paint == null) {
    253                throw new IllegalArgumentException("Null 'paint' argument");
    254            }
    255            this.positiveBarPaint = paint;
    256            notifyListeners(new RendererChangeEvent(this));
    257        }
    258
    259        /**
    260         * Returns the paint used to draw bars with negative values.
    261         *
    262         * @return The paint (never <code>null</code>).
    263         */
    264        public Paint getNegativeBarPaint() {
    265            return this.negativeBarPaint;
    266        }
    267
    268        /**
    269         * Sets the paint that will be used to draw bars having negative values.
    270         *
    271         * @param paint  the paint (<code>null</code> not permitted).
    272         */
    273        public void setNegativeBarPaint(Paint paint) {
    274            if (paint == null) {
    275                throw new IllegalArgumentException("Null 'paint' argument");
    276            }
    277            this.negativeBarPaint = paint;
    278            notifyListeners(new RendererChangeEvent(this));
    279        }
    280
    281        /**
    282         * Draws the bar for a single (series, category) data item.
    283         *
    284         * @param g2  the graphics device.
    285         * @param state  the renderer state.
    286         * @param dataArea  the data area.
    287         * @param plot  the plot.
    288         * @param domainAxis  the domain axis.
    289         * @param rangeAxis  the range axis.
    290         * @param dataset  the dataset.
    291         * @param row  the row index (zero-based).
    292         * @param column  the column index (zero-based).
    293         * @param pass  the pass index.
    294         */
    295        public void drawItem(Graphics2D g2,
    296                             CategoryItemRendererState state,
    297                             Rectangle2D dataArea,
    298                             CategoryPlot plot,
    299                             CategoryAxis domainAxis,
    300                             ValueAxis rangeAxis,
    301                             CategoryDataset dataset,
    302                             int row,
    303                             int column,
    304                             int pass) {
    305
    306            double previous = state.getSeriesRunningTotal();
    307            if (column == dataset.getColumnCount() - 1) {
    308                previous = 0.0;
    309            }
    310            double current = 0.0;
    311            Number n = dataset.getValue(row, column);
    312            if (n != null) {
    313                current = previous + n.doubleValue();
    314            }
    315            state.setSeriesRunningTotal(current);
    316
    317            int seriesCount = getRowCount();
    318            int categoryCount = getColumnCount();
    319            PlotOrientation orientation = plot.getOrientation();
    320
    321            double rectX = 0.0;
    322            double rectY = 0.0;
    323
    324            RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
    325            RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    326
    327            // Y0
    328            double j2dy0 = rangeAxis.valueToJava2D(
    329                previous, dataArea, rangeAxisLocation
    330            );
    331
    332            // Y1
    333            double j2dy1 = rangeAxis.valueToJava2D(
    334                current, dataArea, rangeAxisLocation
    335            );
    336
    337            double valDiff = current - previous;
    338            if (j2dy1 < j2dy0) {
    339                double temp = j2dy1;
    340                j2dy1 = j2dy0;
    341                j2dy0 = temp;
    342            }
    343
    344            // BAR WIDTH
    345            double rectWidth = state.getBarWidth();
    346
    347            // BAR HEIGHT
    348            double rectHeight = Math.max(
    349                getMinimumBarLength(), Math.abs(j2dy1 - j2dy0)
    350            );
    351
    352            if (orientation == PlotOrientation.HORIZONTAL) {
    353                // BAR Y
    354                rectY = domainAxis.getCategoryStart(
    355                    column, getColumnCount(), dataArea, domainAxisLocation
    356                );
    357                if (seriesCount > 1) {
    358                    double seriesGap = dataArea.getHeight() * getItemMargin()
    359                                       / (categoryCount * (seriesCount - 1));
    360                    rectY = rectY + row * (state.getBarWidth() + seriesGap);
    361                }
    362                else {
    363                    rectY = rectY + row * state.getBarWidth();
    364                }
    365
    366                rectX = j2dy0;
    367                rectHeight = state.getBarWidth();
    368                rectWidth = Math.max(
    369                    getMinimumBarLength(), Math.abs(j2dy1 - j2dy0)
    370                );
    371
    372            }
    373            else if (orientation == PlotOrientation.VERTICAL) {
    374                // BAR X
    375                rectX = domainAxis.getCategoryStart(
    376                    column, getColumnCount(), dataArea, domainAxisLocation
    377                );
    378
    379                if (seriesCount > 1) {
    380                    double seriesGap = dataArea.getWidth() * getItemMargin()
    381                                       / (categoryCount * (seriesCount - 1));
    382                    rectX = rectX + row * (state.getBarWidth() + seriesGap);
    383                }
    384                else {
    385                    rectX = rectX + row * state.getBarWidth();
    386                }
    387
    388                rectY = j2dy0;
    389            }
    390            Rectangle2D bar = new Rectangle2D.Double(
    391                rectX, rectY, rectWidth, rectHeight
    392            );
    393            Paint seriesPaint = getFirstBarPaint();
    394            if (column == 0) {
    395                seriesPaint = getFirstBarPaint();
    396            }
    397            else if (column == categoryCount - 1) {
    398                seriesPaint = getLastBarPaint();
    399            }
    400            else {
    401                if (valDiff < 0.0) {
    402                    seriesPaint = getNegativeBarPaint();
    403                }
    404                else if (valDiff > 0.0) {
    405                    seriesPaint = getPositiveBarPaint();
    406                }
    407                else {
    408                    seriesPaint = getLastBarPaint();
    409                }
    410            }
    411            if (getGradientPaintTransformer() != null
    412                    && seriesPaint instanceof GradientPaint) {
    413                GradientPaint gp = (GradientPaint) seriesPaint;
    414                seriesPaint = getGradientPaintTransformer().transform(gp, bar);
    415            }
    416            g2.setPaint(seriesPaint);
    417            g2.fill(bar);
    418
    419            // draw the outline...
    420            if (isDrawBarOutline()
    421                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    422                Stroke stroke = getItemOutlineStroke(row, column);
    423                Paint paint = getItemOutlinePaint(row, column);
    424                if (stroke != null && paint != null) {
    425                    g2.setStroke(stroke);
    426                    g2.setPaint(paint);
    427                    g2.draw(bar);
    428                }
    429            }
    430
    431            CategoryItemLabelGenerator generator
    432                = getItemLabelGenerator(row, column);
    433            if (generator != null && isItemLabelVisible(row, column)) {
    434                drawItemLabel(
    435                    g2, dataset, row, column, plot, generator, bar, (valDiff < 0.0)
    436                );
    437            }
    438
    439            // add an item entity, if this information is being collected
    440            EntityCollection entities = state.getEntityCollection();
    441            if (entities != null) {
    442                addItemEntity(entities, dataset, row, column, bar);
    443            }
    444
    445        }
    446
    447        /**
    448         * Tests an object for equality with this instance.
    449         *
    450         * @param obj  the object (<code>null</code> permitted).
    451         *
    452         * @return A boolean.
    453         */
    454        public boolean equals(Object obj) {
    455
    456            if (obj == this) {
    457                return true;
    458            }
    459            if (!super.equals(obj)) {
    460                return false;
    461            }
    462            if (!(obj instanceof WaterfallBarRenderer)) {
    463                return false;
    464            }
    465            WaterfallBarRenderer that = (WaterfallBarRenderer) obj;
    466            if (!PaintUtilities.equal(this.firstBarPaint, that.firstBarPaint)) {
    467                return false;
    468            }
    469            if (!PaintUtilities.equal(this.lastBarPaint, that.lastBarPaint)) {
    470                return false;
    471            }
    472            if (!PaintUtilities.equal(this.positiveBarPaint,
    473                    that.positiveBarPaint)) {
    474                return false;
    475            }
    476            if (!PaintUtilities.equal(this.negativeBarPaint,
    477                    that.negativeBarPaint)) {
    478                return false;
    479            }
    480            return true;
    481
    482        }
    483
    484        /**
    485         * Provides serialization support.
    486         *
    487         * @param stream  the output stream.
    488         *
    489         * @throws IOException  if there is an I/O error.
    490         */
    491        private void writeObject(ObjectOutputStream stream) throws IOException {
    492            stream.defaultWriteObject();
    493            SerialUtilities.writePaint(this.firstBarPaint, stream);
    494            SerialUtilities.writePaint(this.lastBarPaint, stream);
    495            SerialUtilities.writePaint(this.positiveBarPaint, stream);
    496            SerialUtilities.writePaint(this.negativeBarPaint, stream);
    497        }
    498
    499        /**
    500         * Provides serialization support.
    501         *
    502         * @param stream  the input stream.
    503         *
    504         * @throws IOException  if there is an I/O error.
    505         * @throws ClassNotFoundException  if there is a classpath problem.
    506         */
    507        private void readObject(ObjectInputStream stream)
    508            throws IOException, ClassNotFoundException {
    509            stream.defaultReadObject();
    510            this.firstBarPaint = SerialUtilities.readPaint(stream);
    511            this.lastBarPaint = SerialUtilities.readPaint(stream);
    512            this.positiveBarPaint = SerialUtilities.readPaint(stream);
    513            this.negativeBarPaint = SerialUtilities.readPaint(stream);
    514        }
    515
    516    }