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